From 4287b94c065128d46c6743cc241f5bb3426d5e99 Mon Sep 17 00:00:00 2001 From: itamar Date: Mon, 21 Oct 2024 19:38:00 -0400 Subject: [PATCH 1/9] move optimistic block protos to v1 --- ...tria.sequencerblock.optimistic.v1alpha1.rs | 516 ++++++++++++++++++ ...equencerblock.optimistic.v1alpha1.serde.rs | 460 ++++++++++++++++ ...sequencerblock.optimisticblock.v1alpha1.rs | 516 ++++++++++++++++++ ...cerblock.optimisticblock.v1alpha1.serde.rs | 460 ++++++++++++++++ .../astria.sequencerblock.v1alpha1.rs | 506 ----------------- .../astria.sequencerblock.v1alpha1.serde.rs | 460 ---------------- crates/astria-core/src/generated/mod.rs | 12 + .../v1alpha1/service.proto} | 6 +- 8 files changed, 1967 insertions(+), 969 deletions(-) create mode 100644 crates/astria-core/src/generated/astria.sequencerblock.optimistic.v1alpha1.rs create mode 100644 crates/astria-core/src/generated/astria.sequencerblock.optimistic.v1alpha1.serde.rs create mode 100644 crates/astria-core/src/generated/astria.sequencerblock.optimisticblock.v1alpha1.rs create mode 100644 crates/astria-core/src/generated/astria.sequencerblock.optimisticblock.v1alpha1.serde.rs rename proto/sequencerblockapis/astria/sequencerblock/{v1alpha1/optimistic_block.proto => optimistic/v1alpha1/service.proto} (88%) 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.optimisticblock.v1alpha1.rs b/crates/astria-core/src/generated/astria.sequencerblock.optimisticblock.v1alpha1.rs new file mode 100644 index 0000000000..612e5c9fcf --- /dev/null +++ b/crates/astria-core/src/generated/astria.sequencerblock.optimisticblock.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.optimisticblock.v1alpha1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!( + "astria.sequencerblock.optimisticblock.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.optimisticblock.v1alpha1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!( + "astria.sequencerblock.optimisticblock.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.optimisticblock.v1alpha1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!( + "astria.sequencerblock.optimisticblock.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.optimisticblock.v1alpha1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!( + "astria.sequencerblock.optimisticblock.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.optimisticblock.v1alpha1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!( + "astria.sequencerblock.optimisticblock.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.optimisticblock.v1alpha1.OptimisticBlockService/GetOptimisticBlockStream", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "astria.sequencerblock.optimisticblock.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.optimisticblock.v1alpha1.OptimisticBlockService/GetBlockCommitmentStream", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "astria.sequencerblock.optimisticblock.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.optimisticblock.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.optimisticblock.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.optimisticblock.v1alpha1.OptimisticBlockService"; + } +} diff --git a/crates/astria-core/src/generated/astria.sequencerblock.optimisticblock.v1alpha1.serde.rs b/crates/astria-core/src/generated/astria.sequencerblock.optimisticblock.v1alpha1.serde.rs new file mode 100644 index 0000000000..bc159c576f --- /dev/null +++ b/crates/astria-core/src/generated/astria.sequencerblock.optimisticblock.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.optimisticblock.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.optimisticblock.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.optimisticblock.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.optimisticblock.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.optimisticblock.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.optimisticblock.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.optimisticblock.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.optimisticblock.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.optimisticblock.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.optimisticblock.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.optimisticblock.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.optimisticblock.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.optimisticblock.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.optimisticblock.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.optimisticblock.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..963dd5bbe3 100644 --- a/crates/astria-core/src/generated/mod.rs +++ b/crates/astria-core/src/generated/mod.rs @@ -165,6 +165,18 @@ pub mod sequencerblock { include!("astria.sequencerblock.v1.serde.rs"); } } + + pub mod optimisticblock { + pub mod v1alpha1 { + include!("astria.sequencerblock.optimisticblock.v1alpha1.rs"); + + #[cfg(feature = "serde")] + mod _serde_impl { + use super::*; + include!("astria.sequencerblock.optimisticblock.v1alpha1.serde.rs"); + } + } + } } #[path = ""] 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 From b9bf28c0c46204a0076e9216ce8591c25a336a67 Mon Sep 17 00:00:00 2001 From: itamar Date: Thu, 7 Nov 2024 13:56:51 -0500 Subject: [PATCH 2/9] add auction result --- .../src/generated/astria.bundle.v1alpha1.rs | 27 ++++ .../generated/astria.bundle.v1alpha1.serde.rs | 132 ++++++++++++++++++ .../astria/bundle/v1alpha1/bundle.proto | 16 +++ 3 files changed, 175 insertions(+) diff --git a/crates/astria-core/src/generated/astria.bundle.v1alpha1.rs b/crates/astria-core/src/generated/astria.bundle.v1alpha1.rs index 44265dd03d..bd56ae4175 100644 --- a/crates/astria-core/src/generated/astria.bundle.v1alpha1.rs +++ b/crates/astria-core/src/generated/astria.bundle.v1alpha1.rs @@ -40,6 +40,33 @@ impl ::prost::Name for Bundle { ::prost::alloc::format!("astria.bundle.v1alpha1.{}", Self::NAME) } } +/// The Allocation message is submitted by the Auctioneer to the rollup as a +/// `RollupDataSubmission` on the sequencer. +/// The rollup will verify the signature and public key against its configuration, +/// then unbundle the body into rollup transactions and execute them first in the +/// block. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Allocation { + /// The Ed25519 signature of the Auctioneer, to be verified against config by the + /// rollup. + #[prost(bytes = "bytes", tag = "1")] + pub signature: ::prost::bytes::Bytes, + /// The Ed25519 public key of the Auctioneer, to be verified against config by the + /// rollup. + #[prost(bytes = "bytes", tag = "2")] + pub public_key: ::prost::bytes::Bytes, + /// The bundle that was allocated the winning slot by the Auctioneer. + #[prost(message, optional, tag = "3")] + pub payload: ::core::option::Option, +} +impl ::prost::Name for Allocation { + const NAME: &'static str = "Allocation"; + const PACKAGE: &'static str = "astria.bundle.v1alpha1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("astria.bundle.v1alpha1.{}", Self::NAME) + } +} #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetBundleStreamResponse { diff --git a/crates/astria-core/src/generated/astria.bundle.v1alpha1.serde.rs b/crates/astria-core/src/generated/astria.bundle.v1alpha1.serde.rs index 0ae7a566fd..12c3b97cae 100644 --- a/crates/astria-core/src/generated/astria.bundle.v1alpha1.serde.rs +++ b/crates/astria-core/src/generated/astria.bundle.v1alpha1.serde.rs @@ -1,3 +1,135 @@ +impl serde::Serialize for Allocation { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.signature.is_empty() { + len += 1; + } + if !self.public_key.is_empty() { + len += 1; + } + if self.payload.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("astria.bundle.v1alpha1.Allocation", len)?; + if !self.signature.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("signature", pbjson::private::base64::encode(&self.signature).as_str())?; + } + if !self.public_key.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("publicKey", pbjson::private::base64::encode(&self.public_key).as_str())?; + } + if let Some(v) = self.payload.as_ref() { + struct_ser.serialize_field("payload", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for Allocation { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "signature", + "public_key", + "publicKey", + "payload", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Signature, + PublicKey, + Payload, + } + 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 { + "signature" => Ok(GeneratedField::Signature), + "publicKey" | "public_key" => Ok(GeneratedField::PublicKey), + "payload" => Ok(GeneratedField::Payload), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = Allocation; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct astria.bundle.v1alpha1.Allocation") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut signature__ = None; + let mut public_key__ = None; + let mut payload__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Signature => { + if signature__.is_some() { + return Err(serde::de::Error::duplicate_field("signature")); + } + signature__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::PublicKey => { + if public_key__.is_some() { + return Err(serde::de::Error::duplicate_field("publicKey")); + } + public_key__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::Payload => { + if payload__.is_some() { + return Err(serde::de::Error::duplicate_field("payload")); + } + payload__ = map_.next_value()?; + } + } + } + Ok(Allocation { + signature: signature__.unwrap_or_default(), + public_key: public_key__.unwrap_or_default(), + payload: payload__, + }) + } + } + deserializer.deserialize_struct("astria.bundle.v1alpha1.Allocation", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for BaseBlock { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result diff --git a/proto/executionapis/astria/bundle/v1alpha1/bundle.proto b/proto/executionapis/astria/bundle/v1alpha1/bundle.proto index f4d350803f..3bdc550846 100644 --- a/proto/executionapis/astria/bundle/v1alpha1/bundle.proto +++ b/proto/executionapis/astria/bundle/v1alpha1/bundle.proto @@ -24,6 +24,22 @@ message Bundle { bytes prev_rollup_block_hash = 4; } +// The Allocation message is submitted by the Auctioneer to the rollup as a +// `RollupDataSubmission` on the sequencer. +// The rollup will verify the signature and public key against its configuration, +// then unbundle the body into rollup transactions and execute them first in the +// block. +message Allocation { + // The Ed25519 signature of the Auctioneer, to be verified against config by the + // rollup. + bytes signature = 1; + // The Ed25519 public key of the Auctioneer, to be verified against config by the + // rollup. + bytes public_key = 2; + // The bundle that was allocated the winning slot by the Auctioneer. + Bundle payload = 3; +} + message GetBundleStreamResponse { Bundle bundle = 1; } From 0229358132e278b4db6e6447a0c22c3e8334e35e Mon Sep 17 00:00:00 2001 From: itamar Date: Mon, 23 Sep 2024 16:36:43 -0400 Subject: [PATCH 3/9] auctioneer binary scaffolding --- Cargo.lock | 32 ++++ Cargo.toml | 1 + crates/astria-auctioneer/Cargo.toml | 60 ++++++++ crates/astria-auctioneer/README.md | 35 +++++ crates/astria-auctioneer/build.rs | 4 + crates/astria-auctioneer/justfile | 12 ++ crates/astria-auctioneer/local.env.example | 91 ++++++++++++ .../src/auction_driver/builder.rs | 20 +++ .../src/auction_driver/mod.rs | 17 +++ .../astria-auctioneer/src/auctioneer/inner.rs | 140 ++++++++++++++++++ .../astria-auctioneer/src/auctioneer/mod.rs | 80 ++++++++++ crates/astria-auctioneer/src/build_info.rs | 3 + crates/astria-auctioneer/src/config.rs | 40 +++++ crates/astria-auctioneer/src/lib.rs | 13 ++ crates/astria-auctioneer/src/main.rs | 87 +++++++++++ crates/astria-auctioneer/src/metrics.rs | 25 ++++ 16 files changed, 660 insertions(+) create mode 100644 crates/astria-auctioneer/Cargo.toml create mode 100644 crates/astria-auctioneer/README.md create mode 100644 crates/astria-auctioneer/build.rs create mode 100644 crates/astria-auctioneer/justfile create mode 100644 crates/astria-auctioneer/local.env.example create mode 100644 crates/astria-auctioneer/src/auction_driver/builder.rs create mode 100644 crates/astria-auctioneer/src/auction_driver/mod.rs create mode 100644 crates/astria-auctioneer/src/auctioneer/inner.rs create mode 100644 crates/astria-auctioneer/src/auctioneer/mod.rs create mode 100644 crates/astria-auctioneer/src/build_info.rs create mode 100644 crates/astria-auctioneer/src/config.rs create mode 100644 crates/astria-auctioneer/src/lib.rs create mode 100644 crates/astria-auctioneer/src/main.rs create mode 100644 crates/astria-auctioneer/src/metrics.rs diff --git a/Cargo.lock b/Cargo.lock index b95f060418..e3e64f8f68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -497,6 +497,38 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "astria-auctioneer" +version = "0.0.1" +dependencies = [ + "astria-build-info", + "astria-config", + "astria-core", + "astria-eyre", + "astria-telemetry", + "astria-test-utils", + "async-trait", + "axum", + "futures", + "insta", + "itertools 0.12.1", + "once_cell", + "pin-project-lite", + "prost", + "serde", + "serde_json", + "sha2 0.10.8", + "tempfile", + "tokio", + "tokio-stream", + "tokio-test", + "tokio-util 0.7.11", + "tonic 0.10.2", + "tracing", + "tryhard", + "wiremock", +] + [[package]] name = "astria-bridge-contracts" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index d1cc181333..56b2ffaa57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ exclude = ["tools/protobuf-compiler", "tools/solidity-compiler"] members = [ + "crates/astria-auctioneer", "crates/astria-bridge-contracts", "crates/astria-bridge-withdrawer", "crates/astria-build-info", diff --git a/crates/astria-auctioneer/Cargo.toml b/crates/astria-auctioneer/Cargo.toml new file mode 100644 index 0000000000..2deebe0751 --- /dev/null +++ b/crates/astria-auctioneer/Cargo.toml @@ -0,0 +1,60 @@ +[package] +name = "astria-auctioneer" +version = "0.0.1" +edition = "2021" +rust-version = "1.76" +license = "MIT OR Apache-2.0" +readme = "README.md" +repository = "https://github.com/astriaorg/astria" +homepage = "https://astria.org" + +[[bin]] +name = "astria-auctioneer" + +[dependencies] +astria-build-info = { path = "../astria-build-info", features = ["runtime"] } +astria-core = { path = "../astria-core", features = ["serde", "server"] } +astria-eyre = { path = "../astria-eyre" } +config = { package = "astria-config", path = "../astria-config" } +telemetry = { package = "astria-telemetry", path = "../astria-telemetry", features = [ + "display", +] } + +async-trait = { workspace = true } +axum = { workspace = true } +futures = { workspace = true } +itertools = { workspace = true } +once_cell = { workspace = true } +pin-project-lite = { workspace = true } +prost = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +sha2 = { workspace = true } +tokio = { workspace = true, features = [ + "macros", + "rt-multi-thread", + "sync", + "time", + "signal", +] } +tokio-util = { workspace = true, features = ["rt"] } +tracing = { workspace = true, features = ["attributes"] } +tryhard = { workspace = true } +tonic = { workspace = true } +tokio-stream = { workspace = true, features = ["net"] } + +[dev-dependencies] +astria-core = { path = "../astria-core", features = ["client"] } +config = { package = "astria-config", path = "../astria-config", features = [ + "tests", +] } +insta = { workspace = true, features = ["json"] } +tempfile = { workspace = true } +test_utils = { package = "astria-test-utils", path = "../astria-test-utils", features = [ + "geth", +] } +tokio-test = { workspace = true } +wiremock = { workspace = true } + +[build-dependencies] +astria-build-info = { path = "../astria-build-info", features = ["build"] } diff --git a/crates/astria-auctioneer/README.md b/crates/astria-auctioneer/README.md new file mode 100644 index 0000000000..33936060e0 --- /dev/null +++ b/crates/astria-auctioneer/README.md @@ -0,0 +1,35 @@ +# Astria Auctioneer + +TODO: Add a description of the binary. + +## Running The Auctioneer + +### Dependencies + +We use [just](https://just.systems/man/en/chapter_4.html) for convenient project +specific commands. + +### Configuration + +The Auctioneer is configured via environment variables. An example configuration +can be seen in `local.env.example`. + +To copy a configuration to your `.env` file run: + +```sh + +# Can specify an environment +just copy-env + +# By default will copy `local.env.example` +just copy-env +``` + +### Running locally + +After creating a `.env` file either manually or by copying as above, `just` will +load it and run locally: + +```bash +just run +``` diff --git a/crates/astria-auctioneer/build.rs b/crates/astria-auctioneer/build.rs new file mode 100644 index 0000000000..7347735f49 --- /dev/null +++ b/crates/astria-auctioneer/build.rs @@ -0,0 +1,4 @@ +pub fn main() -> Result<(), Box> { + astria_build_info::emit("auctioneer-v")?; + Ok(()) +} diff --git a/crates/astria-auctioneer/justfile b/crates/astria-auctioneer/justfile new file mode 100644 index 0000000000..e5c9ef9653 --- /dev/null +++ b/crates/astria-auctioneer/justfile @@ -0,0 +1,12 @@ +default: + @just --list + +set dotenv-load +set fallback + +default_env := 'local' +copy-env type=default_env: + cp {{ type }}.env.example .env + +run: + cargo run diff --git a/crates/astria-auctioneer/local.env.example b/crates/astria-auctioneer/local.env.example new file mode 100644 index 0000000000..0dd1638542 --- /dev/null +++ b/crates/astria-auctioneer/local.env.example @@ -0,0 +1,91 @@ +# Configuration options of Astria AUCTIONEER. + +# Log level. One of debug, info, warn, or error +ASTRIA_AUCTIONEER_LOG="astria_AUCTIONEER=info" + +# If true disables writing to the opentelemetry OTLP endpoint. +ASTRIA_AUCTIONEER_NO_OTEL=false + +# If true disables tty detection and forces writing telemetry to stdout. +# If false span data is written to stdout only if it is connected to a tty. +ASTRIA_AUCTIONEER_FORCE_STDOUT=false + +# If true uses an exceedingly pretty human readable format to write to stdout. +# If false uses JSON formatted OTEL traces. +# This does nothing unless stdout is connected to a tty or +# `ASTRIA_AUCTIONEER_FORCE_STDOUT` is set to `true`. +ASTRIA_AUCTIONEER_PRETTY_PRINT=false + +# If set to any non-empty value removes ANSI escape characters from the pretty +# printed output. Note that this does nothing unless `ASTRIA_AUCTIONEER_PRETTY_PRINT` +# is set to `true`. +NO_COLOR= + +# Address of the API server +ASTRIA_AUCTIONEER_API_ADDR="0.0.0.0:0" + +# Address of the RPC server for the sequencer chain +ASTRIA_AUCTIONEER_SEQUENCER_URL="http://127.0.0.1:26657" + +# Chain ID of the sequencer chain which transactions are submitted to. +ASTRIA_AUCTIONEER_SEQUENCER_CHAIN_ID="astria-dev-1" + +# A list of execution `::,::`. +# Rollup names are not case sensitive. If a name is repeated, the last list item is used. +# names are sha256 hashed and used as the `rollup_id` in `SequenceAction`s +ASTRIA_AUCTIONEER_ROLLUPS="astriachain::ws://127.0.0.1:8545" + +# The path to the file storing the private key for the sequencer account used for signing +# transactions. The file should contain a hex-encoded Ed25519 secret key. +ASTRIA_AUCTIONEER_PRIVATE_KEY_FILE=/path/to/priv_sequencer_key.json + +# The prefix that will be used to construct bech32m sequencer addresses. +ASTRIA_AUCTIONEER_SEQUENCER_ADDRESS_PREFIX=astria + +# Block time in milliseconds, used to force submitting of finished bundles. +# Should match the sequencer node configuration for 'timeout_commit', as +# specified in https://docs.tendermint.com/v0.34/tendermint-core/configuration.html +ASTRIA_AUCTIONEER_MAX_SUBMIT_INTERVAL_MS=2000 + +# Max bytes to encode into a single sequencer `SignedTransaction`, not including signature, +# public key, nonce. This is the sum of the sizes of all the `SequenceAction`s. Should be +# set below the sequencer's max block size to allow space for encoding, signature, public +# key and nonce bytes +ASTRIA_AUCTIONEER_MAX_BYTES_PER_BUNDLE=200000 + +# Max amount of finished bundles that can be in the submission queue. +# ASTRIA_AUCTIONEER_BUNDLE_QUEUE_CAPACITY * ASTRIA_AUCTIONEER_MAX_BYTES_PER_BUNDLE (e.g. +# 40000 * 200KB=8GB) is the limit on how much memory the finished bundle queue can consume. +# This should be lower than the resource limit enforced by Kubernetes on the pod, defined here: +# https://github.com/astriaorg/astria/blob/622d4cb8695e4fbcd86456bd16149420b8acda79/charts/evm-rollup/values.yaml#L276 +ASTRIA_AUCTIONEER_BUNDLE_QUEUE_CAPACITY=40000 + +# Set to true to enable prometheus metrics. +ASTRIA_AUCTIONEER_NO_METRICS=true + +# The address at which the prometheus HTTP listener will bind if enabled. +ASTRIA_AUCTIONEER_METRICS_HTTP_LISTENER_ADDR="127.0.0.1:9000" + +# The address at which the gRPC collector and health services are listening. +ASTRIA_AUCTIONEER_GRPC_ADDR="0.0.0.0:0" + +# The asset to use for paying for transactions submitted to sequencer. +ASTRIA_AUCTIONEER_FEE_ASSET="nria" + +# The OTEL specific config options follow the OpenTelemetry Protocol Exporter v1 +# specification as defined here: +# https://github.com/open-telemetry/opentelemetry-specification/blob/e94af89e3d0c01de30127a0f423e912f6cda7bed/specification/protocol/exporter.md + +# Sets the general OTLP endpoint. +OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4317" +# Sets the OTLP endpoint for trace data. This takes precedence over `OTEL_EXPORTER_OTLP_ENDPOINT` if set. +OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="http://localhost:4317/v1/traces" +# The duration in seconds that the OTEL exporter will wait for each batch export. +OTEL_EXPORTER_OTLP_TRACES_TIMEOUT=10 +# The compression format to use for exporting. Only `"gzip"` is supported. +# Don't set the env var if no compression is required. +OTEL_EXPORTER_OTLP_TRACES_COMPRESSION="gzip" +# The HTTP headers that will be set when sending gRPC requests. +OTEL_EXPORTER_OTLP_HEADERS="key1=value1,key2=value2" +# The HTTP headers that will be set when sending gRPC requests. This takes precedence over `OTEL_EXPORTER_OTLP_HEADERS` if set. +OTEL_EXPORTER_OTLP_TRACE_HEADERS="key1=value1,key2=value2" diff --git a/crates/astria-auctioneer/src/auction_driver/builder.rs b/crates/astria-auctioneer/src/auction_driver/builder.rs new file mode 100644 index 0000000000..89baebd619 --- /dev/null +++ b/crates/astria-auctioneer/src/auction_driver/builder.rs @@ -0,0 +1,20 @@ +use astria_eyre::eyre; + +use super::AuctionDriver; +use crate::Metrics; + +pub(crate) struct Builder { + pub(crate) metrics: &'static Metrics, +} + +impl Builder { + pub(crate) fn build(self) -> eyre::Result { + let Self { + metrics, + } = self; + + Ok(AuctionDriver { + metrics, + }) + } +} diff --git a/crates/astria-auctioneer/src/auction_driver/mod.rs b/crates/astria-auctioneer/src/auction_driver/mod.rs new file mode 100644 index 0000000000..d312cfb95f --- /dev/null +++ b/crates/astria-auctioneer/src/auction_driver/mod.rs @@ -0,0 +1,17 @@ +use astria_eyre::eyre; + +use crate::Metrics; + +mod builder; +pub(crate) use builder::Builder; + +pub(crate) struct AuctionDriver { + #[allow(dead_code)] + metrics: &'static Metrics, +} + +impl AuctionDriver { + pub(crate) async fn run(self) -> eyre::Result<()> { + todo!("implement me") + } +} diff --git a/crates/astria-auctioneer/src/auctioneer/inner.rs b/crates/astria-auctioneer/src/auctioneer/inner.rs new file mode 100644 index 0000000000..a6f302c2dc --- /dev/null +++ b/crates/astria-auctioneer/src/auctioneer/inner.rs @@ -0,0 +1,140 @@ +use std::time::Duration; + +use astria_eyre::eyre::{ + self, + WrapErr as _, +}; +use itertools::Itertools as _; +use tokio::{ + select, + task::JoinError, + time::timeout, +}; +use tokio_util::{ + sync::CancellationToken, + task::JoinMap, +}; +use tracing::{ + error, + info, + warn, +}; + +use crate::{ + auction_driver, + Config, + Metrics, +}; + +pub(super) struct Auctioneer { + /// Used to signal the service to shutdown + shutdown_token: CancellationToken, + + /// The different long-running tasks that make up the Auctioneer + tasks: JoinMap<&'static str, eyre::Result<()>>, +} + +impl Auctioneer { + const AUCTION_DRIVER: &'static str = "auction_driver"; + const _BUNDLE_COLLECTOR: &'static str = "bundle_collector"; + const _OPTIMISTIC_EXECUTOR: &'static str = "optimistic_executor"; + + /// Creates an [`Auctioneer`] service from a [`Config`] and [`Metrics`]. + pub(super) fn new( + cfg: Config, + metrics: &'static Metrics, + shutdown_token: CancellationToken, + ) -> eyre::Result { + let Config { + .. + } = cfg; + + let mut tasks = JoinMap::new(); + + // TODO: add tasks here + // - optimistic executor + // - bundle collector + // - auction driver + // - runs the auction + // - runs the sequencer submitter + let auction_driver = auction_driver::Builder { + metrics, + } + .build() + .wrap_err("failed to initialize the auction driver")?; + tasks.spawn(Self::AUCTION_DRIVER, auction_driver.run()); + + Ok(Self { + shutdown_token, + tasks, + }) + } + + /// Runs the [`Auctioneer`] service until it received an exit signal, or one of the constituent + /// tasks either ends unexpectedly or returns an error. + pub(super) async fn run(mut self) -> eyre::Result<()> { + let reason = select! { + biased; + + () = self.shutdown_token.cancelled() => { + Ok("auctioneer received shutdown signal") + }, + + Some((name, res)) = self.tasks.join_next() => { + flatten(res) + .wrap_err_with(|| format!("task `{name}` failed")) + .map(|_| "task `{name}` exited unexpectedly") + } + }; + + match reason { + Ok(msg) => info!(%msg, "received shutdown signal"), + Err(err) => error!(%err, "shutting down due to error"), + } + + self.shutdown().await; + Ok(()) + } + + /// Initiates shutdown of the Auctioneer and waits for all the constituent tasks to shut down. + async fn shutdown(mut self) { + self.shutdown_token.cancel(); + + let shutdown_loop = async { + while let Some((name, res)) = self.tasks.join_next().await { + let message = "task shut down"; + match flatten(res) { + Ok(()) => { + info!(name, message) + } + Err(err) => { + error!(name, %err, message) + } + } + } + }; + + info!("signalling all tasks to shut down; waiting 25 seconds for exit"); + if timeout(Duration::from_secs(25), shutdown_loop) + .await + .is_err() + { + let tasks = self.tasks.keys().join(", "); + warn!( + tasks = format_args!("[{tasks}]"), + "aborting all tasks that have not yet shut down" + ) + } else { + info!("all tasks have shut down regularly"); + } + info!("shutting down"); + } +} + +pub(super) fn flatten(res: Result, JoinError>) -> eyre::Result { + match res { + Ok(Ok(val)) => Ok(val), + Ok(Err(err)) => Err(err).wrap_err("task returned with error"), + Err(err) => Err(err).wrap_err("task panicked"), + } +} diff --git a/crates/astria-auctioneer/src/auctioneer/mod.rs b/crates/astria-auctioneer/src/auctioneer/mod.rs new file mode 100644 index 0000000000..aed31d40bd --- /dev/null +++ b/crates/astria-auctioneer/src/auctioneer/mod.rs @@ -0,0 +1,80 @@ +use std::future::Future; + +use astria_eyre::eyre::{ + self, +}; +use pin_project_lite::pin_project; +use tokio::task::{ + JoinError, + JoinHandle, +}; +use tokio_util::sync::CancellationToken; +use tracing::instrument; + +use crate::{ + Config, + Metrics, +}; + +mod inner; + +pin_project! { + /// Handle to the [`Auctioneer`] service, returned by [`Auctioneer::spawn`]. + pub struct Auctioneer { + shutdown_token: CancellationToken, + task: Option>>, + } +} + +impl Auctioneer { + /// Creates an [`Auctioneer`] service and runs it, returning a handle to the taks and shutdown + /// token. + /// + /// # Errors + /// Returns an error if the Auctioneer cannot be initialized. + #[must_use] + pub fn spawn(cfg: Config, metrics: &'static Metrics) -> eyre::Result { + let shutdown_token = CancellationToken::new(); + let inner = inner::Auctioneer::new(cfg, metrics, shutdown_token.child_token())?; + let task = tokio::spawn(inner.run()); + + Ok(Self { + shutdown_token, + task: Some(task), + }) + } + + /// Initiates shutdown of the Auctioneer and returns its result. + /// + /// # Errors + /// Returns an error if the Auctioneer exited with an error. + /// + /// # Panics + /// Panics if shutdown is called twice. + #[instrument(skip_all, err)] + pub async fn shutdown(&mut self) -> Result, JoinError> { + self.shutdown_token.cancel(); + self.task + .take() + .expect("shutdown must not be called twice") + .await + } +} + +impl Future for Auctioneer { + type Output = Result, tokio::task::JoinError>; + + fn poll( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll { + use futures::future::FutureExt as _; + + let this = self.project(); + let task = this + .task + .as_mut() + .expect("the Auctioneer handle must not be polled after shutdown"); + task.poll_unpin(cx) + } +} diff --git a/crates/astria-auctioneer/src/build_info.rs b/crates/astria-auctioneer/src/build_info.rs new file mode 100644 index 0000000000..2996fcab96 --- /dev/null +++ b/crates/astria-auctioneer/src/build_info.rs @@ -0,0 +1,3 @@ +use astria_build_info::BuildInfo; + +pub const BUILD_INFO: BuildInfo = astria_build_info::get!(); diff --git a/crates/astria-auctioneer/src/config.rs b/crates/astria-auctioneer/src/config.rs new file mode 100644 index 0000000000..a3c0305622 --- /dev/null +++ b/crates/astria-auctioneer/src/config.rs @@ -0,0 +1,40 @@ +use serde::{ + Deserialize, + Serialize, +}; + +// Allowed `struct_excessive_bools` because this is used as a container +// for deserialization. Making this a builder-pattern is not actionable. +#[allow(clippy::struct_excessive_bools)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +/// The single config for creating an astria-auctioneer service. +pub struct Config { + /// Log level for the service. + pub log: String, + /// Forces writing trace data to stdout no matter if connected to a tty or not. + pub force_stdout: bool, + /// Disables writing trace data to an opentelemetry endpoint. + pub no_otel: bool, + /// Set to true to disable the metrics server + pub no_metrics: bool, + /// The endpoint which will be listened on for serving prometheus metrics + pub metrics_http_listener_addr: String, + /// Writes a human readable format to stdout instead of JSON formatted OTEL trace data. + pub pretty_print: bool, +} + +impl config::Config for Config { + const PREFIX: &'static str = "ASTRIA_AUCTIONEER_"; +} + +#[cfg(test)] +mod tests { + use super::Config; + + const EXAMPLE_ENV: &str = include_str!("../local.env.example"); + + #[test] + fn example_env_config_is_up_to_date() { + config::tests::example_env_config_is_up_to_date::(EXAMPLE_ENV); + } +} diff --git a/crates/astria-auctioneer/src/lib.rs b/crates/astria-auctioneer/src/lib.rs new file mode 100644 index 0000000000..cbc2c3ca61 --- /dev/null +++ b/crates/astria-auctioneer/src/lib.rs @@ -0,0 +1,13 @@ +//! TODO: Add a description + +mod auction_driver; +mod auctioneer; +mod build_info; +pub mod config; +pub(crate) mod metrics; + +pub use auctioneer::Auctioneer; +pub use build_info::BUILD_INFO; +pub use config::Config; +pub use metrics::Metrics; +pub use telemetry; diff --git a/crates/astria-auctioneer/src/main.rs b/crates/astria-auctioneer/src/main.rs new file mode 100644 index 0000000000..c6d9ea0db0 --- /dev/null +++ b/crates/astria-auctioneer/src/main.rs @@ -0,0 +1,87 @@ +use std::process::ExitCode; + +use astria_auctioneer::{ + Auctioneer, + Config, + BUILD_INFO, +}; +use astria_eyre::eyre::WrapErr as _; +use tokio::{ + select, + signal::unix::{ + signal, + SignalKind, + }, +}; +use tracing::{ + error, + info, + warn, +}; + +#[tokio::main] +async fn main() -> ExitCode { + astria_eyre::install().expect("astria eyre hook must be the first hook installed"); + + eprintln!("{}", telemetry::display::json(&BUILD_INFO)); + + let cfg: Config = config::get().expect("failed to read configuration"); + eprintln!("{}", telemetry::display::json(&cfg),); + + let mut telemetry_conf = telemetry::configure() + .set_no_otel(cfg.no_otel) + .set_force_stdout(cfg.force_stdout) + .set_pretty_print(cfg.pretty_print) + .set_filter_directives(&cfg.log); + + if !cfg.no_metrics { + telemetry_conf = + telemetry_conf.set_metrics(&cfg.metrics_http_listener_addr, env!("CARGO_PKG_NAME")); + } + + let (metrics, _telemetry_guard) = match telemetry_conf + .try_init(&()) + .wrap_err("failed to setup telemetry") + { + Err(e) => { + eprintln!("initializing auctioneer failed:\n{e:?}"); + return ExitCode::FAILURE; + } + Ok(metrics_and_guard) => metrics_and_guard, + }; + + info!( + config = serde_json::to_string(&cfg).expect("serializing to a string cannot fail"), + "initializing auctioneer" + ); + + let mut auctioneer = match Auctioneer::spawn(cfg, metrics) { + Ok(auctioneer) => auctioneer, + Err(error) => { + error!(%error, "failed initializing auctioneer"); + return ExitCode::FAILURE; + } + }; + + let mut sigterm = signal(SignalKind::terminate()) + .expect("setting a SIGTERM listener should always work on Unix"); + + select! { + _ = sigterm.recv() => { + info!("received SIGTERM; shutting down"); + if let Err(error) = auctioneer.shutdown().await { + warn!(%error, "encountered an error while shutting down"); + } + info!("auctioneer stopped"); + ExitCode::SUCCESS + } + + res = &mut auctioneer => { + error!( + error = res.err().map(tracing::field::display), + "auctioneer task exited unexpectedly" + ); + ExitCode::FAILURE + } + } +} diff --git a/crates/astria-auctioneer/src/metrics.rs b/crates/astria-auctioneer/src/metrics.rs new file mode 100644 index 0000000000..3ecafc3fad --- /dev/null +++ b/crates/astria-auctioneer/src/metrics.rs @@ -0,0 +1,25 @@ +use telemetry::{ + metric_names, + metrics::{ + self, + RegisteringBuilder, + }, +}; + +pub struct Metrics {} + +impl Metrics {} + +impl metrics::Metrics for Metrics { + type Config = (); + + fn register( + _builder: &mut RegisteringBuilder, + _config: &Self::Config, + ) -> Result { + Ok(Self {}) + } +} + +metric_names!(const METRICS_NAMES: +); From 6f08805d4f82c109ef3d5c52736013ad6d033c95 Mon Sep 17 00:00:00 2001 From: itamar Date: Wed, 25 Sep 2024 16:30:13 -0400 Subject: [PATCH 4/9] add optimistic block, block commitment, and optimistic execution streams --- Cargo.lock | 571 +++++++++--------- Cargo.toml | 1 + crates/astria-auctioneer/Cargo.toml | 24 +- crates/astria-auctioneer/local.env.example | 82 +-- crates/astria-auctioneer/src/auction/bid.rs | 57 ++ .../astria-auctioneer/src/auction/builder.rs | 75 +++ crates/astria-auctioneer/src/auction/mod.rs | 237 ++++++++ .../src/auction_driver/builder.rs | 20 - .../src/auction_driver/mod.rs | 17 - .../astria-auctioneer/src/auctioneer/inner.rs | 38 +- .../src/block/commitment_stream.rs | 56 ++ .../src/block/executed_stream.rs | 77 +++ crates/astria-auctioneer/src/block/mod.rs | 217 +++++++ .../src/block/optimistic_stream.rs | 68 +++ crates/astria-auctioneer/src/config.rs | 23 + crates/astria-auctioneer/src/lib.rs | 19 +- .../src/optimistic_execution_client.rs | 145 +++++ .../src/optimistic_executor/builder.rs | 53 ++ .../src/optimistic_executor/mod.rs | 200 ++++++ .../src/sequencer_grpc_client.rs | 132 ++++ 20 files changed, 1710 insertions(+), 402 deletions(-) create mode 100644 crates/astria-auctioneer/src/auction/bid.rs create mode 100644 crates/astria-auctioneer/src/auction/builder.rs create mode 100644 crates/astria-auctioneer/src/auction/mod.rs delete mode 100644 crates/astria-auctioneer/src/auction_driver/builder.rs delete mode 100644 crates/astria-auctioneer/src/auction_driver/mod.rs create mode 100644 crates/astria-auctioneer/src/block/commitment_stream.rs create mode 100644 crates/astria-auctioneer/src/block/executed_stream.rs create mode 100644 crates/astria-auctioneer/src/block/mod.rs create mode 100644 crates/astria-auctioneer/src/block/optimistic_stream.rs create mode 100644 crates/astria-auctioneer/src/optimistic_execution_client.rs create mode 100644 crates/astria-auctioneer/src/optimistic_executor/builder.rs create mode 100644 crates/astria-auctioneer/src/optimistic_executor/mod.rs create mode 100644 crates/astria-auctioneer/src/sequencer_grpc_client.rs diff --git a/Cargo.lock b/Cargo.lock index e3e64f8f68..8cdab5e129 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -180,9 +180,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "ark-bls12-377" @@ -279,7 +279,7 @@ dependencies = [ "num-traits", "paste", "rayon", - "rustc_version 0.4.0", + "rustc_version 0.4.1", "zeroize", ] @@ -455,9 +455,9 @@ dependencies = [ [[package]] name = "arrayref" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" @@ -505,24 +505,28 @@ dependencies = [ "astria-config", "astria-core", "astria-eyre", + "astria-sequencer-client", "astria-telemetry", "astria-test-utils", "async-trait", "axum", + "bytes", "futures", + "humantime", "insta", "itertools 0.12.1", - "once_cell", + "pbjson-types", "pin-project-lite", "prost", "serde", "serde_json", "sha2 0.10.8", "tempfile", + "thiserror", "tokio", "tokio-stream", "tokio-test", - "tokio-util 0.7.11", + "tokio-util 0.7.12", "tonic 0.10.2", "tracing", "tryhard", @@ -576,7 +580,7 @@ dependencies = [ "tendermint-rpc", "tokio", "tokio-stream", - "tokio-util 0.7.11", + "tokio-util 0.7.12", "tonic 0.10.2", "tracing", "tryhard", @@ -659,7 +663,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-test", - "tokio-util 0.7.11", + "tokio-util 0.7.12", "tonic 0.10.2", "tonic-health", "tracing", @@ -691,7 +695,7 @@ dependencies = [ "hex", "http 0.2.12", "humantime", - "indexmap 2.4.0", + "indexmap 2.6.0", "insta", "itertools 0.12.1", "itoa", @@ -710,7 +714,7 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", - "tokio-util 0.7.11", + "tokio-util 0.7.12", "tonic 0.10.2", "tower", "tracing", @@ -744,7 +748,7 @@ dependencies = [ "ed25519-consensus", "hex", "ibc-types", - "indexmap 2.4.0", + "indexmap 2.6.0", "insta", "pbjson", "pbjson-types", @@ -945,7 +949,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-test", - "tokio-util 0.7.11", + "tokio-util 0.7.12", "tonic 0.10.2", "tower", "tracing", @@ -1046,9 +1050,9 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ "async-stream-impl", "futures-core", @@ -1057,24 +1061,24 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] name = "async-trait" -version = "0.1.81" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -1101,7 +1105,7 @@ checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" dependencies = [ "futures", "pharos", - "rustc_version 0.4.0", + "rustc_version 0.4.1", ] [[package]] @@ -1136,14 +1140,14 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" @@ -1309,7 +1313,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -1391,7 +1395,7 @@ checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" dependencies = [ "arrayref", "arrayvec 0.7.6", - "constant_time_eq 0.3.0", + "constant_time_eq 0.3.1", ] [[package]] @@ -1404,7 +1408,7 @@ dependencies = [ "arrayvec 0.7.6", "cc", "cfg-if", - "constant_time_eq 0.3.0", + "constant_time_eq 0.3.1", ] [[package]] @@ -1456,10 +1460,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" dependencies = [ "once_cell", - "proc-macro-crate 3.1.0", + "proc-macro-crate 3.2.0", "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", "syn_derive", ] @@ -1501,7 +1505,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", - "regex-automata 0.4.7", + "regex-automata 0.4.8", "serde", ] @@ -1519,9 +1523,9 @@ checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" [[package]] name = "bytemuck" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fd4c6dcc3b0aea2f5c0b4b82c2b15fe39ddbc76041a310848f4706edf76bb31" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" [[package]] name = "byteorder" @@ -1531,9 +1535,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" dependencies = [ "serde", ] @@ -1589,9 +1593,9 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" [[package]] name = "cc" -version = "1.1.13" +version = "1.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" +checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" dependencies = [ "jobserver", "libc", @@ -1806,9 +1810,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.16" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" dependencies = [ "clap_builder", "clap_derive", @@ -1825,9 +1829,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.15" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" dependencies = [ "anstream", "anstyle", @@ -1838,14 +1842,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.13" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck 0.5.0", "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -2073,9 +2077,9 @@ checksum = "808ac43170e95b11dd23d78aa9eaac5bea45776a602955552c4e833f3f0f823d" [[package]] name = "const-hex" -version = "1.12.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8a24a26d37e1ffd45343323dc9fe6654ceea44c12f2fcb3d7ac29e610bc6" +checksum = "0121754e84117e65f9d90648ee6aa4882a6e63110307ab73967a4c5e7e69e586" dependencies = [ "cfg-if", "cpufeatures", @@ -2092,22 +2096,22 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const_format" -version = "0.2.32" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" +checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b" dependencies = [ "const_format_proc_macros", ] [[package]] name = "const_format_proc_macros" -version = "0.2.32" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" +checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1" dependencies = [ "proc-macro2 1.0.86", "quote", - "unicode-xid 0.2.5", + "unicode-xid 0.2.6", ] [[package]] @@ -2118,9 +2122,9 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "constant_time_eq" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "core-foundation" @@ -2149,9 +2153,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -2255,9 +2259,9 @@ dependencies = [ [[package]] name = "curl" -version = "0.4.46" +version = "0.4.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e2161dd6eba090ff1594084e95fd67aeccf04382ffea77999ea94ed42ec67b6" +checksum = "d9fb4d13a1be2b58f14d60adba57c9834b78c62fd86c3e76a148f732686e9265" dependencies = [ "curl-sys", "libc", @@ -2270,9 +2274,9 @@ dependencies = [ [[package]] name = "curl-sys" -version = "0.4.74+curl-8.9.0" +version = "0.4.77+curl-8.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8af10b986114528fcdc4b63b6f5f021b7057618411046a4de2ba0f0149a097bf" +checksum = "f469e8a5991f277a208224f6c7ad72ecb5f986e36d09ae1f2c1bb9259478a480" dependencies = [ "cc", "libc", @@ -2295,7 +2299,7 @@ dependencies = [ "curve25519-dalek-derive", "fiat-crypto", "rand_core 0.6.4", - "rustc_version 0.4.0", + "rustc_version 0.4.1", "subtle", "zeroize", ] @@ -2308,7 +2312,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -2345,7 +2349,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote", "strsim", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -2356,7 +2360,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -2534,7 +2538,7 @@ checksum = "0a6433aac097572ea8ccc60b3f2e756c661c9aeed9225cdd4d0cb119cb7ff6ba" dependencies = [ "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -2545,7 +2549,7 @@ checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -2631,7 +2635,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -2656,7 +2660,7 @@ checksum = "27540baf49be0d484d8f0130d7d8da3011c32a44d4fc873368154f1510e574a2" dependencies = [ "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -2880,7 +2884,7 @@ dependencies = [ "once_cell", "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -3073,7 +3077,7 @@ dependencies = [ "regex", "serde", "serde_json", - "syn 2.0.75", + "syn 2.0.79", "toml 0.8.19", "walkdir", ] @@ -3091,7 +3095,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote", "serde_json", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -3117,11 +3121,11 @@ dependencies = [ "serde", "serde_json", "strum", - "syn 2.0.75", + "syn 2.0.79", "tempfile", "thiserror", "tiny-keccak", - "unicode-xid 0.2.5", + "unicode-xid 0.2.6", ] [[package]] @@ -3269,9 +3273,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fastrlp" @@ -3317,9 +3321,9 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.24" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf401df4a4e3872c4fe8151134cf483738e74b67fc934d6532c882b3d24a4550" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ "cfg-if", "libc", @@ -3347,9 +3351,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.32" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c0596c1eac1f9e04ed902702e9878208b336edc9d6fddc8a48387349bab3666" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", "miniz_oxide 0.8.0", @@ -3536,7 +3540,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -3661,15 +3665,15 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -3707,18 +3711,18 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.4.0", + "indexmap 2.6.0", "slab", "tokio", - "tokio-util 0.7.11", + "tokio-util 0.7.12", "tracing", ] [[package]] name = "h2" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ "atomic-waker", "bytes", @@ -3726,10 +3730,10 @@ dependencies = [ "futures-core", "futures-sink", "http 1.1.0", - "indexmap 2.4.0", + "indexmap 2.6.0", "slab", "tokio", - "tokio-util 0.7.11", + "tokio-util 0.7.12", "tracing", ] @@ -3773,6 +3777,12 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "hashers" version = "1.0.1" @@ -3800,7 +3810,7 @@ checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" dependencies = [ "atomic-polyfill", "hash32", - "rustc_version 0.4.0", + "rustc_version 0.4.1", "serde", "spin 0.9.8", "stable_deref_trait", @@ -3907,9 +3917,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http 1.1.0", @@ -3924,7 +3934,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -3957,9 +3967,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -4009,16 +4019,16 @@ dependencies = [ [[package]] name = "hyper" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.5", + "h2 0.4.6", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "httparse", "httpdate", "itoa", @@ -4058,29 +4068,28 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.5" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ "bytes", "futures-channel", "futures-util", "http 1.1.0", - "http-body 1.0.0", - "hyper 1.3.1", + "http-body 1.0.1", + "hyper 1.4.1", "pin-project-lite", "socket2", "tokio", - "tower", "tower-service", "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -4497,12 +4506,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.4.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.0", "serde", ] @@ -4539,9 +4548,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.39.0" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "810ae6042d48e2c9e9215043563a58a80b877bc863228a74cf10c49d4620a6f5" +checksum = "6593a41c7a73841868772495db7dc1e8ecab43bb5c0b6da2059246c4b506ab60" dependencies = [ "console", "lazy_static", @@ -4561,9 +4570,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" [[package]] name = "is-terminal" @@ -4695,9 +4704,9 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.20.3" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "affdc52f7596ccb2d7645231fc6163bb314630c989b64998f3699a28b4d5d4dc" +checksum = "138572befc78a9793240645926f30161f8b4143d2be18d09e44ed9814bd7ee2c" dependencies = [ "jsonrpsee-core", "jsonrpsee-http-client", @@ -4711,9 +4720,9 @@ dependencies = [ [[package]] name = "jsonrpsee-client-transport" -version = "0.20.3" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b005c793122d03217da09af68ba9383363caa950b90d3436106df8cabce935" +checksum = "5c671353e4adf926799107bd7f5724a06b6bc0a333db442a0843c58640bdd0c1" dependencies = [ "futures-util", "http 0.2.12", @@ -4724,16 +4733,16 @@ dependencies = [ "thiserror", "tokio", "tokio-rustls", - "tokio-util 0.7.11", + "tokio-util 0.7.12", "tracing", "url", ] [[package]] name = "jsonrpsee-core" -version = "0.20.3" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2327ba8df2fdbd5e897e2b5ed25ce7f299d345b9736b6828814c3dbd1fd47b" +checksum = "f24ea59b037b6b9b0e2ebe2c30a3e782b56bd7c76dcc5d6d70ba55d442af56e3" dependencies = [ "anyhow", "async-lock 2.8.0", @@ -4756,9 +4765,9 @@ dependencies = [ [[package]] name = "jsonrpsee-http-client" -version = "0.20.3" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f80c17f62c7653ce767e3d7288b793dfec920f97067ceb189ebdd3570f2bc20" +checksum = "57c7b9f95208927653e7965a98525e7fc641781cab89f0e27c43fa2974405683" dependencies = [ "async-trait", "hyper 0.14.30", @@ -4776,9 +4785,9 @@ dependencies = [ [[package]] name = "jsonrpsee-proc-macros" -version = "0.20.3" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29110019693a4fa2dbda04876499d098fa16d70eba06b1e6e2b3f1b251419515" +checksum = "dcc0eba68ba205452bcb4c7b80a79ddcb3bf36c261a841b239433142db632d24" dependencies = [ "heck 0.4.1", "proc-macro-crate 1.3.1", @@ -4789,9 +4798,9 @@ dependencies = [ [[package]] name = "jsonrpsee-server" -version = "0.20.3" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c39a00449c9ef3f50b84fc00fc4acba20ef8f559f07902244abf4c15c5ab9c" +checksum = "a482bc4e25eebd0adb61a3468c722763c381225bd3ec46e926f709df8a8eb548" dependencies = [ "futures-util", "http 0.2.12", @@ -4805,16 +4814,16 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", - "tokio-util 0.7.11", + "tokio-util 0.7.12", "tower", "tracing", ] [[package]] name = "jsonrpsee-types" -version = "0.20.3" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be0be325642e850ed0bdff426674d2e66b2b7117c9be23a7caef68a2902b7d9" +checksum = "3264e339143fe37ed081953842ee67bfafa99e3b91559bdded6e4abd8fc8535e" dependencies = [ "anyhow", "beef", @@ -4826,9 +4835,9 @@ dependencies = [ [[package]] name = "jsonrpsee-ws-client" -version = "0.20.3" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca9cb3933ccae417eb6b08c3448eb1cb46e39834e5b503e395e5e5bd08546c0" +checksum = "6d06eeabbb55f0af8405288390a358ebcceb6e79e1390741e6f152309c4d6076" dependencies = [ "http 0.2.12", "jsonrpsee-client-transport", @@ -4853,9 +4862,9 @@ dependencies = [ [[package]] name = "k256" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", "ecdsa", @@ -4888,9 +4897,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libgit2-sys" @@ -4990,9 +4999,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.19" +version = "1.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc53a7799a7496ebc9fd29f31f7df80e83c9bda5299768af5f9e59eeea74647" +checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" dependencies = [ "cc", "libc", @@ -5036,9 +5045,9 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lz4-sys" -version = "1.10.0" +version = "1.11.1+lz4-1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109de74d5d2353660401699a4174a4ff23fcc649caf553df71933c7fb45ad868" +checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" dependencies = [ "cc", "libc", @@ -5093,15 +5102,15 @@ dependencies = [ [[package]] name = "metrics-exporter-prometheus" -version = "0.15.0" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26eb45aff37b45cff885538e1dcbd6c2b462c04fe84ce0155ea469f325672c98" +checksum = "b4f0c8427b39666bf970460908b213ec09b3b350f20c0c2eabcbba51704a08e6" dependencies = [ "base64 0.22.1", "http-body-util", - "hyper 1.3.1", + "hyper 1.4.1", "hyper-util", - "indexmap 2.4.0", + "indexmap 2.6.0", "ipnet", "metrics 0.23.0", "metrics-util", @@ -5199,7 +5208,7 @@ dependencies = [ "once_cell", "parking_lot", "quanta", - "rustc_version 0.4.0", + "rustc_version 0.4.1", "smallvec", "tagptr", "thiserror", @@ -5209,9 +5218,9 @@ dependencies = [ [[package]] name = "multiaddr" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b852bc02a2da5feed68cd14fa50d0774b92790a5bdbfa932a813926c8472070" +checksum = "fe6351f60b488e04c1d21bc69e56b89cb3f5e8f5d22557d6e8031bdfd79b6961" dependencies = [ "arrayref", "byteorder", @@ -5222,7 +5231,7 @@ dependencies = [ "percent-encoding", "serde", "static_assertions", - "unsigned-varint 0.7.2", + "unsigned-varint 0.8.0", "url", ] @@ -5369,10 +5378,10 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 3.1.0", + "proc-macro-crate 3.2.0", "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -5401,9 +5410,12 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" +dependencies = [ + "portable-atomic", +] [[package]] name = "opaque-debug" @@ -5551,9 +5563,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "ordered-float" -version = "4.2.2" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a91171844676f8c7990ce64959210cd2eaef32c2612c50f9fae9f8aaa6065a6" +checksum = "44d501f1a72f71d3c063a6bbc8f7271fa73aa09fe5d6283b6571e2ed176a2537" dependencies = [ "num-traits", ] @@ -5590,7 +5602,7 @@ version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" dependencies = [ - "proc-macro-crate 3.1.0", + "proc-macro-crate 3.2.0", "proc-macro2 1.0.86", "quote", "syn 1.0.109", @@ -5598,9 +5610,9 @@ dependencies = [ [[package]] name = "parking" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" @@ -5707,7 +5719,7 @@ dependencies = [ "proc-macro2 1.0.86", "proc-macro2-diagnostics", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -6017,7 +6029,7 @@ dependencies = [ "tendermint-proto", "tokio", "tokio-stream", - "tokio-util 0.7.11", + "tokio-util 0.7.12", "tonic 0.10.2", "tower", "tower-service", @@ -6046,9 +6058,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.11" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" dependencies = [ "memchr", "thiserror", @@ -6062,7 +6074,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.4.0", + "indexmap 2.6.0", ] [[package]] @@ -6072,7 +6084,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" dependencies = [ "futures", - "rustc_version 0.4.0", + "rustc_version 0.4.1", ] [[package]] @@ -6092,7 +6104,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -6119,9 +6131,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "polling" @@ -6152,9 +6164,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "poseidon-parameters" @@ -6258,12 +6270,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2 1.0.86", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -6292,11 +6304,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.21.1", + "toml_edit 0.22.22", ] [[package]] @@ -6348,7 +6360,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", "version_check", "yansi", ] @@ -6365,7 +6377,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", "unarray", ] @@ -6396,7 +6408,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.75", + "syn 2.0.79", "tempfile", ] @@ -6410,7 +6422,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -6448,9 +6460,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2 1.0.86", ] @@ -6552,9 +6564,9 @@ dependencies = [ [[package]] name = "raw-cpuid" -version = "11.1.0" +version = "11.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb9ee317cfe3fbd54b36a511efc1edd42e216903c9cd575e686dd68a2ba90d8d" +checksum = "1ab240315c661615f2ee9f0f2cd32d5a7343a84d5ebcccb99d46e6637565e7b0" dependencies = [ "bitflags 2.6.0", ] @@ -6581,9 +6593,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] @@ -6607,14 +6619,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -6628,13 +6640,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -6651,9 +6663,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" @@ -6840,7 +6852,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote", "rust-embed-utils", - "syn 2.0.75", + "syn 2.0.79", "walkdir", ] @@ -6894,9 +6906,9 @@ dependencies = [ [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver 1.0.23", ] @@ -6915,9 +6927,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags 2.6.0", "errno", @@ -7064,7 +7076,7 @@ version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" dependencies = [ - "proc-macro-crate 3.1.0", + "proc-macro-crate 3.2.0", "proc-macro2 1.0.86", "quote", "syn 1.0.109", @@ -7072,11 +7084,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -7136,9 +7148,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ "core-foundation-sys", "libc", @@ -7185,9 +7197,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.208" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] @@ -7203,20 +7215,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.208" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] name = "serde_json" -version = "1.0.125" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", @@ -7253,14 +7265,14 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] name = "serde_spanned" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -7279,15 +7291,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.9.0" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" +checksum = "9720086b3357bcb44fce40117d769a4d068c70ecfa190850a980a71755f66fcc" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.4.0", + "indexmap 2.6.0", "serde", "serde_derive", "serde_json", @@ -7297,14 +7309,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.9.0" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" +checksum = "5f1abbfe725f27678f4663bcacb75a83e829fd464c25d78dd038a3a29e307cec" dependencies = [ "darling", "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -7313,7 +7325,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.4.0", + "indexmap 2.6.0", "itoa", "ryu", "serde", @@ -7570,7 +7582,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote", "rustversion", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -7607,9 +7619,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.75" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2 1.0.86", "quote", @@ -7625,7 +7637,7 @@ dependencies = [ "proc-macro-error", "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -7669,12 +7681,12 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", - "fastrand 2.1.0", + "fastrand 2.1.1", "once_cell", "rustix", "windows-sys 0.59.0", @@ -7803,12 +7815,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" dependencies = [ "rustix", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -7844,22 +7856,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -7879,7 +7891,7 @@ checksum = "585e5ef40a784ce60b49c67d762110688d211d395d39e096be204535cf64590e" dependencies = [ "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -7951,9 +7963,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.3" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", @@ -7986,7 +7998,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -8001,9 +8013,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", @@ -8051,9 +8063,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -8095,7 +8107,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.20", + "toml_edit 0.22.22", ] [[package]] @@ -8113,7 +8125,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.4.0", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", @@ -8122,26 +8134,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.21.1" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.4.0", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.22.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" -dependencies = [ - "indexmap 2.4.0", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.18", + "winnow 0.6.20", ] [[package]] @@ -8213,7 +8214,7 @@ dependencies = [ "proc-macro2 1.0.86", "prost-build", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -8244,7 +8245,7 @@ dependencies = [ "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.11", + "tokio-util 0.7.12", "tower-layer", "tower-service", "tracing", @@ -8279,7 +8280,7 @@ dependencies = [ "pin-project", "thiserror", "tokio", - "tokio-util 0.7.11", + "tokio-util 0.7.12", "tower", "tracing", ] @@ -8334,7 +8335,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -8504,9 +8505,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "uint" @@ -8537,30 +8538,30 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-xid" @@ -8570,9 +8571,9 @@ checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" [[package]] name = "unicode-xid" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "universal-hash" @@ -8686,7 +8687,7 @@ dependencies = [ "cfg-if", "git2", "regex", - "rustc_version 0.4.0", + "rustc_version 0.4.1", "rustversion", "time", ] @@ -8705,7 +8706,7 @@ checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" dependencies = [ "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -8776,7 +8777,7 @@ dependencies = [ "once_cell", "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", "wasm-bindgen-shared", ] @@ -8810,7 +8811,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -9115,9 +9116,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -9165,7 +9166,7 @@ dependencies = [ "js-sys", "log", "pharos", - "rustc_version 0.4.0", + "rustc_version 0.4.1", "send_wrapper 0.6.0", "thiserror", "wasm-bindgen", @@ -9206,7 +9207,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -9226,7 +9227,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2 1.0.86", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 56b2ffaa57..a061a63595 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ members = [ # Specify default members so that cargo invocations in github actions will # not act on lints default-members = [ + "crates/astria-auctioneer", "crates/astria-bridge-contracts", "crates/astria-bridge-withdrawer", "crates/astria-build-info", diff --git a/crates/astria-auctioneer/Cargo.toml b/crates/astria-auctioneer/Cargo.toml index 2deebe0751..73ef9a1f43 100644 --- a/crates/astria-auctioneer/Cargo.toml +++ b/crates/astria-auctioneer/Cargo.toml @@ -13,29 +13,33 @@ name = "astria-auctioneer" [dependencies] astria-build-info = { path = "../astria-build-info", features = ["runtime"] } -astria-core = { path = "../astria-core", features = ["serde", "server"] } +astria-core = { path = "../astria-core", features = ["serde", "client"] } astria-eyre = { path = "../astria-eyre" } config = { package = "astria-config", path = "../astria-config" } +sequencer_client = { package = "astria-sequencer-client", path = "../astria-sequencer-client" } telemetry = { package = "astria-telemetry", path = "../astria-telemetry", features = [ - "display", + "display", ] } async-trait = { workspace = true } axum = { workspace = true } +bytes = { workspace = true } futures = { workspace = true } +humantime = { workspace = true } itertools = { workspace = true } -once_cell = { workspace = true } +pbjson-types = { workspace = true } pin-project-lite = { workspace = true } prost = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } sha2 = { workspace = true } +thiserror = { workspace = true } tokio = { workspace = true, features = [ - "macros", - "rt-multi-thread", - "sync", - "time", - "signal", + "macros", + "rt-multi-thread", + "sync", + "time", + "signal", ] } tokio-util = { workspace = true, features = ["rt"] } tracing = { workspace = true, features = ["attributes"] } @@ -46,12 +50,12 @@ tokio-stream = { workspace = true, features = ["net"] } [dev-dependencies] astria-core = { path = "../astria-core", features = ["client"] } config = { package = "astria-config", path = "../astria-config", features = [ - "tests", + "tests", ] } insta = { workspace = true, features = ["json"] } tempfile = { workspace = true } test_utils = { package = "astria-test-utils", path = "../astria-test-utils", features = [ - "geth", + "geth", ] } tokio-test = { workspace = true } wiremock = { workspace = true } diff --git a/crates/astria-auctioneer/local.env.example b/crates/astria-auctioneer/local.env.example index 0dd1638542..9b6f358a3c 100644 --- a/crates/astria-auctioneer/local.env.example +++ b/crates/astria-auctioneer/local.env.example @@ -1,10 +1,36 @@ -# Configuration options of Astria AUCTIONEER. +# Configuration options of Astria Auctioneer. -# Log level. One of debug, info, warn, or error -ASTRIA_AUCTIONEER_LOG="astria_AUCTIONEER=info" +# Address of the gRPC server for the sequencer chain +ASTRIA_AUCTIONEER_SEQUENCER_GRPC_ENDPOINT="http://127.0.0.1:8080" -# If true disables writing to the opentelemetry OTLP endpoint. -ASTRIA_AUCTIONEER_NO_OTEL=false +# Address of the ABCI server for the sequencer chain +ASTRIA_AUCTIONEER_SEQUENCER_ABCI_ENDPOINT="http://127.0.0.1:26657" + +# Chain ID of the sequencer chain which transactions are submitted to. +ASTRIA_AUCTIONEER_SEQUENCER_CHAIN_ID="astria-dev-1" + +# The path to the file storing the private key for the sequencer account used for signing +# transactions. The file should contain a hex-encoded Ed25519 secret key. +ASTRIA_AUCTIONEER_SEQUENCER_PRIVATE_KEY_PATH=/path/to/priv_sequencer_key.json + +# The fee asset denomination that will be used in the submitted sequencer transactions. +ASTRIA_AUCTIONEER_FEE_ASSET_DENOMINATION="nria" + +# The prefix that will be used to construct bech32m sequencer addresses. +ASTRIA_AUCTIONEER_SEQUENCER_ADDRESS_PREFIX=astria + +# Address of the gRPC server for the rollup's Optimistic Execution and Bundle services +ASTRIA_AUCTIONEER_ROLLUP_GRPC_ENDPOINT="http://127.0.0.1:50051" + +# The rollup ID to post the auction result to +ASTRIA_AUCTIONEER_ROLLUP_ID="astriachain" + +# The amount of time in miliseconds to wait between opening the auction and closing it to +# submit the result to the sequencer. +ASTRIA_AUCTIONEER_LATENCY_MARGIN_MS=1000 + +# Log level. One of debug, info, warn, or error +ASTRIA_AUCTIONEER_LOG="astria_auctioneer=info" # If true disables tty detection and forces writing telemetry to stdout. # If false span data is written to stdout only if it is connected to a tty. @@ -21,56 +47,14 @@ ASTRIA_AUCTIONEER_PRETTY_PRINT=false # is set to `true`. NO_COLOR= -# Address of the API server -ASTRIA_AUCTIONEER_API_ADDR="0.0.0.0:0" - -# Address of the RPC server for the sequencer chain -ASTRIA_AUCTIONEER_SEQUENCER_URL="http://127.0.0.1:26657" - -# Chain ID of the sequencer chain which transactions are submitted to. -ASTRIA_AUCTIONEER_SEQUENCER_CHAIN_ID="astria-dev-1" - -# A list of execution `::,::`. -# Rollup names are not case sensitive. If a name is repeated, the last list item is used. -# names are sha256 hashed and used as the `rollup_id` in `SequenceAction`s -ASTRIA_AUCTIONEER_ROLLUPS="astriachain::ws://127.0.0.1:8545" - -# The path to the file storing the private key for the sequencer account used for signing -# transactions. The file should contain a hex-encoded Ed25519 secret key. -ASTRIA_AUCTIONEER_PRIVATE_KEY_FILE=/path/to/priv_sequencer_key.json - -# The prefix that will be used to construct bech32m sequencer addresses. -ASTRIA_AUCTIONEER_SEQUENCER_ADDRESS_PREFIX=astria - -# Block time in milliseconds, used to force submitting of finished bundles. -# Should match the sequencer node configuration for 'timeout_commit', as -# specified in https://docs.tendermint.com/v0.34/tendermint-core/configuration.html -ASTRIA_AUCTIONEER_MAX_SUBMIT_INTERVAL_MS=2000 - -# Max bytes to encode into a single sequencer `SignedTransaction`, not including signature, -# public key, nonce. This is the sum of the sizes of all the `SequenceAction`s. Should be -# set below the sequencer's max block size to allow space for encoding, signature, public -# key and nonce bytes -ASTRIA_AUCTIONEER_MAX_BYTES_PER_BUNDLE=200000 - -# Max amount of finished bundles that can be in the submission queue. -# ASTRIA_AUCTIONEER_BUNDLE_QUEUE_CAPACITY * ASTRIA_AUCTIONEER_MAX_BYTES_PER_BUNDLE (e.g. -# 40000 * 200KB=8GB) is the limit on how much memory the finished bundle queue can consume. -# This should be lower than the resource limit enforced by Kubernetes on the pod, defined here: -# https://github.com/astriaorg/astria/blob/622d4cb8695e4fbcd86456bd16149420b8acda79/charts/evm-rollup/values.yaml#L276 -ASTRIA_AUCTIONEER_BUNDLE_QUEUE_CAPACITY=40000 - # Set to true to enable prometheus metrics. ASTRIA_AUCTIONEER_NO_METRICS=true # The address at which the prometheus HTTP listener will bind if enabled. ASTRIA_AUCTIONEER_METRICS_HTTP_LISTENER_ADDR="127.0.0.1:9000" -# The address at which the gRPC collector and health services are listening. -ASTRIA_AUCTIONEER_GRPC_ADDR="0.0.0.0:0" - -# The asset to use for paying for transactions submitted to sequencer. -ASTRIA_AUCTIONEER_FEE_ASSET="nria" +# If true disables writing to the opentelemetry OTLP endpoint. +ASTRIA_AUCTIONEER_NO_OTEL=false # The OTEL specific config options follow the OpenTelemetry Protocol Exporter v1 # specification as defined here: diff --git a/crates/astria-auctioneer/src/auction/bid.rs b/crates/astria-auctioneer/src/auction/bid.rs new file mode 100644 index 0000000000..d123aeda0b --- /dev/null +++ b/crates/astria-auctioneer/src/auction/bid.rs @@ -0,0 +1,57 @@ +use astria_core::{ + generated::bundle::v1alpha1 as raw, + protocol::transaction::v1::Transaction, +}; +use astria_eyre::eyre::{ + self, +}; +use bytes::Bytes; + +// TODO: this should probably be moved to astria_core::bundle +#[derive(Debug, Clone)] +pub(crate) struct Bundle { + raw: raw::Bundle, + /// The fee that will be charged for this bundle + fee: u64, + /// The byte list of transactions fto be included. + transactions: Vec, + /// The hash of the rollup block that this bundle is based on. + prev_rollup_block_hash: Bytes, + /// The hash of the sequencer block used to derive the rollup block that this bundle is based + /// on. + base_sequencer_block_hash: Bytes, +} + +impl Bundle { + fn try_from_raw(_raw: raw::Bundle) -> eyre::Result { + unimplemented!() + // Ok(Self { + // raw, + // }) + } + + fn into_raw(self) -> raw::Bundle { + unimplemented!() + } + + pub(crate) fn into_transaction(self, _nonce: u64) -> Transaction { + unimplemented!() + } + + pub(crate) fn into_bid(self) -> Bid { + Bid::from_bundle(self) + } +} + +#[derive(Debug, Clone)] +pub(crate) struct Bid {} + +impl Bid { + fn from_bundle(_bundle: Bundle) -> Self { + unimplemented!() + } + + fn into_bundle(self) -> Bundle { + unimplemented!() + } +} diff --git a/crates/astria-auctioneer/src/auction/builder.rs b/crates/astria-auctioneer/src/auction/builder.rs new file mode 100644 index 0000000000..716b123a98 --- /dev/null +++ b/crates/astria-auctioneer/src/auction/builder.rs @@ -0,0 +1,75 @@ +use std::time::Duration; + +use astria_eyre::eyre; +use tokio::sync::{ + mpsc, + oneshot, +}; +use tokio_util::sync::CancellationToken; + +use super::{ + BundlesHandle, + Driver, + Id, + OptimisticExecutionHandle, +}; +use crate::Metrics; + +pub(crate) struct Builder { + pub(crate) metrics: &'static Metrics, + pub(crate) shutdown_token: CancellationToken, + + /// The endpoint for the sequencer gRPC service used to get pending nonces + pub(crate) sequencer_grpc_endpoint: String, + /// The endpoint for the sequencer ABCI service used to submit transactions + pub(crate) sequencer_abci_endpoint: String, + /// The amount of time to wait after a commit before closing the auction for bids and + /// submitting the resulting transaction + pub(crate) latency_margin: Duration, + /// The ID of the auction to be run + pub(crate) auction_id: Id, +} + +impl Builder { + pub(crate) fn build(self) -> eyre::Result<(Driver, OptimisticExecutionHandle, BundlesHandle)> { + let Self { + metrics, + shutdown_token, + sequencer_grpc_endpoint, + sequencer_abci_endpoint, + latency_margin, + auction_id, + } = self; + + let (executed_block_tx, executed_block_rx) = oneshot::channel(); + let (block_commitment_tx, block_commitment_rx) = oneshot::channel(); + let (reorg_tx, reorg_rx) = oneshot::channel(); + // TODO: get the capacity from config or something instead of using a magic number + let (new_bids_tx, new_bids_rx) = mpsc::channel(16); + + let driver = Driver { + metrics, + shutdown_token, + sequencer_grpc_endpoint, + sequencer_abci_endpoint, + executed_block_rx, + block_commitment_rx, + reorg_rx, + new_bids_rx, + auction_id, + latency_margin, + }; + + Ok(( + driver, + OptimisticExecutionHandle { + executed_block_tx: Some(executed_block_tx), + block_commitment_tx: Some(block_commitment_tx), + reorg_tx: Some(reorg_tx), + }, + BundlesHandle { + new_bids_tx, + }, + )) + } +} diff --git a/crates/astria-auctioneer/src/auction/mod.rs b/crates/astria-auctioneer/src/auction/mod.rs new file mode 100644 index 0000000000..dcbc1f8eaa --- /dev/null +++ b/crates/astria-auctioneer/src/auction/mod.rs @@ -0,0 +1,237 @@ +mod bid; +mod builder; +use std::time::Duration; + +use astria_core::protocol::transaction::v1::Transaction; +use astria_eyre::eyre::{ + self, + eyre, + Context, +}; +use bid::{ + Bid, + Bundle, +}; +pub(crate) use builder::Builder; +use tokio::{ + select, + sync::{ + mpsc, + oneshot, + }, +}; +use tokio_util::sync::CancellationToken; + +use crate::Metrics; + +#[derive(Hash, Eq, PartialEq, Clone, Copy)] +pub(crate) struct Id([u8; 32]); + +impl Id { + pub(crate) fn from_sequencer_block_hash(block_hash: [u8; 32]) -> Self { + Self(block_hash) + } +} + +struct Auction { + highest_bid: Option, +} + +impl Auction { + fn new() -> Self { + Self { + highest_bid: None, + } + } + + fn bid(&mut self, _bid: Bundle) -> bool { + // save the bid if its higher than self.highest_bid + unimplemented!() + } + + fn winner(self) -> Bundle { + unimplemented!() + } +} + +pub(crate) struct OptimisticExecutionHandle { + executed_block_tx: Option>, + block_commitment_tx: Option>, + reorg_tx: Option>, +} + +impl OptimisticExecutionHandle { + pub(crate) async fn send_bundle(&self) -> eyre::Result<()> { + unimplemented!() + } + + pub(crate) fn executed_block(&mut self) -> eyre::Result<()> { + let _ = self + .executed_block_tx + .take() + .expect("should only send executed signal to a given auction once") + .send(()); + Ok(()) + } + + pub(crate) fn block_commitment(&mut self) -> eyre::Result<()> { + let _ = self + .block_commitment_tx + .take() + .expect("should only send block commitment signal to a given auction once") + .send(()); + + Ok(()) + } + + pub(crate) fn reorg(&mut self) -> eyre::Result<()> { + let _ = self + .reorg_tx + .take() + .expect("should only send reorg signal to a given auction once"); + + Ok(()) + } +} + +pub(crate) struct BundlesHandle { + new_bids_tx: mpsc::Sender, +} + +impl BundlesHandle { + pub(crate) fn send_bundle_timeout(&mut self, bundle: Bundle) -> eyre::Result<()> { + const BUNDLE_TIMEOUT: Duration = Duration::from_millis(100); + + let bid = bundle.into_bid(); + + self.new_bids_tx + .try_send(bid) + .wrap_err("bid channel full")?; + + Ok(()) + } +} + +// TODO: should this be the same object as the auction? +pub(crate) struct Driver { + #[allow(dead_code)] + metrics: &'static Metrics, + shutdown_token: CancellationToken, + + /// The endpoint for the sequencer's gRPC service, used for fetching pending nonces + sequencer_grpc_endpoint: String, + /// The endpoint for the sequencer's ABCI server, used for submitting transactions + sequencer_abci_endpoint: String, + /// Channel for receiving the executed block signal to start processing bundles + executed_block_rx: oneshot::Receiver<()>, + /// Channel for receiving the block commitment signal to start the latency margin timer + block_commitment_rx: oneshot::Receiver<()>, + /// Channel for receiving the reorg signal + reorg_rx: oneshot::Receiver<()>, + /// Channel for receiving new bundles + new_bids_rx: mpsc::Receiver, + /// The time between receiving a block commitment + latency_margin: Duration, + /// The ID of the auction + auction_id: Id, +} + +impl Driver { + 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 mut auction: Option = None; + + let mut nonce_fetch: Option>> = None; + + let auction_result = loop { + select! { + biased; + + () = self.shutdown_token.cancelled() => break Err(eyre!("received shutdown signal")), + + signal = &mut self.reorg_rx => { + match signal { + Ok(()) => { + break Err(eyre!("reorg signal received")) + } + Err(_) => { + return Err(eyre!("reorg signal channel closed")); + } + } + // + } + + // get the auction winner when the timer expires + // TODO: should this also be conditioned on auction.is_some()? this feels redundant as we only populate the timer if the auction isnt none + _ = async { latency_margin_timer.as_mut().unwrap() }, if latency_margin_timer.is_some() => { + break Ok(auction.unwrap().winner()); + } + + signal = &mut self.executed_block_rx, if auction.is_none() => { + if let Err(e) = signal { + break Err(eyre!("exec signal channel closed")).wrap_err(e); + } + // set auction to open so it starts collecting bids + auction = Some(Auction::new()); + } + + signal = &mut self.block_commitment_rx, if auction.is_some() => { + if let Err(e) = signal { + break Err(eyre!("commit signal channel closed")).wrap_err(e); + } + // set the timer + latency_margin_timer = Some(tokio::time::sleep(self.latency_margin)); + + // TODO: also want to fetch the pending nonce here (we wait for commit because we want the pending nonce from after the commit) + nonce_fetch = Some(tokio::task::spawn(async { + // TODO: fetch the pending nonce using the sequencer client with tryhard + Ok(0) + })); + } + + // TODO: new bundles from the bundle stream if auction exists? + // - add the bid to the auction if executed + + } + // submit the auction result to the sequencer/wait for cancellation signal + // 1. result from submit_fut if !submission.terminated() + }; + + // await the nonce fetch result + // TODO: flatten this or get rid of the option somehow + let nonce = nonce_fetch + .expect("should have received commit to exit the bid loop") + .await + .wrap_err("task failed")? + .wrap_err("failed to fetch nonce")?; + + // handle auction result + let transaction = match auction_result { + // TODO: add signer + Ok(winner) => winner.into_transaction(nonce), + Err(e) => { + return Err(e); + } + }; + + let submission_result = select! { + biased; + + // TODO: should this be Ok() or something? + () = self.shutdown_token.cancelled() => Err(eyre!("received shutdown signal")), + + // submit the transaction to the sequencer + result = self.submit_transaction(transaction) => { + // TODO: handle submission failure better? + result + } + }; + + submission_result + } + + async fn submit_transaction(&self, _transaction: Transaction) -> eyre::Result<()> { + unimplemented!() + } +} diff --git a/crates/astria-auctioneer/src/auction_driver/builder.rs b/crates/astria-auctioneer/src/auction_driver/builder.rs deleted file mode 100644 index 89baebd619..0000000000 --- a/crates/astria-auctioneer/src/auction_driver/builder.rs +++ /dev/null @@ -1,20 +0,0 @@ -use astria_eyre::eyre; - -use super::AuctionDriver; -use crate::Metrics; - -pub(crate) struct Builder { - pub(crate) metrics: &'static Metrics, -} - -impl Builder { - pub(crate) fn build(self) -> eyre::Result { - let Self { - metrics, - } = self; - - Ok(AuctionDriver { - metrics, - }) - } -} diff --git a/crates/astria-auctioneer/src/auction_driver/mod.rs b/crates/astria-auctioneer/src/auction_driver/mod.rs deleted file mode 100644 index d312cfb95f..0000000000 --- a/crates/astria-auctioneer/src/auction_driver/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -use astria_eyre::eyre; - -use crate::Metrics; - -mod builder; -pub(crate) use builder::Builder; - -pub(crate) struct AuctionDriver { - #[allow(dead_code)] - metrics: &'static Metrics, -} - -impl AuctionDriver { - pub(crate) async fn run(self) -> eyre::Result<()> { - todo!("implement me") - } -} diff --git a/crates/astria-auctioneer/src/auctioneer/inner.rs b/crates/astria-auctioneer/src/auctioneer/inner.rs index a6f302c2dc..52b13fd4fb 100644 --- a/crates/astria-auctioneer/src/auctioneer/inner.rs +++ b/crates/astria-auctioneer/src/auctioneer/inner.rs @@ -7,7 +7,6 @@ use astria_eyre::eyre::{ use itertools::Itertools as _; use tokio::{ select, - task::JoinError, time::timeout, }; use tokio_util::{ @@ -21,7 +20,8 @@ use tracing::{ }; use crate::{ - auction_driver, + flatten, + optimistic_executor, Config, Metrics, }; @@ -36,8 +36,8 @@ pub(super) struct Auctioneer { impl Auctioneer { const AUCTION_DRIVER: &'static str = "auction_driver"; + const OPTIMISTIC_EXECUTOR: &'static str = "optimistic_executor"; const _BUNDLE_COLLECTOR: &'static str = "bundle_collector"; - const _OPTIMISTIC_EXECUTOR: &'static str = "optimistic_executor"; /// Creates an [`Auctioneer`] service from a [`Config`] and [`Metrics`]. pub(super) fn new( @@ -46,23 +46,29 @@ impl Auctioneer { shutdown_token: CancellationToken, ) -> eyre::Result { let Config { + sequencer_grpc_endpoint, + sequencer_abci_endpoint, + latency_margin_ms, + rollup_grpc_endpoint, + rollup_id, .. } = cfg; let mut tasks = JoinMap::new(); - // TODO: add tasks here - // - optimistic executor - // - bundle collector - // - auction driver - // - runs the auction - // - runs the sequencer submitter - let auction_driver = auction_driver::Builder { + let optimistic_executor = optimistic_executor::Builder { metrics, + shutdown_token: shutdown_token.clone(), + sequencer_grpc_endpoint, + sequencer_abci_endpoint, + rollup_id, + optimistic_execution_grpc_endpoint: rollup_grpc_endpoint.clone(), + bundle_grpc_endpoint: rollup_grpc_endpoint.clone(), + latency_margin: Duration::from_millis(latency_margin_ms), } .build() - .wrap_err("failed to initialize the auction driver")?; - tasks.spawn(Self::AUCTION_DRIVER, auction_driver.run()); + .wrap_err("failed to initialize the optimistic executor")?; + tasks.spawn(Self::OPTIMISTIC_EXECUTOR, optimistic_executor.run()); Ok(Self { shutdown_token, @@ -130,11 +136,3 @@ impl Auctioneer { info!("shutting down"); } } - -pub(super) fn flatten(res: Result, JoinError>) -> eyre::Result { - match res { - Ok(Ok(val)) => Ok(val), - Ok(Err(err)) => Err(err).wrap_err("task returned with error"), - Err(err) => Err(err).wrap_err("task panicked"), - } -} diff --git a/crates/astria-auctioneer/src/block/commitment_stream.rs b/crates/astria-auctioneer/src/block/commitment_stream.rs new file mode 100644 index 0000000000..9ea3394561 --- /dev/null +++ b/crates/astria-auctioneer/src/block/commitment_stream.rs @@ -0,0 +1,56 @@ +use std::pin::Pin; + +use astria_core::generated::sequencerblock::optimisticblock::v1alpha1::GetBlockCommitmentStreamResponse; +use astria_eyre::eyre::{ + self, + Context, + OptionExt, +}; +use futures::{ + Stream, + StreamExt as _, +}; + +use super::BlockCommitment; +use crate::sequencer_grpc_client::SequencerGrpcClient; + +/// A stream for receiving committed blocks from the sequencer. +pub(crate) struct BlockCommitmentStream { + client: Pin>>, +} + +impl BlockCommitmentStream { + pub(crate) async fn new(sequencer_grpc_endpoint: String) -> eyre::Result { + let mut sequencer_client = SequencerGrpcClient::new(&sequencer_grpc_endpoint) + .wrap_err("failed to initialize sequencer grpc client")?; + + let committed_stream_client = sequencer_client + .get_block_commitment_stream() + .await + .wrap_err("failed to stream block commitments")?; + + Ok(Self { + client: Box::pin(committed_stream_client), + }) + } +} + +impl Stream for BlockCommitmentStream { + type Item = eyre::Result; + + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + let raw = futures::ready!(self.client.poll_next_unpin(cx)) + .ok_or_eyre("stream has been closed")? + .wrap_err("received gRPC error")? + .commitment + .ok_or_eyre("block commitment stream response did not contain block commitment")?; + + let commitment = BlockCommitment::try_from_raw(raw) + .wrap_err("failed to parse raw to BlockCommitment")?; + + std::task::Poll::Ready(Some(Ok(commitment))) + } +} diff --git a/crates/astria-auctioneer/src/block/executed_stream.rs b/crates/astria-auctioneer/src/block/executed_stream.rs new file mode 100644 index 0000000000..1ac7e16038 --- /dev/null +++ b/crates/astria-auctioneer/src/block/executed_stream.rs @@ -0,0 +1,77 @@ +use std::{ + pin::Pin, + time::Duration, +}; + +use astria_core::{ + generated::bundle::v1alpha1::ExecuteOptimisticBlockStreamResponse, + primitive::v1::RollupId, +}; +use astria_eyre::eyre::{ + self, + Context, +}; +use futures::{ + Stream, + StreamExt as _, +}; +use tokio::sync::mpsc; + +use super::{ + Executed, + Optimistic, +}; +use crate::optimistic_execution_client::OptimisticExecutionClient; + +pub(crate) struct Handle { + blocks_to_execute_tx: mpsc::Sender, +} + +impl Handle { + pub(crate) fn try_send_block_to_execute(&mut self, block: Optimistic) -> eyre::Result<()> { + // TODO: move the duration value to a const or config value? + self.blocks_to_execute_tx + .try_send(block) + .wrap_err("failed to send block to execute")?; + + Ok(()) + } +} + +pub(crate) struct ExecutedBlockStream { + client: Pin>>, +} + +impl ExecutedBlockStream { + pub(crate) async fn new( + rollup_id: RollupId, + rollup_grpc_endpoint: String, + ) -> eyre::Result<(Handle, ExecutedBlockStream)> { + let mut optimistic_execution_client = OptimisticExecutionClient::new(&rollup_grpc_endpoint) + .wrap_err("failed to initialize optimistic execution client")?; + let (executed_stream_client, blocks_to_execute_tx) = optimistic_execution_client + .execute_optimistic_block_stream(rollup_id) + .await + .wrap_err("failed to stream executed blocks")?; + + Ok(( + Handle { + blocks_to_execute_tx, + }, + Self { + client: Box::pin(executed_stream_client), + }, + )) + } +} + +impl Stream for ExecutedBlockStream { + type Item = eyre::Result; + + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context, + ) -> std::task::Poll> { + unimplemented!() + } +} diff --git a/crates/astria-auctioneer/src/block/mod.rs b/crates/astria-auctioneer/src/block/mod.rs new file mode 100644 index 0000000000..e160a70e84 --- /dev/null +++ b/crates/astria-auctioneer/src/block/mod.rs @@ -0,0 +1,217 @@ +use astria_core::{ + execution, + generated::{ + bundle::v1alpha1 as raw_bundle, + sequencerblock::{ + optimisticblock::v1alpha1 as raw_optimistic_block, + v1 as raw_sequencer_block, + }, + }, + primitive::v1::RollupId, + sequencerblock::v1::block::{ + FilteredSequencerBlock, + FilteredSequencerBlockParts, + }, + Protobuf, +}; +use astria_eyre::eyre::{ + self, + eyre, + Context, + OptionExt, +}; +use bytes::Bytes; +use prost::Message as _; + +pub(crate) mod commitment_stream; +pub(crate) mod executed_stream; +pub(crate) mod optimistic_stream; + +/// Converts a [`tendermint::Time`] to a [`prost_types::Timestamp`]. +fn convert_tendermint_time_to_protobuf_timestamp( + value: sequencer_client::tendermint::Time, +) -> pbjson_types::Timestamp { + let sequencer_client::tendermint_proto::google::protobuf::Timestamp { + seconds, + nanos, + } = value.into(); + pbjson_types::Timestamp { + seconds, + nanos, + } +} + +#[derive(Debug, Clone)] +pub(crate) struct Optimistic { + filtered_sequencer_block: FilteredSequencerBlock, +} + +impl Optimistic { + pub(crate) fn try_from_raw( + raw: raw_sequencer_block::FilteredSequencerBlock, + ) -> eyre::Result { + Ok(Self { + filtered_sequencer_block: FilteredSequencerBlock::try_from_raw(raw)?, + }) + } + + pub(crate) fn into_raw(self) -> raw_sequencer_block::FilteredSequencerBlock { + self.filtered_sequencer_block.into_raw() + } + + pub(crate) fn try_into_base_block( + self, + rollup_id: RollupId, + ) -> eyre::Result { + let FilteredSequencerBlockParts { + block_hash, + header, + mut rollup_transactions, + .. + } = self.filtered_sequencer_block.into_parts(); + + let serialized_transactions = rollup_transactions + .swap_remove(&rollup_id) + .ok_or_eyre( + "FilteredSequencerBlock does not contain transactions for the given rollup", + )? + .into_parts(); + + let transactions = serialized_transactions + .transactions + .into_iter() + .map(raw_sequencer_block::RollupData::decode) + .collect::>() + .wrap_err("failed to decode RollupData")?; + + let timestamp = Some(convert_tendermint_time_to_protobuf_timestamp(header.time())); + + Ok(raw_bundle::BaseBlock { + sequencer_block_hash: Bytes::copy_from_slice(&block_hash), + transactions, + timestamp, + }) + } + + pub(crate) fn sequencer_block_hash(&self) -> [u8; 32] { + self.filtered_sequencer_block.block_hash().clone() + } + + pub(crate) fn sequencer_height(&self) -> u64 { + self.filtered_sequencer_block.height().into() + } +} + +#[derive(Debug, Clone)] +pub(crate) struct Executed { + block: execution::v1::Block, + sequencer_block_hash: [u8; 32], +} + +impl Executed { + pub(crate) fn try_from_raw( + raw: raw_bundle::ExecuteOptimisticBlockStreamResponse, + ) -> eyre::Result { + let block = if let Some(raw_block) = raw.block { + execution::v1::Block::try_from_raw(raw_block).wrap_err("invalid rollup block")? + } else { + return Err(eyre!("missing block")); + }; + + let sequencer_block_hash = raw + .base_sequencer_block_hash + .as_ref() + .try_into() + .wrap_err("invalid block hash")?; + + Ok(Self { + block, + sequencer_block_hash, + }) + } + + pub(crate) fn sequencer_block_hash(&self) -> [u8; 32] { + self.sequencer_block_hash + } +} + +#[derive(Debug, Clone)] +pub(crate) struct BlockCommitment { + sequencer_height: u64, + sequnecer_block_hash: [u8; 32], +} + +impl BlockCommitment { + pub(crate) fn try_from_raw( + raw: raw_optimistic_block::SequencerBlockCommit, + ) -> eyre::Result { + Ok(Self { + sequencer_height: raw.height, + sequnecer_block_hash: raw + .block_hash + .as_ref() + .try_into() + .wrap_err("invalid block hash")?, + }) + } + + pub(crate) fn into_raw(self) -> raw_optimistic_block::SequencerBlockCommit { + raw_optimistic_block::SequencerBlockCommit { + height: self.sequencer_height, + block_hash: Bytes::copy_from_slice(&self.sequnecer_block_hash), + } + } + + pub(crate) fn sequencer_block_hash(&self) -> [u8; 32] { + self.sequnecer_block_hash + } + + pub(crate) fn sequencer_height(&self) -> u64 { + self.sequencer_height + } +} + +pub(crate) struct CurrentBlock { + optimistic: Optimistic, + executed: Option, + committed: Option, +} + +impl CurrentBlock { + pub(crate) fn opt(optimistic_block: Optimistic) -> Self { + Self { + optimistic: optimistic_block, + executed: None, + committed: None, + } + } + + pub(crate) fn exec(self, executed_block: Executed) -> eyre::Result { + if executed_block.sequencer_block_hash() != self.optimistic.sequencer_block_hash() { + return Err(eyre!("block hash mismatch")); + } + + Ok(Self { + executed: Some(executed_block), + ..self + }) + } + + pub(crate) fn commit(self, block_commitment: BlockCommitment) -> eyre::Result { + if block_commitment.sequencer_block_hash() != self.optimistic.sequencer_block_hash() { + return Err(eyre!("block hash mismatch")); + } + if block_commitment.sequencer_height() != self.optimistic.sequencer_height() { + return Err(eyre!("block height mismatch")); + } + + Ok(Self { + committed: Some(block_commitment), + ..self + }) + } + + pub(crate) fn sequencer_block_hash(&self) -> [u8; 32] { + self.optimistic.sequencer_block_hash() + } +} diff --git a/crates/astria-auctioneer/src/block/optimistic_stream.rs b/crates/astria-auctioneer/src/block/optimistic_stream.rs new file mode 100644 index 0000000000..9a4da71f15 --- /dev/null +++ b/crates/astria-auctioneer/src/block/optimistic_stream.rs @@ -0,0 +1,68 @@ +use std::pin::Pin; + +use astria_core::{ + generated::sequencerblock::optimisticblock::v1alpha1::GetOptimisticBlockStreamResponse, + primitive::v1::RollupId, +}; +use astria_eyre::eyre::{ + self, + Context, + OptionExt, +}; +use futures::{ + Stream, + StreamExt as _, +}; + +use super::Optimistic; +use crate::sequencer_grpc_client::SequencerGrpcClient; + +/// A stream for receiving optimistic blocks from the sequencer. +pub(crate) struct OptimisticBlockStream { + client: Pin>>, + // client: Pin>>>, +} + +impl OptimisticBlockStream { + pub(crate) async fn new( + rollup_id: RollupId, + sequencer_grpc_endpoint: String, + ) -> eyre::Result { + let mut sequencer_client = SequencerGrpcClient::new(&sequencer_grpc_endpoint) + .wrap_err("failed to initialize sequencer grpc client")?; + + let optimistic_stream_client = sequencer_client + .get_optimistic_block_stream(rollup_id) + .await + .wrap_err("failed to stream optimistic blocks")?; + + Ok(OptimisticBlockStream { + // client, + client: Box::pin(optimistic_stream_client), + }) + } +} + +impl Stream for OptimisticBlockStream { + type Item = eyre::Result; + + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context, + ) -> std::task::Poll> { + let raw = futures::ready!(self.client.poll_next_unpin(cx)) + // TODO: better error messages here + .ok_or_eyre("stream has been closed")? + .wrap_err("received gRPC error")? + .block + .ok_or_eyre( + "optimsitic block stream response did not contain filtered sequencer block", + )?; + + let optimistic_block = + Optimistic::try_from_raw(raw).wrap_err("failed to parse raw to Optimistic")?; + + std::task::Poll::Ready(Some(Ok(optimistic_block))) + } +} diff --git a/crates/astria-auctioneer/src/config.rs b/crates/astria-auctioneer/src/config.rs index a3c0305622..ffc6da1ba7 100644 --- a/crates/astria-auctioneer/src/config.rs +++ b/crates/astria-auctioneer/src/config.rs @@ -1,3 +1,4 @@ +use astria_core::primitive::v1::asset; use serde::{ Deserialize, Serialize, @@ -9,6 +10,28 @@ use serde::{ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] /// The single config for creating an astria-auctioneer service. pub struct Config { + /// The endpoint for the sequencer gRPC service used for the optimistic block stream + pub sequencer_grpc_endpoint: String, + /// The endpoint for the sequencer ABCI service used for submitting the auction winner + /// transaction + pub sequencer_abci_endpoint: String, + /// The chain ID for the sequencer network + pub sequencer_chain_id: String, + /// The file path for the private key used to sign sequencer transactions with the auction + /// results + pub sequencer_private_key_path: String, + // The fee asset denomination to use for the sequnecer transactions. + pub fee_asset_denomination: asset::Denom, + // The address prefix to use when constructing sequencer addresses using the signing key. + pub sequencer_address_prefix: String, + /// The endpoint for the rollup gRPC service used for the optimistic execution and bundle + /// streams + pub rollup_grpc_endpoint: String, + /// The rollup ID used to filter the optimistic blocks stream + pub rollup_id: String, + /// The amount of time in miliseconds to wait after a commit before closing the auction for + /// bids and submitting the result to the sequencer. + pub latency_margin_ms: u64, /// Log level for the service. pub log: String, /// Forces writing trace data to stdout no matter if connected to a tty or not. diff --git a/crates/astria-auctioneer/src/lib.rs b/crates/astria-auctioneer/src/lib.rs index cbc2c3ca61..01868855db 100644 --- a/crates/astria-auctioneer/src/lib.rs +++ b/crates/astria-auctioneer/src/lib.rs @@ -1,13 +1,30 @@ //! TODO: Add a description -mod auction_driver; +mod auction; mod auctioneer; +mod block; mod build_info; pub mod config; pub(crate) mod metrics; +mod optimistic_execution_client; +mod optimistic_executor; +mod sequencer_grpc_client; +use astria_eyre::{ + eyre, + eyre::WrapErr as _, +}; pub use auctioneer::Auctioneer; pub use build_info::BUILD_INFO; pub use config::Config; pub use metrics::Metrics; pub use telemetry; +use tokio::task::JoinError; + +fn flatten(res: Result, JoinError>) -> eyre::Result { + match res { + Ok(Ok(val)) => Ok(val), + Ok(Err(err)) => Err(err).wrap_err("task returned with error"), + Err(err) => Err(err).wrap_err("task panicked"), + } +} diff --git a/crates/astria-auctioneer/src/optimistic_execution_client.rs b/crates/astria-auctioneer/src/optimistic_execution_client.rs new file mode 100644 index 0000000000..e4972ea093 --- /dev/null +++ b/crates/astria-auctioneer/src/optimistic_execution_client.rs @@ -0,0 +1,145 @@ +use std::time::Duration; + +use astria_core::{ + generated::bundle::v1alpha1::{ + optimistic_execution_service_client::OptimisticExecutionServiceClient, + ExecuteOptimisticBlockStreamRequest, + ExecuteOptimisticBlockStreamResponse, + }, + primitive::v1::RollupId, +}; +use astria_eyre::eyre::{ + self, + Context, +}; +use futures::Stream; +use tokio::sync::mpsc; +use tokio_stream::{ + wrappers::ReceiverStream, + StreamExt as _, +}; +use tonic::transport::{ + Channel, + Endpoint, + Uri, +}; +use tracing::{ + warn, + Instrument, + Span, +}; +use tryhard::backoff_strategies::ExponentialBackoff; + +use crate::block; + +struct OptimisitcBlocksToExecuteStream { + opts: ReceiverStream, +} + +impl Stream for OptimisitcBlocksToExecuteStream { + type Item = eyre::Result; + + fn poll_next( + self: std::pin::Pin<&mut Self>, + _cx: &mut std::task::Context, + ) -> std::task::Poll> { + // opts.next + unimplemented!("get an opt block if possible. otherwise return pending?"); + } +} + +pub(crate) struct OptimisticExecutionClient { + inner: OptimisticExecutionServiceClient, + uri: Uri, +} + +impl OptimisticExecutionClient { + pub(crate) fn new(rollup_uri: &str) -> eyre::Result { + let uri = rollup_uri + .parse::() + .wrap_err("failed parsing optimistic execution uri")?; + + // TODO: use UDS socket + let endpoint = Endpoint::from(uri.clone()); + let inner = OptimisticExecutionServiceClient::new(endpoint.connect_lazy()); + + Ok(Self { + inner, + uri, + }) + } + + pub(crate) async fn execute_optimistic_block_stream( + &mut self, + rollup_id: RollupId, + ) -> eyre::Result<( + tonic::Streaming, + mpsc::Sender, + )> { + let span = tracing::Span::current(); + let retry_cfg = make_retry_cfg("execute optimistic blocks".into(), span); + let client = self.inner.clone(); + + let (stream, opt_tx) = tryhard::retry_fn(|| { + let mut client = client.clone(); + + // create request stream + let (opts_to_exec_tx, opts_to_exec_rx) = mpsc::channel(16); + let opts = ReceiverStream::new(opts_to_exec_rx); + let rollup_id = rollup_id.clone(); + + let requests = opts.map(move |opt: block::Optimistic| { + let base_block = opt + .try_into_base_block(rollup_id) + .wrap_err("failed to create BaseBlock from filtered_sequencer_block") + // TODO: get rid of this unwrap so we can handle blocks with no transactions. + // - instead of opts.map(), i should onyl create exec requests for blocks with + // transactions in them. + // - moving this into a domain specific stream will help clear up the logic + .unwrap(); + + ExecuteOptimisticBlockStreamRequest { + base_block: Some(base_block), + } + }); + + async move { + let stream = client.execute_optimistic_block_stream(requests).await?; + Ok((stream, opts_to_exec_tx)) + } + }) + .with_config(retry_cfg) + .in_current_span() + .await + .wrap_err("failed to initialize optimistic execution stream")?; + + Ok((stream.into_inner(), opt_tx)) + } +} + +fn make_retry_cfg( + msg: String, + span: Span, +) -> tryhard::RetryFutureConfig< + ExponentialBackoff, + impl Fn(u32, Option, &tonic::Status) -> futures::future::Ready<()>, +> { + tryhard::RetryFutureConfig::new(1024) + .exponential_backoff(Duration::from_millis(100)) + .max_delay(Duration::from_secs(2)) + .on_retry( + move |attempt: u32, next_delay: Option, error: &tonic::Status| { + let wait_duration = next_delay + .map(humantime::format_duration) + .map(tracing::field::display); + warn!( + parent: &span, + attempt, + wait_duration, + error = error as &dyn std::error::Error, + "attempt to {msg} failed; retrying after backoff", + ); + futures::future::ready(()) + }, + ) +} diff --git a/crates/astria-auctioneer/src/optimistic_executor/builder.rs b/crates/astria-auctioneer/src/optimistic_executor/builder.rs new file mode 100644 index 0000000000..25ac901b4c --- /dev/null +++ b/crates/astria-auctioneer/src/optimistic_executor/builder.rs @@ -0,0 +1,53 @@ +use std::time::Duration; + +use astria_core::primitive::v1::RollupId; +use astria_eyre::eyre; +use tokio_util::sync::CancellationToken; + +use super::OptimisticExecutor; +use crate::Metrics; + +pub(crate) struct Builder { + pub(crate) metrics: &'static Metrics, + pub(crate) shutdown_token: CancellationToken, + /// The endpoint for the sequencer gRPC service used for the optimistic block stream + pub(crate) sequencer_grpc_endpoint: String, + /// The endpoint for the sequencer ABCI service used to submit the auction winner transaction + pub(crate) sequencer_abci_endpoint: String, + /// The rollup ID for the filtered optimistic block stream + pub(crate) rollup_id: String, + /// The endpoint for the rollup's optimistic execution gRPC service + pub(crate) optimistic_execution_grpc_endpoint: String, + /// The endpoint for the rollup's bundle gRPC service + pub(crate) bundle_grpc_endpoint: String, + /// The amount of time to wait after a commit before closing the auction + pub(crate) latency_margin: Duration, +} + +impl Builder { + pub(crate) fn build(self) -> eyre::Result { + let Self { + metrics, + shutdown_token, + sequencer_grpc_endpoint, + sequencer_abci_endpoint, + rollup_id, + optimistic_execution_grpc_endpoint, + bundle_grpc_endpoint, + latency_margin, + } = self; + + let rollup_id = RollupId::from_unhashed_bytes(&rollup_id); + + Ok(OptimisticExecutor { + metrics, + shutdown_token, + sequencer_grpc_endpoint, + sequencer_abci_endpoint, + rollup_id, + rollup_grpc_endpoint: optimistic_execution_grpc_endpoint, + bundle_grpc_endpoint, + latency_margin, + }) + } +} diff --git a/crates/astria-auctioneer/src/optimistic_executor/mod.rs b/crates/astria-auctioneer/src/optimistic_executor/mod.rs new file mode 100644 index 0000000000..cc7b1d3fbc --- /dev/null +++ b/crates/astria-auctioneer/src/optimistic_executor/mod.rs @@ -0,0 +1,200 @@ +mod builder; + +use std::{ + collections::HashMap, + time::Duration, +}; + +use astria_core::primitive::v1::RollupId; +use astria_eyre::eyre::{ + self, + OptionExt, + WrapErr as _, +}; +use block::CurrentBlock; +pub(crate) use builder::Builder; +use telemetry::display::base64; +use tokio::select; +use tokio_stream::StreamExt as _; +use tokio_util::{ + sync::CancellationToken, + task::JoinMap, +}; +use tracing::{ + error, + info, +}; + +use super::optimistic_execution_client::OptimisticExecutionClient; +use crate::{ + auction, + block::{ + self, + commitment_stream::BlockCommitmentStream, + executed_stream::ExecutedBlockStream, + optimistic_stream::OptimisticBlockStream, + }, + flatten, +}; + +pub(crate) struct OptimisticExecutor { + #[allow(dead_code)] + metrics: &'static crate::Metrics, + shutdown_token: CancellationToken, + sequencer_grpc_endpoint: String, + sequencer_abci_endpoint: String, + rollup_id: RollupId, + rollup_grpc_endpoint: String, + bundle_grpc_endpoint: String, + latency_margin: Duration, +} + +impl OptimisticExecutor { + pub(crate) async fn run(self) -> eyre::Result<()> { + let mut optimistic_stream = + OptimisticBlockStream::new(self.rollup_id, self.sequencer_grpc_endpoint.clone()) + .await + .wrap_err("failed to initialize optimsitic block stream")?; + + let mut block_commitment_stream = + BlockCommitmentStream::new(self.sequencer_grpc_endpoint.clone()) + .await + .wrap_err("failed to initialize block commitment stream")?; + + let (mut exec_stream_handle, mut executed_block_stream) = + ExecutedBlockStream::new(self.rollup_id, self.rollup_grpc_endpoint.clone()) + .await + .wrap_err("failed to initialize executed block stream")?; + + // let bundle_stream = BundleServiceClient::new(bundle_service_grpc_url) + // .wrap_err("failed to initialize bundle service grpc client")?; + + // maybe just make this a fused future `auction_fut` + let mut auction_futs: JoinMap> = JoinMap::new(); + let mut auction_oe_handles: HashMap = + HashMap::new(); + let mut auction_bundle_handles: HashMap = + HashMap::new(); + + let optimistic_block = optimistic_stream + .next() + .await + .ok_or_eyre("optimistic stream closed during startup?")? + .wrap_err("failed to get optimistic block during startup")?; + let mut curr_block = Some(CurrentBlock::opt(optimistic_block)); + + let reason = { + loop { + select! { + biased; + () = self.shutdown_token.cancelled() => { + break Ok("received shutdown signal"); + }, + + Some((_id, res)) = auction_futs.join_next() => { + // TODO: fix this + break flatten(res) + .wrap_err_with(|| "auction failed for block {id}") + .map(|_| "auction {id} failed"); + }, + + Some(res) = optimistic_stream.next() => { + // move into self.process_optimistic_block() or somethingg + let optimistic_block = res.wrap_err("failed to get optimistic block")?; + + // reorg by shutting down current auction fut, dumping the oneshot (this is the + // state transition) and replace the current block with the optimistic block + let auction_id = auction::Id::from_sequencer_block_hash(optimistic_block.sequencer_block_hash()); + if let Some(curr_block) = curr_block { + let handle = auction_oe_handles.get_mut(&auction_id).ok_or_eyre("unable to get handle for current auction")?; + handle.reorg()?; + + info!( + // TODO: is this how we display block hashes? + optimistic_block.sequencer_block_hash = %base64(optimistic_block.sequencer_block_hash()), + current_block.sequencer_block_hash = %base64(curr_block.sequencer_block_hash()), + "replacing current block with optimistic block"); + + }; + curr_block = Some(CurrentBlock::opt(optimistic_block.clone())); + + // create and run the auction fut and save the its handles + let (auction_driver, optimistic_execution_handle, bundles_handle) = auction::Builder { + metrics: self.metrics, + shutdown_token: self.shutdown_token.clone(), + sequencer_grpc_endpoint: self.sequencer_grpc_endpoint.clone(), + sequencer_abci_endpoint: self.sequencer_abci_endpoint.clone(), + latency_margin: self.latency_margin, + auction_id, + }.build().wrap_err("failed to build auction")?; + + // TODO: clean this up? + auction_futs.spawn(auction_id, auction_driver.run()); + auction_oe_handles.insert(auction_id, optimistic_execution_handle); + auction_bundle_handles.insert(auction_id, bundles_handle); + + // forward the optimistic block to the rollup's optimistic execution server + exec_stream_handle + .try_send_block_to_execute(optimistic_block) + .wrap_err("failed to send optimistic block for execution")?; + }, + + Some(res) = block_commitment_stream.next() => { + let block_commitment = res.wrap_err("failed to get block commitment")?; + + if let Some(block) = curr_block { + curr_block = Some(block.commit(block_commitment).wrap_err("state transition failure")?); + } + + // 2. send commit signal to the auction so it will start the timer + if let Some(hash) = curr_block.as_ref().map(|block| block.sequencer_block_hash()) { + let auction_id = auction::Id::from_sequencer_block_hash(hash); + let handle = auction_oe_handles.get_mut(&auction_id).ok_or_eyre("unable to get handle for current auction")?; + handle.block_commitment()?; + } + }, + + // 3. block commitment stream + Some(res) = executed_block_stream.next() => { + // 1. update curr block state (react if invalid state transition? maybe by dropping + // the block and its auction) + let executed_block = res.wrap_err("failed to get executed block")?; + + let next_block = curr_block + .map(|block| block.exec(executed_block).wrap_err("state transition failure")) + .expect("should only receive executed blocks after an optimistic block has been received")?; + // TODO: probably can get rid of this + curr_block = Some(next_block); + + // 2. send executed signal to the auction so it will start pulling bundles + if let Some(hash) = curr_block.as_ref().map(|block| block.sequencer_block_hash()) { + let auction_id = auction::Id::from_sequencer_block_hash(hash); + let handle = auction_oe_handles.get_mut(&auction_id).ok_or_eyre("unable to get handle for current auction")?; + handle.executed_block()?; + } + } + + + // 2. forward bundles from bundle stream into the correct auction fut + // - bundles will build up in the channel into the auction until the executed signal is + // sent to the auction fut. so if backpressure builds up here, i.e. bids arrive way + // before execution, we can decide how to react here. + // for example, we can drop all the bundles that are stuck in the channel and log a warning, + // or we can kill the auction for that given block + } + } + }; + + match reason { + Ok(msg) => info!(%msg, "shutting down"), + Err(err) => error!(%err, "shutting down due to error"), + }; + + self.shutdown(); + Ok(()) + } + + async fn shutdown(self) { + self.shutdown_token.cancel(); + } +} diff --git a/crates/astria-auctioneer/src/sequencer_grpc_client.rs b/crates/astria-auctioneer/src/sequencer_grpc_client.rs new file mode 100644 index 0000000000..6b2a9253be --- /dev/null +++ b/crates/astria-auctioneer/src/sequencer_grpc_client.rs @@ -0,0 +1,132 @@ +use std::time::Duration; + +use astria_core::{ + generated::sequencerblock::optimisticblock::v1alpha1::{ + optimistic_block_service_client::OptimisticBlockServiceClient, + GetBlockCommitmentStreamRequest, + GetBlockCommitmentStreamResponse, + GetOptimisticBlockStreamRequest, + GetOptimisticBlockStreamResponse, + }, + primitive::v1::RollupId, +}; +use astria_eyre::eyre::{ + self, + WrapErr as _, +}; +use tonic::transport::{ + Channel, + Endpoint, + Uri, +}; +use tracing::{ + instrument, + warn, + Instrument as _, + Span, +}; +use tryhard::backoff_strategies::ExponentialBackoff; + +/// Wraps the gRPC client for the Sequencer service that wraps client calls with `tryhard`. +#[derive(Debug, Clone)] +pub(crate) struct SequencerGrpcClient { + inner: OptimisticBlockServiceClient, + uri: Uri, +} + +impl SequencerGrpcClient { + pub(crate) fn new(sequencer_uri: &str) -> eyre::Result { + let uri = sequencer_uri + .parse::() + .wrap_err("failed parsing provided string as Uri")?; + + // TODO: use a UDS socket instead + let endpoint = Endpoint::from(uri.clone()); + let inner = OptimisticBlockServiceClient::new(endpoint.connect_lazy()); + Ok(Self { + inner, + uri, + }) + } + + #[instrument(skip_all, fields( + uri = %self.uri, + %rollup_id, + err, + ))] + pub(super) async fn get_optimistic_block_stream( + &mut self, + rollup_id: RollupId, + ) -> eyre::Result> { + let span = tracing::Span::current(); + let retry_cfg = make_retry_cfg("stream optimistic blocks".into(), span); + let client = self.inner.clone(); + + let stream = tryhard::retry_fn(|| { + let mut client = client.clone(); + let req = GetOptimisticBlockStreamRequest { + rollup_id: Some(rollup_id.into_raw()), + }; + async move { client.get_optimistic_block_stream(req).await } + }) + .with_config(retry_cfg) + .in_current_span() + .await + .wrap_err("failed to initialize optimistic block stream")? + .into_inner(); + + Ok(stream) + } + + #[instrument(skip_all, fields( + uri = %self.uri, + err, + ))] + pub(super) async fn get_block_commitment_stream( + &mut self, + ) -> eyre::Result> { + let span = tracing::Span::current(); + let retry_cfg = make_retry_cfg("stream block commitments".into(), span); + let client = self.inner.clone(); + + let stream = tryhard::retry_fn(|| { + let mut client = client.clone(); + let req = GetBlockCommitmentStreamRequest {}; + async move { client.get_block_commitment_stream(req).await } + }) + .with_config(retry_cfg) + .in_current_span() + .await + .wrap_err("failed to initialize block commitment stream")? + .into_inner(); + + Ok(stream) + } +} + +fn make_retry_cfg( + msg: String, + span: Span, +) -> tryhard::RetryFutureConfig< + ExponentialBackoff, + impl Fn(u32, Option, &tonic::Status) -> futures::future::Ready<()>, +> { + tryhard::RetryFutureConfig::new(1024) + .exponential_backoff(Duration::from_millis(100)) + .max_delay(Duration::from_secs(2)) + .on_retry( + move |attempt: u32, next_delay: Option, error: &tonic::Status| { + let wait_duration = next_delay + .map(humantime::format_duration) + .map(tracing::field::display); + warn!( + parent: &span, + attempt, + wait_duration, + error = error as &dyn std::error::Error, + "attempt to {msg} failed; retrying after backoff", + ); + futures::future::ready(()) + }, + ) +} From d13f6f4cf2cfa8011f9cd8a04a0b630cbadd26eb Mon Sep 17 00:00:00 2001 From: itamar Date: Fri, 1 Nov 2024 16:58:58 -0400 Subject: [PATCH 5/9] add auction manager --- Cargo.lock | 1 + crates/astria-auctioneer/Cargo.toml | 1 + .../src/auction/allocation_rule.rs | 26 ++ crates/astria-auctioneer/src/auction/bid.rs | 57 ---- .../astria-auctioneer/src/auction/builder.rs | 74 +++-- .../astria-auctioneer/src/auction/manager.rs | 189 ++++++++++++ crates/astria-auctioneer/src/auction/mod.rs | 239 +++++++++------ .../astria-auctioneer/src/auctioneer/inner.rs | 45 ++- ...t_stream.rs => block_commitment_stream.rs} | 15 +- .../src/block/executed_stream.rs | 60 +++- crates/astria-auctioneer/src/block/mod.rs | 40 +-- .../src/block/optimistic_stream.rs | 11 +- crates/astria-auctioneer/src/bundle/client.rs | 134 +++++++++ crates/astria-auctioneer/src/bundle/mod.rs | 94 ++++++ crates/astria-auctioneer/src/config.rs | 4 +- crates/astria-auctioneer/src/lib.rs | 6 +- ...c_client.rs => optimistic_block_client.rs} | 6 +- .../src/optimistic_execution_client.rs | 55 +--- .../src/optimistic_executor/builder.rs | 40 ++- .../src/optimistic_executor/mod.rs | 274 +++++++++++------- crates/astria-auctioneer/src/sequencer_key.rs | 108 +++++++ 21 files changed, 1067 insertions(+), 412 deletions(-) create mode 100644 crates/astria-auctioneer/src/auction/allocation_rule.rs delete mode 100644 crates/astria-auctioneer/src/auction/bid.rs create mode 100644 crates/astria-auctioneer/src/auction/manager.rs rename crates/astria-auctioneer/src/block/{commitment_stream.rs => block_commitment_stream.rs} (71%) create mode 100644 crates/astria-auctioneer/src/bundle/client.rs create mode 100644 crates/astria-auctioneer/src/bundle/mod.rs rename crates/astria-auctioneer/src/{sequencer_grpc_client.rs => optimistic_block_client.rs} (93%) create mode 100644 crates/astria-auctioneer/src/sequencer_key.rs diff --git a/Cargo.lock b/Cargo.lock index 8cdab5e129..5d33c6200b 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/allocation_rule.rs b/crates/astria-auctioneer/src/auction/allocation_rule.rs new file mode 100644 index 0000000000..1b617c60f7 --- /dev/null +++ b/crates/astria-auctioneer/src/auction/allocation_rule.rs @@ -0,0 +1,26 @@ +use super::Bundle; + +pub(super) struct FirstPrice { + highest_bid: Option, +} + +impl FirstPrice { + pub(super) fn new() -> Self { + Self { + highest_bid: None, + } + } + + pub(crate) fn bid(&mut self, bundle: Bundle) -> bool { + if bundle.bid() > self.highest_bid.as_ref().map_or(0, |b| b.bid()) { + self.highest_bid = Some(bundle); + true + } else { + false + } + } + + pub(crate) fn highest_bid(self) -> Option { + self.highest_bid + } +} diff --git a/crates/astria-auctioneer/src/auction/bid.rs b/crates/astria-auctioneer/src/auction/bid.rs deleted file mode 100644 index d123aeda0b..0000000000 --- a/crates/astria-auctioneer/src/auction/bid.rs +++ /dev/null @@ -1,57 +0,0 @@ -use astria_core::{ - generated::bundle::v1alpha1 as raw, - protocol::transaction::v1::Transaction, -}; -use astria_eyre::eyre::{ - self, -}; -use bytes::Bytes; - -// TODO: this should probably be moved to astria_core::bundle -#[derive(Debug, Clone)] -pub(crate) struct Bundle { - raw: raw::Bundle, - /// The fee that will be charged for this bundle - fee: u64, - /// The byte list of transactions fto be included. - transactions: Vec, - /// The hash of the rollup block that this bundle is based on. - prev_rollup_block_hash: Bytes, - /// The hash of the sequencer block used to derive the rollup block that this bundle is based - /// on. - base_sequencer_block_hash: Bytes, -} - -impl Bundle { - fn try_from_raw(_raw: raw::Bundle) -> eyre::Result { - unimplemented!() - // Ok(Self { - // raw, - // }) - } - - fn into_raw(self) -> raw::Bundle { - unimplemented!() - } - - pub(crate) fn into_transaction(self, _nonce: u64) -> Transaction { - unimplemented!() - } - - pub(crate) fn into_bid(self) -> Bid { - Bid::from_bundle(self) - } -} - -#[derive(Debug, Clone)] -pub(crate) struct Bid {} - -impl Bid { - fn from_bundle(_bundle: Bundle) -> Self { - unimplemented!() - } - - fn into_bundle(self) -> Bundle { - unimplemented!() - } -} diff --git a/crates/astria-auctioneer/src/auction/builder.rs b/crates/astria-auctioneer/src/auction/builder.rs index 716b123a98..89fd4839d1 100644 --- a/crates/astria-auctioneer/src/auction/builder.rs +++ b/crates/astria-auctioneer/src/auction/builder.rs @@ -1,6 +1,12 @@ use std::time::Duration; -use astria_eyre::eyre; +use astria_core::{ + generated::sequencerblock::v1::sequencer_service_client::SequencerServiceClient, + primitive::v1::{ + asset, + RollupId, + }, +}; use tokio::sync::{ mpsc, oneshot, @@ -8,10 +14,10 @@ use tokio::sync::{ use tokio_util::sync::CancellationToken; use super::{ - BundlesHandle, - Driver, + Auction, + Handle, Id, - OptimisticExecutionHandle, + SequencerKey, }; use crate::Metrics; @@ -20,56 +26,70 @@ pub(crate) struct Builder { pub(crate) shutdown_token: CancellationToken, /// The endpoint for the sequencer gRPC service used to get pending nonces - pub(crate) sequencer_grpc_endpoint: String, + pub(crate) sequencer_grpc_client: SequencerServiceClient, /// The endpoint for the sequencer ABCI service used to submit transactions - pub(crate) sequencer_abci_endpoint: String, + pub(crate) sequencer_abci_client: sequencer_client::HttpClient, /// The amount of time to wait after a commit before closing the auction for bids and /// submitting the resulting transaction pub(crate) latency_margin: Duration, /// The ID of the auction to be run pub(crate) auction_id: Id, + /// The key used to sign sequencer transactions + pub(crate) sequencer_key: SequencerKey, + /// The denomination of the fee asset used in the sequencer transactions + pub(crate) fee_asset_denomination: asset::Denom, + /// The chain ID used for sequencer transactions + pub(crate) sequencer_chain_id: String, + /// The rollup ID used for `RollupDataSubmission` with the auction result + pub(crate) rollup_id: RollupId, } impl Builder { - pub(crate) fn build(self) -> eyre::Result<(Driver, OptimisticExecutionHandle, BundlesHandle)> { + pub(crate) fn build(self) -> (Handle, Auction) { let Self { metrics, shutdown_token, - sequencer_grpc_endpoint, - sequencer_abci_endpoint, + sequencer_grpc_client, + sequencer_abci_client, latency_margin, auction_id, + fee_asset_denomination, + rollup_id, + sequencer_key, + sequencer_chain_id, } = self; let (executed_block_tx, executed_block_rx) = oneshot::channel(); let (block_commitment_tx, block_commitment_rx) = oneshot::channel(); let (reorg_tx, reorg_rx) = oneshot::channel(); // TODO: get the capacity from config or something instead of using a magic number - let (new_bids_tx, new_bids_rx) = mpsc::channel(16); + let (new_bundles_tx, new_bundles_rx) = mpsc::channel(16); - let driver = Driver { + let auction = Auction { metrics, shutdown_token, - sequencer_grpc_endpoint, - sequencer_abci_endpoint, - executed_block_rx, - block_commitment_rx, - reorg_rx, - new_bids_rx, + sequencer_grpc_client, + sequencer_abci_client, + start_processing_bids_rx: executed_block_rx, + start_timer_rx: block_commitment_rx, + abort_rx: reorg_rx, + new_bundles_rx, auction_id, latency_margin, + sequencer_key, + fee_asset_denomination, + sequencer_chain_id, + rollup_id, }; - Ok(( - driver, - OptimisticExecutionHandle { - executed_block_tx: Some(executed_block_tx), - block_commitment_tx: Some(block_commitment_tx), - reorg_tx: Some(reorg_tx), - }, - BundlesHandle { - new_bids_tx, + ( + Handle { + new_bundles_tx, + start_processing_bids_tx: Some(executed_block_tx), + start_timer_tx: Some(block_commitment_tx), + abort_tx: Some(reorg_tx), }, - )) + auction, + ) } } diff --git a/crates/astria-auctioneer/src/auction/manager.rs b/crates/astria-auctioneer/src/auction/manager.rs new file mode 100644 index 0000000000..4e7e128516 --- /dev/null +++ b/crates/astria-auctioneer/src/auction/manager.rs @@ -0,0 +1,189 @@ +use std::collections::HashMap; + +use astria_core::{ + generated::sequencerblock::v1::sequencer_service_client::SequencerServiceClient, + primitive::v1::{ + asset, + RollupId, + }, +}; +use astria_eyre::eyre::{ + self, + OptionExt as _, + WrapErr as _, +}; +use tokio_util::{ + sync::CancellationToken, + task::JoinMap, +}; +use tonic::transport::Endpoint; +use tracing::{ + info, + instrument, +}; + +use super::{ + Bundle, + Handle, + Id, + SequencerKey, +}; +use crate::flatten_result; + +pub(crate) struct Builder { + pub(crate) metrics: &'static crate::Metrics, + pub(crate) shutdown_token: CancellationToken, + + /// The gRPC endpoint for the sequencer service used by auctions. + pub(crate) sequencer_grpc_endpoint: String, + /// The ABCI endpoint for the sequencer service used by auctions. + pub(crate) sequencer_abci_endpoint: String, + /// The amount of time to run the auction timer for. + pub(crate) latency_margin: std::time::Duration, + /// The private key used to sign sequencer transactions. + pub(crate) sequencer_private_key_path: String, + /// The prefix for the address used to sign sequencer transactions + pub(crate) sequencer_address_prefix: String, + /// The denomination of the fee asset used in the sequencer transactions + pub(crate) fee_asset_denomination: asset::Denom, + /// The chain ID for sequencer transactions + pub(crate) sequencer_chain_id: String, + /// The rollup ID for the `RollupDataSubmission`s with auction results + pub(crate) rollup_id: String, +} + +impl Builder { + pub(crate) fn build(self) -> eyre::Result { + let Self { + metrics, + shutdown_token, + sequencer_grpc_endpoint, + sequencer_abci_endpoint, + latency_margin, + fee_asset_denomination, + rollup_id, + sequencer_private_key_path, + sequencer_address_prefix, + sequencer_chain_id, + } = self; + + let sequencer_key = SequencerKey::builder() + .path(sequencer_private_key_path) + .prefix(sequencer_address_prefix) + .try_build() + .wrap_err("failed to load sequencer private key")?; + info!(address = %sequencer_key.address(), "loaded sequencer signer"); + + let sequencer_grpc_uri: tonic::transport::Uri = sequencer_grpc_endpoint + .parse() + .wrap_err("failed to parse sequencer grpc endpoint as URI")?; + let sequencer_grpc_client = + SequencerServiceClient::new(Endpoint::from(sequencer_grpc_uri).connect_lazy()); + + let sequencer_abci_client = + sequencer_client::HttpClient::new(sequencer_abci_endpoint.as_str()) + .wrap_err("failed constructing sequencer abci client")?; + + let rollup_id = RollupId::from_unhashed_bytes(&rollup_id); + + Ok(Manager { + metrics, + shutdown_token, + sequencer_grpc_client, + sequencer_abci_client, + latency_margin, + running_auctions: JoinMap::new(), + auction_handles: HashMap::new(), + sequencer_key, + fee_asset_denomination, + sequencer_chain_id, + rollup_id, + }) + } +} + +pub(crate) struct Manager { + metrics: &'static crate::Metrics, + shutdown_token: CancellationToken, + sequencer_grpc_client: SequencerServiceClient, + sequencer_abci_client: sequencer_client::HttpClient, + latency_margin: std::time::Duration, + running_auctions: JoinMap>, + auction_handles: HashMap, + // TODO: hold the bundle stream here? + sequencer_key: SequencerKey, + fee_asset_denomination: asset::Denom, + sequencer_chain_id: String, + rollup_id: RollupId, +} + +impl Manager { + #[instrument(skip(self))] + pub(crate) fn new_auction(&mut self, auction_id: Id) { + let (handle, auction) = super::Builder { + metrics: self.metrics, + shutdown_token: self.shutdown_token.clone(), + sequencer_grpc_client: self.sequencer_grpc_client.clone(), + sequencer_abci_client: self.sequencer_abci_client.clone(), + latency_margin: self.latency_margin, + auction_id, + sequencer_key: self.sequencer_key.clone(), + fee_asset_denomination: self.fee_asset_denomination.clone(), + sequencer_chain_id: self.sequencer_chain_id.clone(), + rollup_id: self.rollup_id, + } + .build(); + + // spawn and save handle + self.running_auctions.spawn(auction_id, auction.run()); + self.auction_handles.insert(auction_id, handle); + } + + pub(crate) fn abort_auction(&mut self, auction_id: Id) -> eyre::Result<()> { + // TODO: this should return an option in case the auction returned before being aborted + self.auction_handles + .get_mut(&auction_id) + .ok_or_eyre("unable to get handle for the given auction")? + .abort() + .wrap_err("failed to abort auction") + } + + #[instrument(skip(self))] + pub(crate) fn start_timer(&mut self, auction_id: Id) -> eyre::Result<()> { + self.auction_handles + .get_mut(&auction_id) + .ok_or_eyre("unable to get handle for the given auction")? + .start_timer() + .wrap_err("failed to start timer") + } + + #[instrument(skip(self))] + pub(crate) fn start_processing_bids(&mut self, auction_id: Id) -> eyre::Result<()> { + self.auction_handles + .get_mut(&auction_id) + .ok_or_eyre("unable to get handle for the given auction")? + .start_processing_bids() + .wrap_err("failed to start processing bids") + } + + pub(crate) fn try_send_bundle(&mut self, auction_id: Id, bundle: Bundle) -> eyre::Result<()> { + self.auction_handles + .get_mut(&auction_id) + .ok_or_eyre("unable to get handle for the given auction")? + .try_send_bundle(bundle) + .wrap_err("failed to add bundle to auction") + } + + pub(crate) async fn join_next(&mut self) -> Option<(Id, eyre::Result<()>)> { + if let Some((auction_id, result)) = self.running_auctions.join_next().await { + // TODO: get rid of this expect? + self.auction_handles + .remove(&auction_id) + .expect("unable to get handle for the given auction"); + + Some((auction_id, flatten_result(result))) + } else { + None + } + } +} diff --git a/crates/astria-auctioneer/src/auction/mod.rs b/crates/astria-auctioneer/src/auction/mod.rs index dcbc1f8eaa..af89c3a517 100644 --- a/crates/astria-auctioneer/src/auction/mod.rs +++ b/crates/astria-auctioneer/src/auction/mod.rs @@ -1,18 +1,27 @@ -mod bid; mod builder; use std::time::Duration; -use astria_core::protocol::transaction::v1::Transaction; +use allocation_rule::FirstPrice; +use astria_core::{ + generated::sequencerblock::v1::{ + sequencer_service_client::SequencerServiceClient, + GetPendingNonceRequest, + }, + primitive::v1::{ + asset, + RollupId, + }, + protocol::transaction::v1::Transaction, +}; use astria_eyre::eyre::{ self, eyre, Context, -}; -use bid::{ - Bid, - Bundle, + OptionExt as _, }; pub(crate) use builder::Builder; +use sequencer_client::Address; +use telemetry::display::base64; use tokio::{ select, sync::{ @@ -21,10 +30,24 @@ use tokio::{ }, }; use tokio_util::sync::CancellationToken; +use tracing::{ + info, + instrument, + warn, + Instrument, + Span, +}; +use tryhard::backoff_strategies::ExponentialBackoff; + +use crate::{ + bundle::Bundle, + sequencer_key::SequencerKey, + Metrics, +}; -use crate::Metrics; +pub(crate) mod manager; -#[derive(Hash, Eq, PartialEq, Clone, Copy)] +#[derive(Hash, Eq, PartialEq, Clone, Copy, Debug)] pub(crate) struct Id([u8; 32]); impl Id { @@ -33,50 +56,45 @@ impl Id { } } -struct Auction { - highest_bid: Option, +impl AsRef<[u8]> for Id { + fn as_ref(&self) -> &[u8] { + &self.0 + } } -impl Auction { - fn new() -> Self { - Self { - highest_bid: None, - } - } +pub(crate) use manager::Manager; - fn bid(&mut self, _bid: Bundle) -> bool { - // save the bid if its higher than self.highest_bid - unimplemented!() - } +mod allocation_rule; - fn winner(self) -> Bundle { - unimplemented!() - } +pub(crate) struct Handle { + start_processing_bids_tx: Option>, + start_timer_tx: Option>, + abort_tx: Option>, + new_bundles_tx: mpsc::Sender, } -pub(crate) struct OptimisticExecutionHandle { - executed_block_tx: Option>, - block_commitment_tx: Option>, - reorg_tx: Option>, -} +impl Handle { + pub(crate) fn abort(&mut self) -> eyre::Result<()> { + let _ = self + .abort_tx + .take() + .expect("should only send reorg signal to a given auction once"); -impl OptimisticExecutionHandle { - pub(crate) async fn send_bundle(&self) -> eyre::Result<()> { - unimplemented!() + Ok(()) } - pub(crate) fn executed_block(&mut self) -> eyre::Result<()> { + pub(crate) fn start_processing_bids(&mut self) -> eyre::Result<()> { let _ = self - .executed_block_tx + .start_processing_bids_tx .take() .expect("should only send executed signal to a given auction once") .send(()); Ok(()) } - pub(crate) fn block_commitment(&mut self) -> eyre::Result<()> { + pub(crate) fn start_timer(&mut self) -> eyre::Result<()> { let _ = self - .block_commitment_tx + .start_timer_tx .take() .expect("should only send block commitment signal to a given auction once") .send(()); @@ -84,28 +102,9 @@ impl OptimisticExecutionHandle { Ok(()) } - pub(crate) fn reorg(&mut self) -> eyre::Result<()> { - let _ = self - .reorg_tx - .take() - .expect("should only send reorg signal to a given auction once"); - - Ok(()) - } -} - -pub(crate) struct BundlesHandle { - new_bids_tx: mpsc::Sender, -} - -impl BundlesHandle { - pub(crate) fn send_bundle_timeout(&mut self, bundle: Bundle) -> eyre::Result<()> { - const BUNDLE_TIMEOUT: Duration = Duration::from_millis(100); - - let bid = bundle.into_bid(); - - self.new_bids_tx - .try_send(bid) + pub(crate) fn try_send_bundle(&mut self, bundle: Bundle) -> eyre::Result<()> { + self.new_bundles_tx + .try_send(bundle) .wrap_err("bid channel full")?; Ok(()) @@ -113,36 +112,44 @@ impl BundlesHandle { } // TODO: should this be the same object as the auction? -pub(crate) struct Driver { +struct Auction { #[allow(dead_code)] metrics: &'static Metrics, shutdown_token: CancellationToken, - /// The endpoint for the sequencer's gRPC service, used for fetching pending nonces - sequencer_grpc_endpoint: String, - /// The endpoint for the sequencer's ABCI server, used for submitting transactions - sequencer_abci_endpoint: String, + /// The sequencer's gRPC client, used for fetching pending nonces + sequencer_grpc_client: SequencerServiceClient, + /// The sequencer's ABCI client, used for submitting transactions + sequencer_abci_client: sequencer_client::HttpClient, /// Channel for receiving the executed block signal to start processing bundles - executed_block_rx: oneshot::Receiver<()>, + start_processing_bids_rx: oneshot::Receiver<()>, /// Channel for receiving the block commitment signal to start the latency margin timer - block_commitment_rx: oneshot::Receiver<()>, + start_timer_rx: oneshot::Receiver<()>, /// Channel for receiving the reorg signal - reorg_rx: oneshot::Receiver<()>, + abort_rx: oneshot::Receiver<()>, /// Channel for receiving new bundles - new_bids_rx: mpsc::Receiver, + new_bundles_rx: mpsc::Receiver, /// The time between receiving a block commitment 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_denomination: asset::Denom, + /// The chain ID used for sequencer transactions + sequencer_chain_id: String, + /// Rollup ID to submit the auction result to + rollup_id: RollupId, } -impl Driver { +impl Auction { 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 mut auction: Option = None; + let mut 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! { @@ -150,7 +157,7 @@ impl Driver { () = self.shutdown_token.cancelled() => break Err(eyre!("received shutdown signal")), - signal = &mut self.reorg_rx => { + signal = &mut self.abort_rx => { match signal { Ok(()) => { break Err(eyre!("reorg signal received")) @@ -163,20 +170,19 @@ impl Driver { } // get the auction winner when the timer expires - // TODO: should this also be conditioned on auction.is_some()? this feels redundant as we only populate the timer if the auction isnt none _ = async { latency_margin_timer.as_mut().unwrap() }, if latency_margin_timer.is_some() => { - break Ok(auction.unwrap().winner()); + break Ok(allocation_rule.highest_bid()); } - signal = &mut self.executed_block_rx, if auction.is_none() => { + signal = &mut self.start_processing_bids_rx, if !auction_is_open => { if let Err(e) = signal { break Err(eyre!("exec signal channel closed")).wrap_err(e); } // set auction to open so it starts collecting bids - auction = Some(Auction::new()); + auction_is_open = true; } - signal = &mut self.block_commitment_rx, if auction.is_some() => { + signal = &mut self.start_timer_rx, if auction_is_open => { if let Err(e) = signal { break Err(eyre!("commit signal channel closed")).wrap_err(e); } @@ -190,16 +196,17 @@ impl Driver { })); } - // TODO: new bundles from the bundle stream if auction exists? - // - add the bid to the auction if executed + Some(bundle) = self.new_bundles_rx.recv(), if auction_is_open => { + if allocation_rule.bid(bundle.clone()) { + info!(auction.id = %base64(self.auction_id), bundle.bid = %bundle.bid(), "new highest bid") + } + } } - // submit the auction result to the sequencer/wait for cancellation signal - // 1. result from submit_fut if !submission.terminated() }; // await the nonce fetch result - // TODO: flatten this or get rid of the option somehow + // TODO: flatten this or get rid of the option somehow? let nonce = nonce_fetch .expect("should have received commit to exit the bid loop") .await @@ -207,18 +214,17 @@ impl Driver { .wrap_err("failed to fetch nonce")?; // handle auction result - let transaction = match auction_result { - // TODO: add signer - Ok(winner) => winner.into_transaction(nonce), - Err(e) => { - return Err(e); - } - }; + let transaction_body = auction_result + .wrap_err("")? + .ok_or_eyre("auction ended with no winning bid")? + .into_transaction_body(nonce, self.rollup_id, self.fee_asset_denomination.clone()); + + let transaction = transaction_body.sign(self.sequencer_key.signing_key()); let submission_result = select! { biased; - // TODO: should this be Ok() or something? + // TODO: should this be Ok(())? () = self.shutdown_token.cancelled() => Err(eyre!("received shutdown signal")), // submit the transaction to the sequencer @@ -231,7 +237,62 @@ impl Driver { submission_result } + #[instrument(skip_all, fields(auction.id = %base64(self.auction_id), %address, err))] + async fn get_pending_nonce(&self, address: Address) -> eyre::Result { + let span = tracing::Span::current(); + let retry_cfg = make_retry_cfg("get pending nonce".into(), span); + let client = self.sequencer_grpc_client.clone(); + + let nonce = tryhard::retry_fn(|| { + let mut client = client.clone(); + let address = address.clone(); + + async move { + client + .get_pending_nonce(GetPendingNonceRequest { + address: Some(address.into_raw()), + }) + .await + } + }) + .with_config(retry_cfg) + .in_current_span() + .await + .wrap_err("failed to get pending nonce")? + .into_inner() + .inner; + + Ok(nonce) + } + async fn submit_transaction(&self, _transaction: Transaction) -> eyre::Result<()> { unimplemented!() } } + +fn make_retry_cfg( + msg: String, + span: Span, +) -> tryhard::RetryFutureConfig< + ExponentialBackoff, + impl Fn(u32, Option, &tonic::Status) -> futures::future::Ready<()>, +> { + tryhard::RetryFutureConfig::new(1024) + .exponential_backoff(Duration::from_millis(100)) + .max_delay(Duration::from_secs(2)) + .on_retry( + move |attempt: u32, next_delay: Option, error: &tonic::Status| { + let wait_duration = next_delay + .map(humantime::format_duration) + .map(tracing::field::display); + warn!( + parent: &span, + attempt, + wait_duration, + error = error as &dyn std::error::Error, + "attempt to {msg} failed; retrying after backoff", + ); + futures::future::ready(()) + }, + ) +} diff --git a/crates/astria-auctioneer/src/auctioneer/inner.rs b/crates/astria-auctioneer/src/auctioneer/inner.rs index 52b13fd4fb..577a238c87 100644 --- a/crates/astria-auctioneer/src/auctioneer/inner.rs +++ b/crates/astria-auctioneer/src/auctioneer/inner.rs @@ -20,7 +20,8 @@ use tracing::{ }; use crate::{ - flatten, + auction, + flatten_result, optimistic_executor, Config, Metrics, @@ -51,24 +52,48 @@ impl Auctioneer { latency_margin_ms, rollup_grpc_endpoint, rollup_id, + sequencer_chain_id, + sequencer_private_key_path, + sequencer_address_prefix, + fee_asset_denomination, .. } = cfg; let mut tasks = JoinMap::new(); - let optimistic_executor = optimistic_executor::Builder { + let auctions = auction::manager::Builder { metrics, shutdown_token: shutdown_token.clone(), - sequencer_grpc_endpoint, + sequencer_grpc_endpoint: sequencer_grpc_endpoint.clone(), sequencer_abci_endpoint, - rollup_id, - optimistic_execution_grpc_endpoint: rollup_grpc_endpoint.clone(), - bundle_grpc_endpoint: rollup_grpc_endpoint.clone(), latency_margin: Duration::from_millis(latency_margin_ms), + sequencer_private_key_path, + sequencer_address_prefix, + fee_asset_denomination, + sequencer_chain_id, + rollup_id: rollup_id.clone(), } .build() - .wrap_err("failed to initialize the optimistic executor")?; - tasks.spawn(Self::OPTIMISTIC_EXECUTOR, optimistic_executor.run()); + .wrap_err("failed to initialize auction manager")?; + + let optimistic_executor = optimistic_executor::Builder { + metrics, + shutdown_token: shutdown_token.clone(), + sequencer_grpc_endpoint, + rollup_id, + rollup_grpc_endpoint, + auctions, + } + .build(); + + tasks.spawn(Self::OPTIMISTIC_EXECUTOR, async { + optimistic_executor + .startup() + .await + .wrap_err("optimistic executor startup failed")? + .run() + .await + }); Ok(Self { shutdown_token, @@ -87,7 +112,7 @@ impl Auctioneer { }, Some((name, res)) = self.tasks.join_next() => { - flatten(res) + flatten_result(res) .wrap_err_with(|| format!("task `{name}` failed")) .map(|_| "task `{name}` exited unexpectedly") } @@ -109,7 +134,7 @@ impl Auctioneer { let shutdown_loop = async { while let Some((name, res)) = self.tasks.join_next().await { let message = "task shut down"; - match flatten(res) { + match flatten_result(res) { Ok(()) => { info!(name, message) } diff --git a/crates/astria-auctioneer/src/block/commitment_stream.rs b/crates/astria-auctioneer/src/block/block_commitment_stream.rs similarity index 71% rename from crates/astria-auctioneer/src/block/commitment_stream.rs rename to crates/astria-auctioneer/src/block/block_commitment_stream.rs index 9ea3394561..ccdc0d3d08 100644 --- a/crates/astria-auctioneer/src/block/commitment_stream.rs +++ b/crates/astria-auctioneer/src/block/block_commitment_stream.rs @@ -11,8 +11,8 @@ use futures::{ StreamExt as _, }; -use super::BlockCommitment; -use crate::sequencer_grpc_client::SequencerGrpcClient; +use super::Commitment; +use crate::optimistic_block_client::OptimisticBlockClient; /// A stream for receiving committed blocks from the sequencer. pub(crate) struct BlockCommitmentStream { @@ -20,10 +20,7 @@ pub(crate) struct BlockCommitmentStream { } impl BlockCommitmentStream { - pub(crate) async fn new(sequencer_grpc_endpoint: String) -> eyre::Result { - let mut sequencer_client = SequencerGrpcClient::new(&sequencer_grpc_endpoint) - .wrap_err("failed to initialize sequencer grpc client")?; - + pub(crate) async fn connect(mut sequencer_client: OptimisticBlockClient) -> eyre::Result { let committed_stream_client = sequencer_client .get_block_commitment_stream() .await @@ -36,7 +33,7 @@ impl BlockCommitmentStream { } impl Stream for BlockCommitmentStream { - type Item = eyre::Result; + type Item = eyre::Result; fn poll_next( mut self: Pin<&mut Self>, @@ -48,8 +45,8 @@ impl Stream for BlockCommitmentStream { .commitment .ok_or_eyre("block commitment stream response did not contain block commitment")?; - let commitment = BlockCommitment::try_from_raw(raw) - .wrap_err("failed to parse raw to BlockCommitment")?; + let commitment = + Commitment::try_from_raw(raw).wrap_err("failed to parse raw to BlockCommitment")?; std::task::Poll::Ready(Some(Ok(commitment))) } diff --git a/crates/astria-auctioneer/src/block/executed_stream.rs b/crates/astria-auctioneer/src/block/executed_stream.rs index 1ac7e16038..725e8e5f5e 100644 --- a/crates/astria-auctioneer/src/block/executed_stream.rs +++ b/crates/astria-auctioneer/src/block/executed_stream.rs @@ -1,21 +1,24 @@ -use std::{ - pin::Pin, - time::Duration, -}; +use std::pin::Pin; use astria_core::{ - generated::bundle::v1alpha1::ExecuteOptimisticBlockStreamResponse, + generated::bundle::v1alpha1::{ + ExecuteOptimisticBlockStreamRequest, + ExecuteOptimisticBlockStreamResponse, + }, primitive::v1::RollupId, }; use astria_eyre::eyre::{ self, Context, + OptionExt, }; use futures::{ Stream, - StreamExt as _, + StreamExt, }; use tokio::sync::mpsc; +use tokio_stream::wrappers::ReceiverStream; +use tracing::info; use super::{ Executed, @@ -29,7 +32,6 @@ pub(crate) struct Handle { impl Handle { pub(crate) fn try_send_block_to_execute(&mut self, block: Optimistic) -> eyre::Result<()> { - // TODO: move the duration value to a const or config value? self.blocks_to_execute_tx .try_send(block) .wrap_err("failed to send block to execute")?; @@ -37,13 +39,12 @@ impl Handle { Ok(()) } } - pub(crate) struct ExecutedBlockStream { client: Pin>>, } impl ExecutedBlockStream { - pub(crate) async fn new( + pub(crate) async fn connect( rollup_id: RollupId, rollup_grpc_endpoint: String, ) -> eyre::Result<(Handle, ExecutedBlockStream)> { @@ -72,6 +73,45 @@ impl Stream for ExecutedBlockStream { mut self: Pin<&mut Self>, cx: &mut std::task::Context, ) -> std::task::Poll> { - unimplemented!() + let raw = futures::ready!(self.client.poll_next_unpin(cx)) + .ok_or_eyre("stream has been closed")? + .wrap_err("received gRPC Error")?; + + let executed_block = + Executed::try_from_raw(raw).wrap_err("failed to parse raw to Executed")?; + + std::task::Poll::Ready(Some(Ok(executed_block))) } } + +pub(crate) fn make_execution_requests_stream( + rollup_id: RollupId, +) -> ( + mpsc::Sender, + impl tonic::IntoStreamingRequest, +) { + // TODO: should the capacity be a config instead of a magic number? OPTIMISTIC_REORG_BUFFER? + // - add a metric so we can see if that becomes a problem + let (blocks_to_execute_tx, blocks_to_execute_rx) = mpsc::channel(16); + let blocks_to_execute_stream_rx = ReceiverStream::new(blocks_to_execute_rx); + + let requests = blocks_to_execute_stream_rx.filter_map(move |block: Optimistic| async move { + let base_block = block + .try_into_base_block(rollup_id) + .wrap_err("failed to create BaseBlock from FilteredSequencerBlock"); + + // skip blocks which fail to produce a BaseBlock for the given rollup_id + match base_block { + Ok(base_block) => Some(ExecuteOptimisticBlockStreamRequest { + base_block: Some(base_block), + }), + Err(e) => { + info!(error = ?e, "skipping execution of invalid block"); + + None + } + } + }); + + (blocks_to_execute_tx, requests) +} diff --git a/crates/astria-auctioneer/src/block/mod.rs b/crates/astria-auctioneer/src/block/mod.rs index e160a70e84..8b9b50cd88 100644 --- a/crates/astria-auctioneer/src/block/mod.rs +++ b/crates/astria-auctioneer/src/block/mod.rs @@ -23,7 +23,7 @@ use astria_eyre::eyre::{ use bytes::Bytes; use prost::Message as _; -pub(crate) mod commitment_stream; +pub(crate) mod block_commitment_stream; pub(crate) mod executed_stream; pub(crate) mod optimistic_stream; @@ -133,15 +133,23 @@ impl Executed { pub(crate) fn sequencer_block_hash(&self) -> [u8; 32] { self.sequencer_block_hash } + + pub(crate) fn parent_rollup_block_hash(&self) -> [u8; 32] { + self.block + .hash() + .as_ref() + .try_into() + .expect("rollup block hash must be 32 bytes") + } } #[derive(Debug, Clone)] -pub(crate) struct BlockCommitment { +pub(crate) struct Commitment { sequencer_height: u64, sequnecer_block_hash: [u8; 32], } -impl BlockCommitment { +impl Commitment { pub(crate) fn try_from_raw( raw: raw_optimistic_block::SequencerBlockCommit, ) -> eyre::Result { @@ -171,33 +179,31 @@ impl BlockCommitment { } } -pub(crate) struct CurrentBlock { +pub(crate) struct Current { optimistic: Optimistic, executed: Option, - committed: Option, + commitment: Option, } -impl CurrentBlock { - pub(crate) fn opt(optimistic_block: Optimistic) -> Self { +impl Current { + pub(crate) fn with_optimistic(optimistic_block: Optimistic) -> Self { Self { optimistic: optimistic_block, executed: None, - committed: None, + commitment: None, } } - pub(crate) fn exec(self, executed_block: Executed) -> eyre::Result { + pub(crate) fn execute(&mut self, executed_block: Executed) -> eyre::Result<()> { if executed_block.sequencer_block_hash() != self.optimistic.sequencer_block_hash() { return Err(eyre!("block hash mismatch")); } - Ok(Self { - executed: Some(executed_block), - ..self - }) + self.executed = Some(executed_block); + Ok(()) } - pub(crate) fn commit(self, block_commitment: BlockCommitment) -> eyre::Result { + pub(crate) fn commitment(&mut self, block_commitment: Commitment) -> eyre::Result<()> { if block_commitment.sequencer_block_hash() != self.optimistic.sequencer_block_hash() { return Err(eyre!("block hash mismatch")); } @@ -205,10 +211,8 @@ impl CurrentBlock { return Err(eyre!("block height mismatch")); } - Ok(Self { - committed: Some(block_commitment), - ..self - }) + self.commitment = Some(block_commitment); + Ok(()) } pub(crate) fn sequencer_block_hash(&self) -> [u8; 32] { diff --git a/crates/astria-auctioneer/src/block/optimistic_stream.rs b/crates/astria-auctioneer/src/block/optimistic_stream.rs index 9a4da71f15..e435d71ab8 100644 --- a/crates/astria-auctioneer/src/block/optimistic_stream.rs +++ b/crates/astria-auctioneer/src/block/optimistic_stream.rs @@ -15,23 +15,18 @@ use futures::{ }; use super::Optimistic; -use crate::sequencer_grpc_client::SequencerGrpcClient; +use crate::optimistic_block_client::OptimisticBlockClient; /// A stream for receiving optimistic blocks from the sequencer. pub(crate) struct OptimisticBlockStream { client: Pin>>, - // client: Pin>>>, } impl OptimisticBlockStream { - pub(crate) async fn new( + pub(crate) async fn connect( rollup_id: RollupId, - sequencer_grpc_endpoint: String, + mut sequencer_client: OptimisticBlockClient, ) -> eyre::Result { - let mut sequencer_client = SequencerGrpcClient::new(&sequencer_grpc_endpoint) - .wrap_err("failed to initialize sequencer grpc client")?; - let optimistic_stream_client = sequencer_client .get_optimistic_block_stream(rollup_id) .await diff --git a/crates/astria-auctioneer/src/bundle/client.rs b/crates/astria-auctioneer/src/bundle/client.rs new file mode 100644 index 0000000000..2adfceb26c --- /dev/null +++ b/crates/astria-auctioneer/src/bundle/client.rs @@ -0,0 +1,134 @@ +use std::{ + pin::Pin, + time::Duration, +}; + +use astria_core::generated::bundle::v1alpha1::{ + bundle_service_client::BundleServiceClient, + GetBundleStreamRequest, + GetBundleStreamResponse, +}; +use astria_eyre::eyre::{ + self, + OptionExt, + WrapErr as _, +}; +use axum::http::Uri; +use futures::{ + Stream, + StreamExt, +}; +use tonic::transport::Endpoint; +use tracing::{ + warn, + Instrument, + Span, +}; +use tryhard::backoff_strategies::ExponentialBackoff; + +use super::Bundle; + +pub(crate) struct BundleClient { + inner: BundleServiceClient, + uri: Uri, +} + +impl BundleClient { + pub(crate) fn new(rollup_uri: &str) -> eyre::Result { + let uri = rollup_uri + .parse::() + .wrap_err("failed to parse rollup_grpc_endpoint")?; + let endpoint = Endpoint::from(uri.clone()); + let client = BundleServiceClient::new(endpoint.connect_lazy()); + + Ok(Self { + inner: client, + uri, + }) + } + + pub(crate) async fn get_bundle_stream( + &mut self, + ) -> eyre::Result> { + let span = tracing::Span::current(); + let retry_cfg = make_retry_cfg("get bundle stream".into(), span); + let client = self.inner.clone(); + + let stream = tryhard::retry_fn(|| { + let mut client = client.clone(); + async move { client.get_bundle_stream(GetBundleStreamRequest {}).await } + }) + .with_config(retry_cfg) + .in_current_span() + .await + .wrap_err("failed to get bundle stream")? + .into_inner(); + + Ok(stream) + } +} + +fn make_retry_cfg( + msg: String, + span: Span, +) -> tryhard::RetryFutureConfig< + ExponentialBackoff, + impl Fn(u32, Option, &tonic::Status) -> futures::future::Ready<()>, +> { + tryhard::RetryFutureConfig::new(1024) + .exponential_backoff(Duration::from_millis(100)) + .max_delay(Duration::from_secs(2)) + .on_retry( + move |attempt: u32, next_delay: Option, error: &tonic::Status| { + let wait_duration = next_delay + .map(humantime::format_duration) + .map(tracing::field::display); + warn!( + parent: &span, + attempt, + wait_duration, + error = error as &dyn std::error::Error, + "attempt to {msg} failed; retrying after backoff", + ); + futures::future::ready(()) + }, + ) +} + +pub(crate) struct BundleStream { + client: Pin>>, +} + +impl BundleStream { + pub(crate) async fn connect(rollup_grpc_endpoint: String) -> eyre::Result { + let mut client = BundleClient::new(&rollup_grpc_endpoint) + .wrap_err("failed to initialize bundle service client")?; + let stream = client + .get_bundle_stream() + .await + .wrap_err("failed to get bundle stream")?; + + Ok(Self { + client: Box::pin(stream), + }) + } +} + +impl Stream for BundleStream { + type Item = eyre::Result; + + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + let raw = futures::ready!(self.client.poll_next_unpin(cx)) + .ok_or_eyre("stream has been closed")? + .wrap_err("received gRPC error")? + .bundle + .ok_or_eyre("bundle stream response did not contain bundle")?; + + let bundle = Bundle::try_from_raw(raw).wrap_err("failed to parse raw Bundle")?; + + std::task::Poll::Ready(Some(Ok(bundle))) + } +} diff --git a/crates/astria-auctioneer/src/bundle/mod.rs b/crates/astria-auctioneer/src/bundle/mod.rs new file mode 100644 index 0000000000..5758504387 --- /dev/null +++ b/crates/astria-auctioneer/src/bundle/mod.rs @@ -0,0 +1,94 @@ +use astria_core::{ + generated::bundle::v1alpha1::{ + self as raw, + }, + primitive::v1::{ + asset, + RollupId, + }, + protocol::transaction::v1::{ + action::RollupDataSubmission, + TransactionBody, + }, +}; +use astria_eyre::eyre::{ + self, + WrapErr as _, +}; +use bytes::Bytes; +pub(crate) use client::BundleStream; +use prost::Message as _; + +mod client; + +// TODO: this should probably be moved to astria_core::bundle +#[derive(Debug, Clone)] +pub(crate) struct Bundle { + /// The fee that will be charged for this bundle + fee: u64, + /// The byte list of transactions fto be included. + transactions: Vec, + /// The hash of the rollup block that this bundle is based on. + prev_rollup_block_hash: [u8; 32], + /// The hash of the sequencer block used to derive the rollup block that this bundle is based + /// on. + base_sequencer_block_hash: [u8; 32], +} + +impl Bundle { + fn try_from_raw(raw: raw::Bundle) -> eyre::Result { + let raw::Bundle { + fee, + transactions, + base_sequencer_block_hash, + prev_rollup_block_hash, + } = raw; + Ok(Self { + fee, + transactions, + prev_rollup_block_hash: prev_rollup_block_hash + .as_ref() + .try_into() + .wrap_err("invalid prev_rollup_block_hash")?, + base_sequencer_block_hash: base_sequencer_block_hash + .as_ref() + .try_into() + .wrap_err("invalid base_sequencer_block_hash")?, + }) + } + + fn into_raw(self) -> raw::Bundle { + raw::Bundle { + fee: self.fee, + transactions: self.transactions, + base_sequencer_block_hash: Bytes::copy_from_slice(&self.base_sequencer_block_hash), + prev_rollup_block_hash: Bytes::copy_from_slice(&self.prev_rollup_block_hash), + } + } + + pub(crate) fn into_transaction_body( + self, + nonce: u32, + rollup_id: RollupId, + fee_asset: asset::Denom, + ) -> TransactionBody { + let data = self.into_raw().encode_to_vec(); + + TransactionBody::builder() + .actions(vec![ + RollupDataSubmission { + rollup_id, + data: data.into(), + fee_asset, + } + .into(), + ]) + .nonce(nonce) + .try_build() + .expect("failed to build transaction body") + } + + pub(crate) fn bid(&self) -> u64 { + self.fee + } +} diff --git a/crates/astria-auctioneer/src/config.rs b/crates/astria-auctioneer/src/config.rs index ffc6da1ba7..7ef5c971f7 100644 --- a/crates/astria-auctioneer/src/config.rs +++ b/crates/astria-auctioneer/src/config.rs @@ -20,10 +20,10 @@ pub struct Config { /// The file path for the private key used to sign sequencer transactions with the auction /// results pub sequencer_private_key_path: String, - // The fee asset denomination to use for the sequnecer transactions. - pub fee_asset_denomination: asset::Denom, // The address prefix to use when constructing sequencer addresses using the signing key. pub sequencer_address_prefix: String, + // The fee asset denomination to use for the sequnecer transactions. + pub fee_asset_denomination: asset::Denom, /// The endpoint for the rollup gRPC service used for the optimistic execution and bundle /// streams pub rollup_grpc_endpoint: String, diff --git a/crates/astria-auctioneer/src/lib.rs b/crates/astria-auctioneer/src/lib.rs index 01868855db..b50e8078b7 100644 --- a/crates/astria-auctioneer/src/lib.rs +++ b/crates/astria-auctioneer/src/lib.rs @@ -4,11 +4,13 @@ mod auction; mod auctioneer; mod block; mod build_info; +mod bundle; pub mod config; pub(crate) mod metrics; +mod optimistic_block_client; mod optimistic_execution_client; mod optimistic_executor; -mod sequencer_grpc_client; +mod sequencer_key; use astria_eyre::{ eyre, @@ -21,7 +23,7 @@ pub use metrics::Metrics; pub use telemetry; use tokio::task::JoinError; -fn flatten(res: Result, JoinError>) -> eyre::Result { +fn flatten_result(res: Result, JoinError>) -> eyre::Result { match res { Ok(Ok(val)) => Ok(val), Ok(Err(err)) => Err(err).wrap_err("task returned with error"), diff --git a/crates/astria-auctioneer/src/sequencer_grpc_client.rs b/crates/astria-auctioneer/src/optimistic_block_client.rs similarity index 93% rename from crates/astria-auctioneer/src/sequencer_grpc_client.rs rename to crates/astria-auctioneer/src/optimistic_block_client.rs index 6b2a9253be..7a2cacc96d 100644 --- a/crates/astria-auctioneer/src/sequencer_grpc_client.rs +++ b/crates/astria-auctioneer/src/optimistic_block_client.rs @@ -29,12 +29,12 @@ use tryhard::backoff_strategies::ExponentialBackoff; /// Wraps the gRPC client for the Sequencer service that wraps client calls with `tryhard`. #[derive(Debug, Clone)] -pub(crate) struct SequencerGrpcClient { +pub(crate) struct OptimisticBlockClient { inner: OptimisticBlockServiceClient, uri: Uri, } -impl SequencerGrpcClient { +impl OptimisticBlockClient { pub(crate) fn new(sequencer_uri: &str) -> eyre::Result { let uri = sequencer_uri .parse::() @@ -49,6 +49,7 @@ impl SequencerGrpcClient { }) } + // TODO: this should probably be separated from the tryhard logic and put in an extension trait #[instrument(skip_all, fields( uri = %self.uri, %rollup_id, @@ -78,6 +79,7 @@ impl SequencerGrpcClient { Ok(stream) } + // TODO: this should probably be separated from the tryhard logic and put in an extension trait #[instrument(skip_all, fields( uri = %self.uri, err, diff --git a/crates/astria-auctioneer/src/optimistic_execution_client.rs b/crates/astria-auctioneer/src/optimistic_execution_client.rs index e4972ea093..d836a41451 100644 --- a/crates/astria-auctioneer/src/optimistic_execution_client.rs +++ b/crates/astria-auctioneer/src/optimistic_execution_client.rs @@ -3,7 +3,6 @@ use std::time::Duration; use astria_core::{ generated::bundle::v1alpha1::{ optimistic_execution_service_client::OptimisticExecutionServiceClient, - ExecuteOptimisticBlockStreamRequest, ExecuteOptimisticBlockStreamResponse, }, primitive::v1::RollupId, @@ -12,41 +11,24 @@ use astria_eyre::eyre::{ self, Context, }; -use futures::Stream; use tokio::sync::mpsc; -use tokio_stream::{ - wrappers::ReceiverStream, - StreamExt as _, -}; use tonic::transport::{ Channel, Endpoint, Uri, }; use tracing::{ + instrument, warn, Instrument, Span, }; use tryhard::backoff_strategies::ExponentialBackoff; -use crate::block; - -struct OptimisitcBlocksToExecuteStream { - opts: ReceiverStream, -} - -impl Stream for OptimisitcBlocksToExecuteStream { - type Item = eyre::Result; - - fn poll_next( - self: std::pin::Pin<&mut Self>, - _cx: &mut std::task::Context, - ) -> std::task::Poll> { - // opts.next - unimplemented!("get an opt block if possible. otherwise return pending?"); - } -} +use crate::block::{ + self, + executed_stream::make_execution_requests_stream, +}; pub(crate) struct OptimisticExecutionClient { inner: OptimisticExecutionServiceClient, @@ -69,6 +51,11 @@ impl OptimisticExecutionClient { }) } + #[instrument(skip_all, fields( + uri = %self.uri, + %rollup_id, + err, + ))] pub(crate) async fn execute_optimistic_block_stream( &mut self, rollup_id: RollupId, @@ -83,29 +70,11 @@ impl OptimisticExecutionClient { let (stream, opt_tx) = tryhard::retry_fn(|| { let mut client = client.clone(); - // create request stream - let (opts_to_exec_tx, opts_to_exec_rx) = mpsc::channel(16); - let opts = ReceiverStream::new(opts_to_exec_rx); - let rollup_id = rollup_id.clone(); - - let requests = opts.map(move |opt: block::Optimistic| { - let base_block = opt - .try_into_base_block(rollup_id) - .wrap_err("failed to create BaseBlock from filtered_sequencer_block") - // TODO: get rid of this unwrap so we can handle blocks with no transactions. - // - instead of opts.map(), i should onyl create exec requests for blocks with - // transactions in them. - // - moving this into a domain specific stream will help clear up the logic - .unwrap(); - - ExecuteOptimisticBlockStreamRequest { - base_block: Some(base_block), - } - }); + let (blocks_to_execute_tx, requests) = make_execution_requests_stream(rollup_id); async move { let stream = client.execute_optimistic_block_stream(requests).await?; - Ok((stream, opts_to_exec_tx)) + Ok((stream, blocks_to_execute_tx)) } }) .with_config(retry_cfg) diff --git a/crates/astria-auctioneer/src/optimistic_executor/builder.rs b/crates/astria-auctioneer/src/optimistic_executor/builder.rs index 25ac901b4c..8dce746aa0 100644 --- a/crates/astria-auctioneer/src/optimistic_executor/builder.rs +++ b/crates/astria-auctioneer/src/optimistic_executor/builder.rs @@ -1,53 +1,47 @@ -use std::time::Duration; - use astria_core::primitive::v1::RollupId; -use astria_eyre::eyre; use tokio_util::sync::CancellationToken; -use super::OptimisticExecutor; -use crate::Metrics; +use super::Startup; +use crate::{ + auction, + Metrics, +}; pub(crate) struct Builder { pub(crate) metrics: &'static Metrics, pub(crate) shutdown_token: CancellationToken, /// The endpoint for the sequencer gRPC service used for the optimistic block stream pub(crate) sequencer_grpc_endpoint: String, - /// The endpoint for the sequencer ABCI service used to submit the auction winner transaction - pub(crate) sequencer_abci_endpoint: String, + /// The file path for the private key used to sign sequencer transactions with the auction + /// results /// The rollup ID for the filtered optimistic block stream pub(crate) rollup_id: String, /// The endpoint for the rollup's optimistic execution gRPC service - pub(crate) optimistic_execution_grpc_endpoint: String, - /// The endpoint for the rollup's bundle gRPC service - pub(crate) bundle_grpc_endpoint: String, - /// The amount of time to wait after a commit before closing the auction - pub(crate) latency_margin: Duration, + pub(crate) rollup_grpc_endpoint: String, + /// Manager for ongoing auctions + pub(crate) auctions: auction::Manager, } impl Builder { - pub(crate) fn build(self) -> eyre::Result { + pub(crate) fn build(self) -> Startup { let Self { metrics, shutdown_token, sequencer_grpc_endpoint, - sequencer_abci_endpoint, rollup_id, - optimistic_execution_grpc_endpoint, - bundle_grpc_endpoint, - latency_margin, + rollup_grpc_endpoint, + auctions, } = self; let rollup_id = RollupId::from_unhashed_bytes(&rollup_id); - Ok(OptimisticExecutor { + Startup { metrics, shutdown_token, sequencer_grpc_endpoint, - sequencer_abci_endpoint, rollup_id, - rollup_grpc_endpoint: optimistic_execution_grpc_endpoint, - bundle_grpc_endpoint, - latency_margin, - }) + rollup_grpc_endpoint, + auctions, + } } } diff --git a/crates/astria-auctioneer/src/optimistic_executor/mod.rs b/crates/astria-auctioneer/src/optimistic_executor/mod.rs index cc7b1d3fbc..5fde8be404 100644 --- a/crates/astria-auctioneer/src/optimistic_executor/mod.rs +++ b/crates/astria-auctioneer/src/optimistic_executor/mod.rs @@ -1,89 +1,122 @@ mod builder; -use std::{ - collections::HashMap, - time::Duration, -}; +use std::time::Duration; -use astria_core::primitive::v1::RollupId; +use astria_core::primitive::v1::{ + asset, + RollupId, +}; use astria_eyre::eyre::{ self, OptionExt, WrapErr as _, }; -use block::CurrentBlock; pub(crate) use builder::Builder; use telemetry::display::base64; use tokio::select; use tokio_stream::StreamExt as _; -use tokio_util::{ - sync::CancellationToken, - task::JoinMap, -}; +use tokio_util::sync::CancellationToken; use tracing::{ error, info, + instrument, }; -use super::optimistic_execution_client::OptimisticExecutionClient; use crate::{ auction, block::{ self, - commitment_stream::BlockCommitmentStream, + block_commitment_stream::BlockCommitmentStream, executed_stream::ExecutedBlockStream, optimistic_stream::OptimisticBlockStream, }, - flatten, + bundle::{ + Bundle, + BundleStream, + }, + optimistic_block_client::OptimisticBlockClient, }; -pub(crate) struct OptimisticExecutor { +pub(crate) struct Startup { #[allow(dead_code)] metrics: &'static crate::Metrics, shutdown_token: CancellationToken, sequencer_grpc_endpoint: String, - sequencer_abci_endpoint: String, rollup_id: RollupId, rollup_grpc_endpoint: String, - bundle_grpc_endpoint: String, - latency_margin: Duration, + auctions: auction::Manager, } -impl OptimisticExecutor { - pub(crate) async fn run(self) -> eyre::Result<()> { - let mut optimistic_stream = - OptimisticBlockStream::new(self.rollup_id, self.sequencer_grpc_endpoint.clone()) +impl Startup { + pub(crate) async fn startup(self) -> eyre::Result { + let Self { + metrics, + shutdown_token, + sequencer_grpc_endpoint, + rollup_id, + rollup_grpc_endpoint, + auctions, + } = self; + + let sequencer_client = OptimisticBlockClient::new(&sequencer_grpc_endpoint) + .wrap_err("failed to initialize sequencer grpc client")?; + // TODO: have a connect streams helper? + let mut optimistic_blocks = + OptimisticBlockStream::connect(rollup_id, sequencer_client.clone()) .await .wrap_err("failed to initialize optimsitic block stream")?; - let mut block_commitment_stream = - BlockCommitmentStream::new(self.sequencer_grpc_endpoint.clone()) - .await - .wrap_err("failed to initialize block commitment stream")?; + let block_commitments = BlockCommitmentStream::connect(sequencer_client) + .await + .wrap_err("failed to initialize block commitment stream")?; - let (mut exec_stream_handle, mut executed_block_stream) = - ExecutedBlockStream::new(self.rollup_id, self.rollup_grpc_endpoint.clone()) + let (blocks_to_execute_handle, executed_blocks) = + ExecutedBlockStream::connect(rollup_id, rollup_grpc_endpoint.clone()) .await .wrap_err("failed to initialize executed block stream")?; + let bundle_stream = BundleStream::connect(rollup_grpc_endpoint) + .await + .wrap_err("failed to initialize bundle stream")?; // let bundle_stream = BundleServiceClient::new(bundle_service_grpc_url) // .wrap_err("failed to initialize bundle service grpc client")?; - // maybe just make this a fused future `auction_fut` - let mut auction_futs: JoinMap> = JoinMap::new(); - let mut auction_oe_handles: HashMap = - HashMap::new(); - let mut auction_bundle_handles: HashMap = - HashMap::new(); - - let optimistic_block = optimistic_stream + let optimistic_block = optimistic_blocks .next() .await .ok_or_eyre("optimistic stream closed during startup?")? .wrap_err("failed to get optimistic block during startup")?; - let mut curr_block = Some(CurrentBlock::opt(optimistic_block)); + let current_block = block::Current::with_optimistic(optimistic_block); + + Ok(Running { + metrics, + shutdown_token, + optimistic_blocks, + block_commitments, + executed_blocks, + blocks_to_execute_handle, + bundle_stream, + auctions, + current_block, + }) + } +} + +pub(crate) struct Running { + metrics: &'static crate::Metrics, + shutdown_token: CancellationToken, + optimistic_blocks: OptimisticBlockStream, + block_commitments: BlockCommitmentStream, + executed_blocks: ExecutedBlockStream, + blocks_to_execute_handle: block::executed_stream::Handle, + bundle_stream: BundleStream, + auctions: auction::Manager, + current_block: block::Current, +} - let reason = { +impl Running { + pub(crate) async fn run(mut self) -> eyre::Result<()> { + let reason: eyre::Result<&str> = { loop { select! { biased; @@ -91,96 +124,35 @@ impl OptimisticExecutor { break Ok("received shutdown signal"); }, - Some((_id, res)) = auction_futs.join_next() => { - // TODO: fix this - break flatten(res) - .wrap_err_with(|| "auction failed for block {id}") - .map(|_| "auction {id} failed"); + Some((id, res)) = self.auctions.join_next() => { + // TODO: why doesnt this use `id` + res.wrap_err_with(|| "auction failed for block {id}").map(|_| "auction {id} failed")?; }, - Some(res) = optimistic_stream.next() => { - // move into self.process_optimistic_block() or somethingg + Some(res) = self.optimistic_blocks.next() => { let optimistic_block = res.wrap_err("failed to get optimistic block")?; - // reorg by shutting down current auction fut, dumping the oneshot (this is the - // state transition) and replace the current block with the optimistic block - let auction_id = auction::Id::from_sequencer_block_hash(optimistic_block.sequencer_block_hash()); - if let Some(curr_block) = curr_block { - let handle = auction_oe_handles.get_mut(&auction_id).ok_or_eyre("unable to get handle for current auction")?; - handle.reorg()?; - - info!( - // TODO: is this how we display block hashes? - optimistic_block.sequencer_block_hash = %base64(optimistic_block.sequencer_block_hash()), - current_block.sequencer_block_hash = %base64(curr_block.sequencer_block_hash()), - "replacing current block with optimistic block"); - - }; - curr_block = Some(CurrentBlock::opt(optimistic_block.clone())); - - // create and run the auction fut and save the its handles - let (auction_driver, optimistic_execution_handle, bundles_handle) = auction::Builder { - metrics: self.metrics, - shutdown_token: self.shutdown_token.clone(), - sequencer_grpc_endpoint: self.sequencer_grpc_endpoint.clone(), - sequencer_abci_endpoint: self.sequencer_abci_endpoint.clone(), - latency_margin: self.latency_margin, - auction_id, - }.build().wrap_err("failed to build auction")?; - - // TODO: clean this up? - auction_futs.spawn(auction_id, auction_driver.run()); - auction_oe_handles.insert(auction_id, optimistic_execution_handle); - auction_bundle_handles.insert(auction_id, bundles_handle); - - // forward the optimistic block to the rollup's optimistic execution server - exec_stream_handle - .try_send_block_to_execute(optimistic_block) - .wrap_err("failed to send optimistic block for execution")?; + self.optimistic_block_handler(optimistic_block).wrap_err("failed to handle optimistic block")?; }, - Some(res) = block_commitment_stream.next() => { + Some(res) = self.block_commitments.next() => { let block_commitment = res.wrap_err("failed to get block commitment")?; - if let Some(block) = curr_block { - curr_block = Some(block.commit(block_commitment).wrap_err("state transition failure")?); - } + self.block_commitment_handler(block_commitment).wrap_err("failed to handle block commitment")?; - // 2. send commit signal to the auction so it will start the timer - if let Some(hash) = curr_block.as_ref().map(|block| block.sequencer_block_hash()) { - let auction_id = auction::Id::from_sequencer_block_hash(hash); - let handle = auction_oe_handles.get_mut(&auction_id).ok_or_eyre("unable to get handle for current auction")?; - handle.block_commitment()?; - } }, - // 3. block commitment stream - Some(res) = executed_block_stream.next() => { - // 1. update curr block state (react if invalid state transition? maybe by dropping - // the block and its auction) + Some(res) = self.executed_blocks.next() => { let executed_block = res.wrap_err("failed to get executed block")?; - let next_block = curr_block - .map(|block| block.exec(executed_block).wrap_err("state transition failure")) - .expect("should only receive executed blocks after an optimistic block has been received")?; - // TODO: probably can get rid of this - curr_block = Some(next_block); - - // 2. send executed signal to the auction so it will start pulling bundles - if let Some(hash) = curr_block.as_ref().map(|block| block.sequencer_block_hash()) { - let auction_id = auction::Id::from_sequencer_block_hash(hash); - let handle = auction_oe_handles.get_mut(&auction_id).ok_or_eyre("unable to get handle for current auction")?; - handle.executed_block()?; - } + self.executed_block_handler(executed_block).wrap_err("failed to handle executed block")?; } + Some(res) = self.bundle_stream.next() => { + let bundle = res.wrap_err("failed to get bundle")?; - // 2. forward bundles from bundle stream into the correct auction fut - // - bundles will build up in the channel into the auction until the executed signal is - // sent to the auction fut. so if backpressure builds up here, i.e. bids arrive way - // before execution, we can decide how to react here. - // for example, we can drop all the bundles that are stuck in the channel and log a warning, - // or we can kill the auction for that given block + self.bundle_handler(bundle).wrap_err("failed to handle bundle")?; + } } } }; @@ -190,7 +162,85 @@ impl OptimisticExecutor { Err(err) => error!(%err, "shutting down due to error"), }; - self.shutdown(); + self.shutdown().await; + Ok(()) + } + + #[instrument(skip(self), fields(auction.old_id = %base64(self.current_block.sequencer_block_hash())))] + fn optimistic_block_handler( + &mut self, + optimistic_block: block::Optimistic, + ) -> eyre::Result<()> { + let old_auction_id = + auction::Id::from_sequencer_block_hash(self.current_block.sequencer_block_hash()); + self.auctions + .abort_auction(old_auction_id) + .wrap_err("failed to abort auction")?; + + info!( + // TODO: is this how we display block hashes? + optimistic_block.sequencer_block_hash = %base64(optimistic_block.sequencer_block_hash()), + "received optimistic block, aborting old auction and starting new auction" + ); + + self.current_block = block::Current::with_optimistic(optimistic_block.clone()); + let new_auction_id = + auction::Id::from_sequencer_block_hash(self.current_block.sequencer_block_hash()); + self.auctions.new_auction(new_auction_id); + + // create and run the auction fut and save the its handles + // forward the optimistic block to the rollup's optimistic execution server + // TODO: don't want to exit on this, just complain with a log and skip the block or smth + self.blocks_to_execute_handle + .try_send_block_to_execute(optimistic_block) + .wrap_err("failed to send optimistic block for execution")?; + + Ok(()) + } + + #[instrument(skip(self), fields(auction.id = %base64(self.current_block.sequencer_block_hash())))] + fn block_commitment_handler( + &mut self, + block_commitment: block::Commitment, + ) -> eyre::Result<()> { + // TODO: handle this with a log instead of exiting + self.current_block + .commitment(block_commitment) + .wrap_err("failed to set block commitment")?; + + let auction_id = + auction::Id::from_sequencer_block_hash(self.current_block.sequencer_block_hash()); + self.auctions + .start_timer(auction_id) + .wrap_err("failed to start timer")?; + + Ok(()) + } + + #[instrument(skip(self), fields(auction.id = %base64(self.current_block.sequencer_block_hash())))] + fn executed_block_handler(&mut self, executed_block: block::Executed) -> eyre::Result<()> { + // TODO: handle this with a log instead of exiting + self.current_block + .execute(executed_block) + .wrap_err("failed to set block to executed")?; + + let auction_id = + auction::Id::from_sequencer_block_hash(self.current_block.sequencer_block_hash()); + self.auctions + .start_processing_bids(auction_id) + .wrap_err("failed to start processing bids")?; + + Ok(()) + } + + #[instrument(skip(self), fields(auction.id = %base64(self.current_block.sequencer_block_hash())))] + fn bundle_handler(&mut self, bundle: Bundle) -> eyre::Result<()> { + let auction_id = + auction::Id::from_sequencer_block_hash(self.current_block.sequencer_block_hash()); + self.auctions + .try_send_bundle(auction_id, bundle) + .wrap_err("failed to submit bundle to auction")?; + Ok(()) } diff --git a/crates/astria-auctioneer/src/sequencer_key.rs b/crates/astria-auctioneer/src/sequencer_key.rs new file mode 100644 index 0000000000..463f77fc27 --- /dev/null +++ b/crates/astria-auctioneer/src/sequencer_key.rs @@ -0,0 +1,108 @@ +use std::{ + fs, + path::{ + Path, + PathBuf, + }, +}; + +use astria_core::{ + crypto::SigningKey, + primitive::v1::Address, +}; +use astria_eyre::eyre::{ + self, + bail, + eyre, + Context, +}; + +#[derive(Clone)] +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 + } +} From 5545a94889bc29253dacdb6091ca5a285279ed0e Mon Sep 17 00:00:00 2001 From: itamar Date: Mon, 4 Nov 2024 19:14:52 -0500 Subject: [PATCH 6/9] transaction submission logic --- .../src/auction/allocation_rule.rs | 3 + .../astria-auctioneer/src/auction/manager.rs | 33 ++- crates/astria-auctioneer/src/auction/mod.rs | 209 ++++++++++++------ .../astria-auctioneer/src/auctioneer/inner.rs | 1 - ...mitment_stream.rs => commitment_stream.rs} | 4 + .../src/block/executed_stream.rs | 16 +- crates/astria-auctioneer/src/block/mod.rs | 48 ++-- .../src/block/optimistic_stream.rs | 27 ++- crates/astria-auctioneer/src/bundle/client.rs | 2 + crates/astria-auctioneer/src/bundle/mod.rs | 14 ++ .../src/optimistic_executor/mod.rs | 103 +++++---- 11 files changed, 307 insertions(+), 153 deletions(-) rename crates/astria-auctioneer/src/block/{block_commitment_stream.rs => commitment_stream.rs} (89%) diff --git a/crates/astria-auctioneer/src/auction/allocation_rule.rs b/crates/astria-auctioneer/src/auction/allocation_rule.rs index 1b617c60f7..77877a9efc 100644 --- a/crates/astria-auctioneer/src/auction/allocation_rule.rs +++ b/crates/astria-auctioneer/src/auction/allocation_rule.rs @@ -11,6 +11,9 @@ impl FirstPrice { } } + /// Submit a bundle with a bid. + /// + /// Returns `true` if the bid is accepted as the highest bid. pub(crate) fn bid(&mut self, bundle: Bundle) -> bool { if bundle.bid() > self.highest_bid.as_ref().map_or(0, |b| b.bid()) { self.highest_bid = Some(bundle); diff --git a/crates/astria-auctioneer/src/auction/manager.rs b/crates/astria-auctioneer/src/auction/manager.rs index 4e7e128516..18aa48c7e7 100644 --- a/crates/astria-auctioneer/src/auction/manager.rs +++ b/crates/astria-auctioneer/src/auction/manager.rs @@ -122,7 +122,7 @@ impl Manager { pub(crate) fn new_auction(&mut self, auction_id: Id) { let (handle, auction) = super::Builder { metrics: self.metrics, - shutdown_token: self.shutdown_token.clone(), + shutdown_token: self.shutdown_token.child_token(), sequencer_grpc_client: self.sequencer_grpc_client.clone(), sequencer_abci_client: self.sequencer_abci_client.clone(), latency_margin: self.latency_margin, @@ -141,29 +141,40 @@ impl Manager { pub(crate) fn abort_auction(&mut self, auction_id: Id) -> eyre::Result<()> { // TODO: this should return an option in case the auction returned before being aborted - self.auction_handles + let handle = self + .auction_handles .get_mut(&auction_id) - .ok_or_eyre("unable to get handle for the given auction")? - .abort() - .wrap_err("failed to abort auction") + .ok_or_eyre("unable to get handle for the given auction")?; + + handle.abort().expect("should only abort once per auction"); + Ok(()) } #[instrument(skip(self))] pub(crate) fn start_timer(&mut self, auction_id: Id) -> eyre::Result<()> { - self.auction_handles + let handle = self + .auction_handles .get_mut(&auction_id) - .ok_or_eyre("unable to get handle for the given auction")? + .ok_or_eyre("unable to get handle for the given auction")?; + + handle .start_timer() - .wrap_err("failed to start timer") + .expect("should only start timer once per auction"); + + Ok(()) } #[instrument(skip(self))] pub(crate) fn start_processing_bids(&mut self, auction_id: Id) -> eyre::Result<()> { - self.auction_handles + let handle = self + .auction_handles .get_mut(&auction_id) - .ok_or_eyre("unable to get handle for the given auction")? + .ok_or_eyre("unable to get handle for the given auction")?; + + handle .start_processing_bids() - .wrap_err("failed to start processing bids") + .expect("should only start processing bids once per auction"); + Ok(()) } pub(crate) fn try_send_bundle(&mut self, auction_id: Id, bundle: Bundle) -> eyre::Result<()> { diff --git a/crates/astria-auctioneer/src/auction/mod.rs b/crates/astria-auctioneer/src/auction/mod.rs index af89c3a517..6238e1a17a 100644 --- a/crates/astria-auctioneer/src/auction/mod.rs +++ b/crates/astria-auctioneer/src/auction/mod.rs @@ -17,10 +17,15 @@ use astria_eyre::eyre::{ self, eyre, Context, + ContextCompat, OptionExt as _, }; pub(crate) use builder::Builder; -use sequencer_client::Address; +use sequencer_client::{ + tendermint_rpc::endpoint::broadcast::tx_sync, + Address, + SequencerClientExt, +}; use telemetry::display::base64; use tokio::{ select, @@ -31,13 +36,13 @@ use tokio::{ }; use tokio_util::sync::CancellationToken; use tracing::{ + debug, + error, info, instrument, warn, Instrument, - Span, }; -use tryhard::backoff_strategies::ExponentialBackoff; use crate::{ bundle::Bundle, @@ -66,7 +71,14 @@ pub(crate) use manager::Manager; mod allocation_rule; +enum Command { + StartProcessingBids, + StartTimer, + Abort, +} + pub(crate) struct Handle { + commands_tx: mpsc::Sender, start_processing_bids_tx: Option>, start_timer_tx: Option>, abort_tx: Option>, @@ -78,7 +90,7 @@ impl Handle { let _ = self .abort_tx .take() - .expect("should only send reorg signal to a given auction once"); + .ok_or_eyre("should only send reorg signal to a given auction once")?; Ok(()) } @@ -87,7 +99,7 @@ impl Handle { let _ = self .start_processing_bids_tx .take() - .expect("should only send executed signal to a given auction once") + .ok_or_eyre("should only send executed signal to a given auction once")? .send(()); Ok(()) } @@ -96,7 +108,7 @@ impl Handle { let _ = self .start_timer_tx .take() - .expect("should only send block commitment signal to a given auction once") + .ok_or_eyre("should only send block commitment signal to a given auction once")? .send(()); Ok(()) @@ -112,7 +124,7 @@ impl Handle { } // TODO: should this be the same object as the auction? -struct Auction { +pub(crate) struct Auction { #[allow(dead_code)] metrics: &'static Metrics, shutdown_token: CancellationToken, @@ -176,7 +188,7 @@ impl Auction { signal = &mut self.start_processing_bids_rx, if !auction_is_open => { if let Err(e) = signal { - break Err(eyre!("exec signal channel closed")).wrap_err(e); + break Err(e).wrap_err("exec signal channel closed"); } // set auction to open so it starts collecting bids auction_is_open = true; @@ -184,100 +196,97 @@ impl Auction { signal = &mut self.start_timer_rx, if auction_is_open => { if let Err(e) = signal { - break Err(eyre!("commit signal channel closed")).wrap_err(e); + break Err(e).wrap_err("commit signal channel closed"); } // set the timer latency_margin_timer = Some(tokio::time::sleep(self.latency_margin)); - // TODO: also want to fetch the pending nonce here (we wait for commit because we want the pending nonce from after the commit) - nonce_fetch = Some(tokio::task::spawn(async { - // TODO: fetch the pending nonce using the sequencer client with tryhard - Ok(0) + let client = self.sequencer_grpc_client.clone(); + let address = self.sequencer_key.address().clone(); + + // we wait for commit because we want the pending nonce from after the commit + // TODO: fix lifetime issue with passing metrics here? + nonce_fetch = Some(tokio::task::spawn(async move { + get_pending_nonce(client, address).await })); } Some(bundle) = self.new_bundles_rx.recv(), if auction_is_open => { if allocation_rule.bid(bundle.clone()) { - info!(auction.id = %base64(self.auction_id), bundle.bid = %bundle.bid(), "new highest bid") + info!( + auction.id = %base64(self.auction_id), + bundle.bid = %bundle.bid(), + "received new highest bid" + ); + } else { + debug!( + auction.id = %base64(self.auction_id), + bundle.bid = %bundle.bid(), + "received bid lower than current highest bid, discarding" + ); } } } }; - // await the nonce fetch result + // TODO: separate the rest of this to a different object, e.g. AuctionResult? // TODO: flatten this or get rid of the option somehow? + // await the nonce fetch result let nonce = nonce_fetch - .expect("should have received commit to exit the bid loop") + .expect( + "should have received commit and fetched pending nonce before exiting the auction \ + loop", + ) .await - .wrap_err("task failed")? + .wrap_err("get_pending_nonce task failed")? .wrap_err("failed to fetch nonce")?; - // handle auction result + // serialize, sign and submit to the sequencer let transaction_body = auction_result - .wrap_err("")? + .wrap_err("auction failed unexpectedly")? .ok_or_eyre("auction ended with no winning bid")? - .into_transaction_body(nonce, self.rollup_id, self.fee_asset_denomination.clone()); + .into_transaction_body( + nonce, + self.rollup_id, + self.fee_asset_denomination.clone(), + self.sequencer_chain_id, + ); let transaction = transaction_body.sign(self.sequencer_key.signing_key()); let submission_result = select! { biased; - // TODO: should this be Ok(())? - () = self.shutdown_token.cancelled() => Err(eyre!("received shutdown signal")), - - // submit the transaction to the sequencer - result = self.submit_transaction(transaction) => { - // TODO: handle submission failure better? - result + // TODO: should this be Ok(())? or Ok("received shutdown signal")? + () = self.shutdown_token.cancelled() => Err(eyre!("received shutdown signal during auction result submission")), + + result = submit_transaction(self.sequencer_abci_client.clone(), transaction, self.metrics) => { + // TODO: how to handle submission failure better? + match result { + Ok(resp) => { + // TODO: handle failed submission instead of just logging the result + info!(auction.id = %base64(self.auction_id), auction.result = %resp.log, "auction result submitted to sequencer"); + Ok(()) + }, + Err(e) => { + error!(auction.id = %base64(self.auction_id), err = %e, "failed to submit auction result to sequencer"); + Err(e).wrap_err("failed to submit auction result to sequencer") + }, + } } }; - submission_result } - - #[instrument(skip_all, fields(auction.id = %base64(self.auction_id), %address, err))] - async fn get_pending_nonce(&self, address: Address) -> eyre::Result { - let span = tracing::Span::current(); - let retry_cfg = make_retry_cfg("get pending nonce".into(), span); - let client = self.sequencer_grpc_client.clone(); - - let nonce = tryhard::retry_fn(|| { - let mut client = client.clone(); - let address = address.clone(); - - async move { - client - .get_pending_nonce(GetPendingNonceRequest { - address: Some(address.into_raw()), - }) - .await - } - }) - .with_config(retry_cfg) - .in_current_span() - .await - .wrap_err("failed to get pending nonce")? - .into_inner() - .inner; - - Ok(nonce) - } - - async fn submit_transaction(&self, _transaction: Transaction) -> eyre::Result<()> { - unimplemented!() - } } -fn make_retry_cfg( - msg: String, - span: Span, -) -> tryhard::RetryFutureConfig< - ExponentialBackoff, - impl Fn(u32, Option, &tonic::Status) -> futures::future::Ready<()>, -> { - tryhard::RetryFutureConfig::new(1024) +#[instrument(skip_all, fields(%address, err))] +async fn get_pending_nonce( + client: SequencerServiceClient, + address: Address, +) -> eyre::Result { + let span = tracing::Span::current(); + let retry_cfg = tryhard::RetryFutureConfig::new(1024) .exponential_backoff(Duration::from_millis(100)) .max_delay(Duration::from_secs(2)) .on_retry( @@ -290,9 +299,69 @@ fn make_retry_cfg( attempt, wait_duration, error = error as &dyn std::error::Error, - "attempt to {msg} failed; retrying after backoff", + "attempt to get pending nonce failed; retrying after backoff", + ); + futures::future::ready(()) + }, + ); + + let nonce = tryhard::retry_fn(|| { + let mut client = client.clone(); + let address = address.clone(); + + async move { + client + .get_pending_nonce(GetPendingNonceRequest { + address: Some(address.into_raw()), + }) + .await + } + }) + .with_config(retry_cfg) + .in_current_span() + .await + .wrap_err("failed to get pending nonce")? + .into_inner() + .inner; + + Ok(nonce) +} + +async fn submit_transaction( + client: sequencer_client::HttpClient, + transaction: Transaction, + _metrics: &'static Metrics, +) -> eyre::Result { + let span = tracing::Span::current(); + let retry_cfg = tryhard::RetryFutureConfig::new(1024) + .exponential_backoff(Duration::from_millis(100)) + .max_delay(Duration::from_secs(2)) + .on_retry( + move |attempt: u32, + next_delay: Option, + error: &sequencer_client::extension_trait::Error| { + let wait_duration = next_delay + .map(humantime::format_duration) + .map(tracing::field::display); + warn!( + parent: &span, + attempt, + wait_duration, + error = error as &dyn std::error::Error, + "attempt to submit transaction failed; retrying after backoff", ); futures::future::ready(()) }, - ) + ); + + tryhard::retry_fn(|| { + let client = client.clone(); + let transaction = transaction.clone(); + + async move { client.submit_transaction_sync(transaction).await } + }) + .with_config(retry_cfg) + .in_current_span() + .await + .wrap_err("failed to submit transaction") } diff --git a/crates/astria-auctioneer/src/auctioneer/inner.rs b/crates/astria-auctioneer/src/auctioneer/inner.rs index 577a238c87..f34d44bf49 100644 --- a/crates/astria-auctioneer/src/auctioneer/inner.rs +++ b/crates/astria-auctioneer/src/auctioneer/inner.rs @@ -36,7 +36,6 @@ pub(super) struct Auctioneer { } impl Auctioneer { - const AUCTION_DRIVER: &'static str = "auction_driver"; const OPTIMISTIC_EXECUTOR: &'static str = "optimistic_executor"; const _BUNDLE_COLLECTOR: &'static str = "bundle_collector"; diff --git a/crates/astria-auctioneer/src/block/block_commitment_stream.rs b/crates/astria-auctioneer/src/block/commitment_stream.rs similarity index 89% rename from crates/astria-auctioneer/src/block/block_commitment_stream.rs rename to crates/astria-auctioneer/src/block/commitment_stream.rs index ccdc0d3d08..fd68bfe5c0 100644 --- a/crates/astria-auctioneer/src/block/block_commitment_stream.rs +++ b/crates/astria-auctioneer/src/block/commitment_stream.rs @@ -10,6 +10,8 @@ use futures::{ Stream, StreamExt as _, }; +use telemetry::display::base64; +use tracing::debug; use super::Commitment; use crate::optimistic_block_client::OptimisticBlockClient; @@ -48,6 +50,8 @@ impl Stream for BlockCommitmentStream { let commitment = Commitment::try_from_raw(raw).wrap_err("failed to parse raw to BlockCommitment")?; + debug!(block_commitment.sequencer_block_hash = %base64(&commitment.sequencer_block_hash()), "received block commitment"); + std::task::Poll::Ready(Some(Ok(commitment))) } } diff --git a/crates/astria-auctioneer/src/block/executed_stream.rs b/crates/astria-auctioneer/src/block/executed_stream.rs index 725e8e5f5e..6edc0c9579 100644 --- a/crates/astria-auctioneer/src/block/executed_stream.rs +++ b/crates/astria-auctioneer/src/block/executed_stream.rs @@ -16,9 +16,13 @@ use futures::{ Stream, StreamExt, }; +use telemetry::display::base64; use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; -use tracing::info; +use tracing::{ + debug, + error, +}; use super::{ Executed, @@ -80,6 +84,12 @@ impl Stream for ExecutedBlockStream { let executed_block = Executed::try_from_raw(raw).wrap_err("failed to parse raw to Executed")?; + debug!( + executed_block.rollup_block_hash = %base64(executed_block.rollup_block_hash()), + executed_block.sequencer_block_hash = %base64(executed_block.sequencer_block_hash()), + "received block execution result" + ); + std::task::Poll::Ready(Some(Ok(executed_block))) } } @@ -100,13 +110,13 @@ pub(crate) fn make_execution_requests_stream( .try_into_base_block(rollup_id) .wrap_err("failed to create BaseBlock from FilteredSequencerBlock"); - // skip blocks which fail to produce a BaseBlock for the given rollup_id + // skip blocks which fail to decode the transactions? match base_block { Ok(base_block) => Some(ExecuteOptimisticBlockStreamRequest { base_block: Some(base_block), }), Err(e) => { - info!(error = ?e, "skipping execution of invalid block"); + error!(error = ?e, "skipping execution of invalid block"); None } diff --git a/crates/astria-auctioneer/src/block/mod.rs b/crates/astria-auctioneer/src/block/mod.rs index 8b9b50cd88..7deaa9bf79 100644 --- a/crates/astria-auctioneer/src/block/mod.rs +++ b/crates/astria-auctioneer/src/block/mod.rs @@ -18,12 +18,11 @@ use astria_eyre::eyre::{ self, eyre, Context, - OptionExt, }; use bytes::Bytes; use prost::Message as _; -pub(crate) mod block_commitment_stream; +pub(crate) mod commitment_stream; pub(crate) mod executed_stream; pub(crate) mod optimistic_stream; @@ -55,10 +54,9 @@ impl Optimistic { }) } - pub(crate) fn into_raw(self) -> raw_sequencer_block::FilteredSequencerBlock { - self.filtered_sequencer_block.into_raw() - } - + /// Converts this [`Optimistic`] into a [`BaseBlock`] for the given `rollup_id`. + /// If there are no transactions for the given `rollup_id`, this will return a `BaseBlock`. + // TODO: add typed errors here? pub(crate) fn try_into_base_block( self, rollup_id: RollupId, @@ -70,19 +68,19 @@ impl Optimistic { .. } = self.filtered_sequencer_block.into_parts(); - let serialized_transactions = rollup_transactions + let maybe_serialized_transactions = rollup_transactions .swap_remove(&rollup_id) - .ok_or_eyre( - "FilteredSequencerBlock does not contain transactions for the given rollup", - )? - .into_parts(); - - let transactions = serialized_transactions - .transactions - .into_iter() - .map(raw_sequencer_block::RollupData::decode) - .collect::>() - .wrap_err("failed to decode RollupData")?; + .map(|transactions| transactions.into_parts()); + + let transactions = + maybe_serialized_transactions.map_or(Ok(vec![]), |serialized_transactions| { + serialized_transactions + .transactions + .into_iter() + .map(raw_sequencer_block::RollupData::decode) + .collect::>() + .wrap_err("failed to decode RollupData") + })?; let timestamp = Some(convert_tendermint_time_to_protobuf_timestamp(header.time())); @@ -135,6 +133,14 @@ impl Executed { } pub(crate) fn parent_rollup_block_hash(&self) -> [u8; 32] { + self.block + .parent_block_hash() + .as_ref() + .try_into() + .expect("rollup block hash must be 32 bytes") + } + + pub(crate) fn rollup_block_hash(&self) -> [u8; 32] { self.block .hash() .as_ref() @@ -218,4 +224,10 @@ impl Current { pub(crate) fn sequencer_block_hash(&self) -> [u8; 32] { self.optimistic.sequencer_block_hash() } + + pub(crate) fn rollup_parent_block_hash(&self) -> Option<[u8; 32]> { + self.executed + .as_ref() + .map(|executed| executed.parent_rollup_block_hash()) + } } diff --git a/crates/astria-auctioneer/src/block/optimistic_stream.rs b/crates/astria-auctioneer/src/block/optimistic_stream.rs index e435d71ab8..cd052f4d42 100644 --- a/crates/astria-auctioneer/src/block/optimistic_stream.rs +++ b/crates/astria-auctioneer/src/block/optimistic_stream.rs @@ -13,11 +13,14 @@ use futures::{ Stream, StreamExt as _, }; +use telemetry::display::base64; +use tracing::debug; use super::Optimistic; use crate::optimistic_block_client::OptimisticBlockClient; /// A stream for receiving optimistic blocks from the sequencer. +// TODO: pin project these instead pub(crate) struct OptimisticBlockStream { client: Pin>>, } @@ -33,7 +36,6 @@ impl OptimisticBlockStream { .wrap_err("failed to stream optimistic blocks")?; Ok(OptimisticBlockStream { - // client, client: Box::pin(optimistic_stream_client), }) } @@ -46,18 +48,25 @@ impl Stream for OptimisticBlockStream { mut self: Pin<&mut Self>, cx: &mut std::task::Context, ) -> std::task::Poll> { - let raw = futures::ready!(self.client.poll_next_unpin(cx)) - // TODO: better error messages here - .ok_or_eyre("stream has been closed")? - .wrap_err("received gRPC error")? - .block - .ok_or_eyre( - "optimsitic block stream response did not contain filtered sequencer block", - )?; + // TODO: return none when stream is closed + let rsp = match futures::ready!(self.client.poll_next_unpin(cx)) { + Some(raw) => raw, + None => return std::task::Poll::Ready(None), + }; + + // TODO: filter_map on these errors + let raw = rsp.wrap_err("received gRPC error")?.block.ok_or_eyre( + "optimsitic block stream response did not contain filtered sequencer block", + )?; let optimistic_block = Optimistic::try_from_raw(raw).wrap_err("failed to parse raw to Optimistic")?; + debug!( + optimistic_block.sequencer_block_hash = %base64(optimistic_block.sequencer_block_hash()), + "received optimistic block from sequencer" + ); + std::task::Poll::Ready(Some(Ok(optimistic_block))) } } diff --git a/crates/astria-auctioneer/src/bundle/client.rs b/crates/astria-auctioneer/src/bundle/client.rs index 2adfceb26c..50de5bfdf2 100644 --- a/crates/astria-auctioneer/src/bundle/client.rs +++ b/crates/astria-auctioneer/src/bundle/client.rs @@ -20,6 +20,7 @@ use futures::{ }; use tonic::transport::Endpoint; use tracing::{ + instrument, warn, Instrument, Span, @@ -47,6 +48,7 @@ impl BundleClient { }) } + #[instrument(skip_all, fields(uri = %self.uri))] pub(crate) async fn get_bundle_stream( &mut self, ) -> eyre::Result> { diff --git a/crates/astria-auctioneer/src/bundle/mod.rs b/crates/astria-auctioneer/src/bundle/mod.rs index 5758504387..a5f79b3597 100644 --- a/crates/astria-auctioneer/src/bundle/mod.rs +++ b/crates/astria-auctioneer/src/bundle/mod.rs @@ -29,6 +29,7 @@ pub(crate) struct Bundle { /// The byte list of transactions fto be included. transactions: Vec, /// The hash of the rollup block that this bundle is based on. + // TODO: rename this to `parent_rollup_block_hash` to match execution api prev_rollup_block_hash: [u8; 32], /// The hash of the sequencer block used to derive the rollup block that this bundle is based /// on. @@ -71,9 +72,13 @@ impl Bundle { nonce: u32, rollup_id: RollupId, fee_asset: asset::Denom, + chain_id: String, ) -> TransactionBody { let data = self.into_raw().encode_to_vec(); + // TODO: sign the bundle data and put it in a `SignedBundle` message or something (need to + // update protos for this) + TransactionBody::builder() .actions(vec![ RollupDataSubmission { @@ -84,6 +89,7 @@ impl Bundle { .into(), ]) .nonce(nonce) + .chain_id(chain_id) .try_build() .expect("failed to build transaction body") } @@ -91,4 +97,12 @@ impl Bundle { pub(crate) fn bid(&self) -> u64 { self.fee } + + pub(crate) fn prev_rollup_block_hash(&self) -> [u8; 32] { + self.prev_rollup_block_hash + } + + pub(crate) fn base_sequencer_block_hash(&self) -> [u8; 32] { + self.base_sequencer_block_hash + } } diff --git a/crates/astria-auctioneer/src/optimistic_executor/mod.rs b/crates/astria-auctioneer/src/optimistic_executor/mod.rs index 5fde8be404..04e9221e56 100644 --- a/crates/astria-auctioneer/src/optimistic_executor/mod.rs +++ b/crates/astria-auctioneer/src/optimistic_executor/mod.rs @@ -1,17 +1,12 @@ -mod builder; - -use std::time::Duration; +//! - [ ] mention backpressure here -use astria_core::primitive::v1::{ - asset, - RollupId, -}; +use astria_core::primitive::v1::RollupId; use astria_eyre::eyre::{ self, + eyre, OptionExt, WrapErr as _, }; -pub(crate) use builder::Builder; use telemetry::display::base64; use tokio::select; use tokio_stream::StreamExt as _; @@ -26,7 +21,7 @@ use crate::{ auction, block::{ self, - block_commitment_stream::BlockCommitmentStream, + commitment_stream::BlockCommitmentStream, executed_stream::ExecutedBlockStream, optimistic_stream::OptimisticBlockStream, }, @@ -37,6 +32,18 @@ use crate::{ optimistic_block_client::OptimisticBlockClient, }; +mod builder; +pub(crate) use builder::Builder; + +macro_rules! break_for_closed_stream { + ($stream_res:expr, $msg:expr) => { + match $stream_res { + Some(val) => val, + None => break Err(eyre!($msg)), + } + }; +} + pub(crate) struct Startup { #[allow(dead_code)] metrics: &'static crate::Metrics, @@ -60,7 +67,6 @@ impl Startup { let sequencer_client = OptimisticBlockClient::new(&sequencer_grpc_endpoint) .wrap_err("failed to initialize sequencer grpc client")?; - // TODO: have a connect streams helper? let mut optimistic_blocks = OptimisticBlockStream::connect(rollup_id, sequencer_client.clone()) .await @@ -78,8 +84,6 @@ impl Startup { let bundle_stream = BundleStream::connect(rollup_grpc_endpoint) .await .wrap_err("failed to initialize bundle stream")?; - // let bundle_stream = BundleServiceClient::new(bundle_service_grpc_url) - // .wrap_err("failed to initialize bundle service grpc client")?; let optimistic_block = optimistic_blocks .next() @@ -117,6 +121,7 @@ pub(crate) struct Running { impl Running { pub(crate) async fn run(mut self) -> eyre::Result<()> { let reason: eyre::Result<&str> = { + // This is a long running loop. Errors are emitted inside the handlers. loop { select! { biased; @@ -125,33 +130,33 @@ impl Running { }, Some((id, res)) = self.auctions.join_next() => { - // TODO: why doesnt this use `id` + // TODO: this seems wrong? res.wrap_err_with(|| "auction failed for block {id}").map(|_| "auction {id} failed")?; }, - Some(res) = self.optimistic_blocks.next() => { - let optimistic_block = res.wrap_err("failed to get optimistic block")?; + res = self.optimistic_blocks.next() => { + let res = break_for_closed_stream!(res, "optimistic block stream closed"); - self.optimistic_block_handler(optimistic_block).wrap_err("failed to handle optimistic block")?; + let _ = self.handle_optimistic_block(res); }, Some(res) = self.block_commitments.next() => { - let block_commitment = res.wrap_err("failed to get block commitment")?; + let block_commitment = tri!(res.wrap_err("failed to get block commitment")); - self.block_commitment_handler(block_commitment).wrap_err("failed to handle block commitment")?; + let _ = self.handle_block_commitment(block_commitment); }, Some(res) = self.executed_blocks.next() => { let executed_block = res.wrap_err("failed to get executed block")?; - self.executed_block_handler(executed_block).wrap_err("failed to handle executed block")?; + let _ = self.handle_executed_block(executed_block); } Some(res) = self.bundle_stream.next() => { let bundle = res.wrap_err("failed to get bundle")?; - self.bundle_handler(bundle).wrap_err("failed to handle bundle")?; + let _ = self.handle_incoming_bundle(bundle); } } } @@ -162,15 +167,16 @@ impl Running { Err(err) => error!(%err, "shutting down due to error"), }; - self.shutdown().await; Ok(()) } - #[instrument(skip(self), fields(auction.old_id = %base64(self.current_block.sequencer_block_hash())))] - fn optimistic_block_handler( + #[instrument(skip(self), fields(auction.old_id = %base64(self.current_block.sequencer_block_hash())), err)] + fn handle_optimistic_block( &mut self, - optimistic_block: block::Optimistic, + optimistic_block: eyre::Result, ) -> eyre::Result<()> { + let optimistic_block = optimistic_block.wrap_err("failed receiving optimistic block")?; + let old_auction_id = auction::Id::from_sequencer_block_hash(self.current_block.sequencer_block_hash()); self.auctions @@ -188,9 +194,8 @@ impl Running { auction::Id::from_sequencer_block_hash(self.current_block.sequencer_block_hash()); self.auctions.new_auction(new_auction_id); - // create and run the auction fut and save the its handles // forward the optimistic block to the rollup's optimistic execution server - // TODO: don't want to exit on this, just complain with a log and skip the block or smth + // TODO: don't want to exit on this, just complain with a log and skip the block or smth? self.blocks_to_execute_handle .try_send_block_to_execute(optimistic_block) .wrap_err("failed to send optimistic block for execution")?; @@ -198,18 +203,16 @@ impl Running { Ok(()) } - #[instrument(skip(self), fields(auction.id = %base64(self.current_block.sequencer_block_hash())))] - fn block_commitment_handler( - &mut self, - block_commitment: block::Commitment, - ) -> eyre::Result<()> { - // TODO: handle this with a log instead of exiting + #[instrument(skip_all, fields(auction.id = %base64(self.current_block.sequencer_block_hash())), err)] + fn handle_block_commitment(&mut self, block_commitment: block::Commitment) -> eyre::Result<()> { + // TODO: handle this with a log instead of exiting? self.current_block .commitment(block_commitment) .wrap_err("failed to set block commitment")?; let auction_id = auction::Id::from_sequencer_block_hash(self.current_block.sequencer_block_hash()); + self.auctions .start_timer(auction_id) .wrap_err("failed to start timer")?; @@ -217,15 +220,16 @@ impl Running { Ok(()) } - #[instrument(skip(self), fields(auction.id = %base64(self.current_block.sequencer_block_hash())))] - fn executed_block_handler(&mut self, executed_block: block::Executed) -> eyre::Result<()> { - // TODO: handle this with a log instead of exiting + #[instrument(skip_all, fields(auction.id = %base64(self.current_block.sequencer_block_hash())))] + fn handle_executed_block(&mut self, executed_block: block::Executed) -> eyre::Result<()> { + // TODO: handle this with a log instead of exiting? self.current_block .execute(executed_block) .wrap_err("failed to set block to executed")?; let auction_id = auction::Id::from_sequencer_block_hash(self.current_block.sequencer_block_hash()); + self.auctions .start_processing_bids(auction_id) .wrap_err("failed to start processing bids")?; @@ -233,8 +237,29 @@ impl Running { Ok(()) } - #[instrument(skip(self), fields(auction.id = %base64(self.current_block.sequencer_block_hash())))] - fn bundle_handler(&mut self, bundle: Bundle) -> eyre::Result<()> { + #[instrument(skip_all, fields(auction.id = %base64(self.current_block.sequencer_block_hash())))] + fn handle_incoming_bundle(&mut self, bundle: Bundle) -> eyre::Result<()> { + // TODO: use ensure! here and provide the hashes in the error + if bundle.base_sequencer_block_hash() != self.current_block.sequencer_block_hash() { + return Err(eyre!( + "incoming bundle's {sequencer_block_hash} does not match current sequencer block \ + hash" + )); + } + + if let Some(rollup_parent_block_hash) = self.current_block.rollup_parent_block_hash() { + if bundle.prev_rollup_block_hash() != rollup_parent_block_hash { + return Err(eyre!( + "bundle's rollup parent block hash does not match current rollup parent block \ + hash" + )); + } + } else { + // TODO: should i buffer these up in the channel until the block is executed and then + // filter them in the auction if the parent hashes dont match? + return Err(eyre!("current block has not been executed yet.")); + } + let auction_id = auction::Id::from_sequencer_block_hash(self.current_block.sequencer_block_hash()); self.auctions @@ -243,8 +268,4 @@ impl Running { Ok(()) } - - async fn shutdown(self) { - self.shutdown_token.cancel(); - } } From 253488d7e85ffb8c1a6f14913b3e589887bbf5d7 Mon Sep 17 00:00:00 2001 From: itamar Date: Thu, 7 Nov 2024 18:45:27 -0500 Subject: [PATCH 7/9] doc strings and minor cleanups --- .../src/auction/allocation_rule.rs | 5 +- .../astria-auctioneer/src/auction/builder.rs | 19 +-- .../astria-auctioneer/src/auction/manager.rs | 16 +- crates/astria-auctioneer/src/auction/mod.rs | 149 ++++++++++------- .../src/block/commitment_stream.rs | 26 ++- .../src/block/executed_stream.rs | 30 ++-- crates/astria-auctioneer/src/block/mod.rs | 52 +++++- .../src/block/optimistic_stream.rs | 51 ++++-- crates/astria-auctioneer/src/bundle/mod.rs | 4 +- .../src/optimistic_executor/mod.rs | 154 +++++++++++------- 10 files changed, 319 insertions(+), 187 deletions(-) diff --git a/crates/astria-auctioneer/src/auction/allocation_rule.rs b/crates/astria-auctioneer/src/auction/allocation_rule.rs index 77877a9efc..3bae574daa 100644 --- a/crates/astria-auctioneer/src/auction/allocation_rule.rs +++ b/crates/astria-auctioneer/src/auction/allocation_rule.rs @@ -1,3 +1,5 @@ +//! The allocation rule is the mechanism by which the auction processes incoming bids and determines +//! the winner. use super::Bundle; pub(super) struct FirstPrice { @@ -23,7 +25,8 @@ impl FirstPrice { } } - pub(crate) fn highest_bid(self) -> Option { + /// Returns the winner of the auction, if one exists. + pub(crate) fn winner(self) -> Option { self.highest_bid } } diff --git a/crates/astria-auctioneer/src/auction/builder.rs b/crates/astria-auctioneer/src/auction/builder.rs index 89fd4839d1..ae5bf8464e 100644 --- a/crates/astria-auctioneer/src/auction/builder.rs +++ b/crates/astria-auctioneer/src/auction/builder.rs @@ -7,10 +7,7 @@ use astria_core::{ RollupId, }, }; -use tokio::sync::{ - mpsc, - oneshot, -}; +use tokio::sync::mpsc; use tokio_util::sync::CancellationToken; use super::{ @@ -59,10 +56,8 @@ impl Builder { sequencer_chain_id, } = self; - let (executed_block_tx, executed_block_rx) = oneshot::channel(); - let (block_commitment_tx, block_commitment_rx) = oneshot::channel(); - let (reorg_tx, reorg_rx) = oneshot::channel(); - // TODO: get the capacity from config or something instead of using a magic number + // TODO: get the capacities from config or something instead of using a magic number + let (commands_tx, commands_rx) = mpsc::channel(16); let (new_bundles_tx, new_bundles_rx) = mpsc::channel(16); let auction = Auction { @@ -70,9 +65,7 @@ impl Builder { shutdown_token, sequencer_grpc_client, sequencer_abci_client, - start_processing_bids_rx: executed_block_rx, - start_timer_rx: block_commitment_rx, - abort_rx: reorg_rx, + commands_rx, new_bundles_rx, auction_id, latency_margin, @@ -85,9 +78,7 @@ impl Builder { ( Handle { new_bundles_tx, - start_processing_bids_tx: Some(executed_block_tx), - start_timer_tx: Some(block_commitment_tx), - abort_tx: Some(reorg_tx), + commands_tx, }, auction, ) diff --git a/crates/astria-auctioneer/src/auction/manager.rs b/crates/astria-auctioneer/src/auction/manager.rs index 18aa48c7e7..e85f6f3f1f 100644 --- a/crates/astria-auctioneer/src/auction/manager.rs +++ b/crates/astria-auctioneer/src/auction/manager.rs @@ -1,3 +1,5 @@ +/// The auction Manager is responsible for managing running auction futures and their +/// associated handles. use std::collections::HashMap; use astria_core::{ @@ -110,7 +112,6 @@ pub(crate) struct Manager { latency_margin: std::time::Duration, running_auctions: JoinMap>, auction_handles: HashMap, - // TODO: hold the bundle stream here? sequencer_key: SequencerKey, fee_asset_denomination: asset::Denom, sequencer_chain_id: String, @@ -140,13 +141,14 @@ impl Manager { } pub(crate) fn abort_auction(&mut self, auction_id: Id) -> eyre::Result<()> { - // TODO: this should return an option in case the auction returned before being aborted let handle = self .auction_handles .get_mut(&auction_id) .ok_or_eyre("unable to get handle for the given auction")?; - handle.abort().expect("should only abort once per auction"); + handle + .try_abort() + .wrap_err("failed to send command to abort auction")?; Ok(()) } @@ -159,7 +161,7 @@ impl Manager { handle .start_timer() - .expect("should only start timer once per auction"); + .wrap_err("failed to send command to start timer to auction")?; Ok(()) } @@ -173,7 +175,7 @@ impl Manager { handle .start_processing_bids() - .expect("should only start processing bids once per auction"); + .wrap_err("failed to send command to start processing bids")?; Ok(()) } @@ -187,10 +189,10 @@ impl Manager { pub(crate) async fn join_next(&mut self) -> Option<(Id, eyre::Result<()>)> { if let Some((auction_id, result)) = self.running_auctions.join_next().await { - // TODO: get rid of this expect? + // TODO: get rid of this expect somehow self.auction_handles .remove(&auction_id) - .expect("unable to get handle for the given auction"); + .expect("handle should always exist for running auction"); Some((auction_id, flatten_result(result))) } else { diff --git a/crates/astria-auctioneer/src/auction/mod.rs b/crates/astria-auctioneer/src/auction/mod.rs index 6238e1a17a..8bedeadb17 100644 --- a/crates/astria-auctioneer/src/auction/mod.rs +++ b/crates/astria-auctioneer/src/auction/mod.rs @@ -1,3 +1,42 @@ +//! The Auction is repsonsible for running an auction for a given block. An auction advances through +//! the following states, controlled via the `commands_rx` channel received: +//! 1. The auction is initialized but not yet started (i.e. no commands have been received). +//! 2. After receiving a `Command::StartProcessingBids`, the auction will start processing incoming +//! bundles from `new_bundles_rx`. +//! 3. After receiving a `Command::StartTimer`, the auction will set a timer for `latency_margin` +//! (denominated in milliseconds). +//! 4. Once the timer expires, the auction will choose a winner based on its `AllocationRule` and +//! submit it to the sequencer. +//! +//! ## Aborting an Auction +//! The auction may also be aborted at any point before the timer expires by receiving a +//! `Command::Abort`. This will cause the auction to return early without submitting a winner, +//! effectively discarding any bundles that were processed. +//! This is used for leveraging optimsitic execution, running an auction for block data that has +//! been proposed in the sequencer network's CometBFT but not yet finalized. +//! We assume that most proposals are adopted in CometBFT, allowing us to buy a few hundred +//! milliseconds before they are finalized. However, if multiple rounds of voting invalidate a +//! proposal, we can abort the auction and avoid submitting a potentially invalid bundle. In this +//! case, the auction will abort and a new one will be created for the newly processed proposal +//! (which will be received by the Optimistic Executor via the optimistic block stream). +//! +//! ## Auction Result +//! The auction result is a `Bundle` that is signed by the Auctioneer and submitted to the rollup +//! via the sequencer. The rollup defines a trusted Auctioneer address that it allows to submit +//! bundles, and thus must verify the Auctioneer's signature over this bundle. +//! +//! Since the sequencer does not include the transaction signer's metadata with the `RollupData` +//! events that it saves in its block data, the Auctioneer must include this metadata in its +//! `RollupDataSubmission`s. This is done by wrapping the winning `Bundle` object in an +//! `AuctionResult` object, which is then serialized into the `RollupDataSubmission`. +//! +//! ## Submission to Sequencer +//! The auction will submit the winning bundle to the sequencer via the `broadcast_tx_sync` ABCI(?) +//! endpoint. +//! In order to save time on fetching a nonce, the auction will fetch the next pending nonce as soon +//! as it received the signal to start the timer. This corresponds to the sequencer block being +//! committed, thus providing the latest pending nonce. + mod builder; use std::time::Duration; @@ -17,7 +56,6 @@ use astria_eyre::eyre::{ self, eyre, Context, - ContextCompat, OptionExt as _, }; pub(crate) use builder::Builder; @@ -29,10 +67,7 @@ use sequencer_client::{ use telemetry::display::base64; use tokio::{ select, - sync::{ - mpsc, - oneshot, - }, + sync::mpsc, }; use tokio_util::sync::CancellationToken; use tracing::{ @@ -79,37 +114,32 @@ enum Command { pub(crate) struct Handle { commands_tx: mpsc::Sender, - start_processing_bids_tx: Option>, - start_timer_tx: Option>, - abort_tx: Option>, new_bundles_tx: mpsc::Sender, } impl Handle { - pub(crate) fn abort(&mut self) -> eyre::Result<()> { + pub(crate) fn try_abort(&mut self) -> eyre::Result<()> { let _ = self - .abort_tx - .take() - .ok_or_eyre("should only send reorg signal to a given auction once")?; + .commands_tx + .try_send(Command::Abort) + .wrap_err("unable to send abort command to auction")?; Ok(()) } pub(crate) fn start_processing_bids(&mut self) -> eyre::Result<()> { let _ = self - .start_processing_bids_tx - .take() - .ok_or_eyre("should only send executed signal to a given auction once")? - .send(()); + .commands_tx + .try_send(Command::StartProcessingBids) + .wrap_err("unable to send command to start processing bids to auction")?; Ok(()) } pub(crate) fn start_timer(&mut self) -> eyre::Result<()> { let _ = self - .start_timer_tx - .take() - .ok_or_eyre("should only send block commitment signal to a given auction once")? - .send(()); + .commands_tx + .try_send(Command::StartTimer) + .wrap_err("unable to send command to start time to auction")?; Ok(()) } @@ -123,7 +153,6 @@ impl Handle { } } -// TODO: should this be the same object as the auction? pub(crate) struct Auction { #[allow(dead_code)] metrics: &'static Metrics, @@ -133,12 +162,8 @@ pub(crate) struct Auction { sequencer_grpc_client: SequencerServiceClient, /// The sequencer's ABCI client, used for submitting transactions sequencer_abci_client: sequencer_client::HttpClient, - /// Channel for receiving the executed block signal to start processing bundles - start_processing_bids_rx: oneshot::Receiver<()>, - /// Channel for receiving the block commitment signal to start the latency margin timer - start_timer_rx: oneshot::Receiver<()>, - /// Channel for receiving the reorg signal - abort_rx: oneshot::Receiver<()>, + /// Channel for receiving commands sent via the handle + commands_rx: mpsc::Receiver, /// Channel for receiving new bundles new_bundles_rx: mpsc::Receiver, /// The time between receiving a block commitment @@ -158,6 +183,7 @@ pub(crate) struct Auction { impl Auction { pub(crate) async fn run(mut self) -> eyre::Result<()> { let mut latency_margin_timer = None; + // TODO: do we want to make this configurable to allow for more complex allocation rules? let mut allocation_rule = FirstPrice::new(); let mut auction_is_open = false; @@ -169,46 +195,40 @@ impl Auction { () = self.shutdown_token.cancelled() => break Err(eyre!("received shutdown signal")), - signal = &mut self.abort_rx => { - match signal { - Ok(()) => { - break Err(eyre!("reorg signal received")) - } - Err(_) => { - return Err(eyre!("reorg signal channel closed")); - } - } - // - } - // get the auction winner when the timer expires _ = async { latency_margin_timer.as_mut().unwrap() }, if latency_margin_timer.is_some() => { - break Ok(allocation_rule.highest_bid()); + break Ok(allocation_rule.winner()); } - signal = &mut self.start_processing_bids_rx, if !auction_is_open => { - if let Err(e) = signal { - break Err(e).wrap_err("exec signal channel closed"); - } - // set auction to open so it starts collecting bids - auction_is_open = true; - } - - signal = &mut self.start_timer_rx, if auction_is_open => { - if let Err(e) = signal { - break Err(e).wrap_err("commit signal channel closed"); + Some(cmd) = self.commands_rx.recv() => { + match cmd { + Command::Abort => { + // abort the auction early + // TODO: should this be an error? + break Err(eyre!("auction {id} received abort signal", id = base64(&self.auction_id))); + }, + Command::StartProcessingBids => { + if auction_is_open { + break Err(eyre!("auction received signal to start processing bids twice")); + } + auction_is_open = true; + }, + Command::StartTimer => { + if !auction_is_open { + break Err(eyre!("auction received signal to start timer before signal to start processing bids")); + } + + // set the timer + latency_margin_timer = Some(tokio::time::sleep(self.latency_margin)); + + // we wait for commit because we want the pending nonce from the committed block + nonce_fetch = { + let client = self.sequencer_grpc_client.clone(); + let address = self.sequencer_key.address().clone(); + Some(tokio::task::spawn(async move { get_pending_nonce(client, address, self.metrics).await })) + }; + } } - // set the timer - latency_margin_timer = Some(tokio::time::sleep(self.latency_margin)); - - let client = self.sequencer_grpc_client.clone(); - let address = self.sequencer_key.address().clone(); - - // we wait for commit because we want the pending nonce from after the commit - // TODO: fix lifetime issue with passing metrics here? - nonce_fetch = Some(tokio::task::spawn(async move { - get_pending_nonce(client, address).await - })); } Some(bundle) = self.new_bundles_rx.recv(), if auction_is_open => { @@ -284,6 +304,8 @@ impl Auction { async fn get_pending_nonce( client: SequencerServiceClient, address: Address, + // TODO: emit metrics here + #[allow(unused_variables)] metrics: &'static Metrics, ) -> eyre::Result { let span = tracing::Span::current(); let retry_cfg = tryhard::RetryFutureConfig::new(1024) @@ -330,7 +352,8 @@ async fn get_pending_nonce( async fn submit_transaction( client: sequencer_client::HttpClient, transaction: Transaction, - _metrics: &'static Metrics, + // TODO: emit metrics here + #[allow(unused_variables)] metrics: &'static Metrics, ) -> eyre::Result { let span = tracing::Span::current(); let retry_cfg = tryhard::RetryFutureConfig::new(1024) diff --git a/crates/astria-auctioneer/src/block/commitment_stream.rs b/crates/astria-auctioneer/src/block/commitment_stream.rs index fd68bfe5c0..a6c6bcd814 100644 --- a/crates/astria-auctioneer/src/block/commitment_stream.rs +++ b/crates/astria-auctioneer/src/block/commitment_stream.rs @@ -4,32 +4,36 @@ use astria_core::generated::sequencerblock::optimisticblock::v1alpha1::GetBlockC use astria_eyre::eyre::{ self, Context, - OptionExt, + OptionExt as _, }; use futures::{ Stream, StreamExt as _, }; +use pin_project_lite::pin_project; use telemetry::display::base64; use tracing::debug; use super::Commitment; use crate::optimistic_block_client::OptimisticBlockClient; -/// A stream for receiving committed blocks from the sequencer. -pub(crate) struct BlockCommitmentStream { - client: Pin>>, +pin_project! { + /// A stream for receiving committed blocks from the sequencer. + pub(crate) struct BlockCommitmentStream { + #[pin] + client: tonic::Streaming, + } } impl BlockCommitmentStream { pub(crate) async fn connect(mut sequencer_client: OptimisticBlockClient) -> eyre::Result { - let committed_stream_client = sequencer_client + let commitment_stream_client = sequencer_client .get_block_commitment_stream() .await .wrap_err("failed to stream block commitments")?; Ok(Self { - client: Box::pin(committed_stream_client), + client: commitment_stream_client, }) } } @@ -41,11 +45,15 @@ impl Stream for BlockCommitmentStream { mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { - let raw = futures::ready!(self.client.poll_next_unpin(cx)) - .ok_or_eyre("stream has been closed")? + let res = match futures::ready!(self.client.poll_next_unpin(cx)) { + Some(res) => res, + None => return std::task::Poll::Ready(None), + }; + + let raw = res .wrap_err("received gRPC error")? .commitment - .ok_or_eyre("block commitment stream response did not contain block commitment")?; + .ok_or_eyre("response did not contain block commitment")?; let commitment = Commitment::try_from_raw(raw).wrap_err("failed to parse raw to BlockCommitment")?; diff --git a/crates/astria-auctioneer/src/block/executed_stream.rs b/crates/astria-auctioneer/src/block/executed_stream.rs index 6edc0c9579..184d9f02ed 100644 --- a/crates/astria-auctioneer/src/block/executed_stream.rs +++ b/crates/astria-auctioneer/src/block/executed_stream.rs @@ -9,13 +9,13 @@ use astria_core::{ }; use astria_eyre::eyre::{ self, - Context, - OptionExt, + WrapErr as _, }; use futures::{ Stream, StreamExt, }; +use pin_project_lite::pin_project; use telemetry::display::base64; use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; @@ -43,8 +43,13 @@ impl Handle { Ok(()) } } -pub(crate) struct ExecutedBlockStream { - client: Pin>>, + +pin_project! { + /// A stream for receiving optimistic execution results from the rollup node. + pub(crate) struct ExecutedBlockStream { + #[pin] + client: tonic::Streaming, + } } impl ExecutedBlockStream { @@ -64,7 +69,7 @@ impl ExecutedBlockStream { blocks_to_execute_tx, }, Self { - client: Box::pin(executed_stream_client), + client: executed_stream_client, }, )) } @@ -77,9 +82,12 @@ impl Stream for ExecutedBlockStream { mut self: Pin<&mut Self>, cx: &mut std::task::Context, ) -> std::task::Poll> { - let raw = futures::ready!(self.client.poll_next_unpin(cx)) - .ok_or_eyre("stream has been closed")? - .wrap_err("received gRPC Error")?; + let res = match futures::ready!(self.client.poll_next_unpin(cx)) { + Some(res) => res, + None => return std::task::Poll::Ready(None), + }; + + let raw = res.wrap_err("received gRPC Error")?; let executed_block = Executed::try_from_raw(raw).wrap_err("failed to parse raw to Executed")?; @@ -100,8 +108,8 @@ pub(crate) fn make_execution_requests_stream( mpsc::Sender, impl tonic::IntoStreamingRequest, ) { - // TODO: should the capacity be a config instead of a magic number? OPTIMISTIC_REORG_BUFFER? - // - add a metric so we can see if that becomes a problem + // TODO: should this capacity be a config instead of a magic number? OPTIMISTIC_REORG_BUFFER? + // TODO: add a metric so we can see if that becomes a problem let (blocks_to_execute_tx, blocks_to_execute_rx) = mpsc::channel(16); let blocks_to_execute_stream_rx = ReceiverStream::new(blocks_to_execute_rx); @@ -116,7 +124,7 @@ pub(crate) fn make_execution_requests_stream( base_block: Some(base_block), }), Err(e) => { - error!(error = ?e, "skipping execution of invalid block"); + error!(error = %e, "skipping execution of invalid block"); None } diff --git a/crates/astria-auctioneer/src/block/mod.rs b/crates/astria-auctioneer/src/block/mod.rs index 7deaa9bf79..0fd30b99c3 100644 --- a/crates/astria-auctioneer/src/block/mod.rs +++ b/crates/astria-auctioneer/src/block/mod.rs @@ -16,11 +16,15 @@ use astria_core::{ }; use astria_eyre::eyre::{ self, + ensure, eyre, Context, }; use bytes::Bytes; use prost::Message as _; +use telemetry::display::base64; + +use crate::bundle::Bundle; pub(crate) mod commitment_stream; pub(crate) mod executed_stream; @@ -42,6 +46,7 @@ fn convert_tendermint_time_to_protobuf_timestamp( #[derive(Debug, Clone)] pub(crate) struct Optimistic { + /// The optimistic block data, filtered for a rollup id. filtered_sequencer_block: FilteredSequencerBlock, } @@ -55,7 +60,11 @@ impl Optimistic { } /// Converts this [`Optimistic`] into a [`BaseBlock`] for the given `rollup_id`. - /// If there are no transactions for the given `rollup_id`, this will return a `BaseBlock`. + /// If there are no transactions for the given `rollup_id`, this will return a `BaseBlock` + /// with no transactions. + /// + /// # Errors + /// Invalid `RollupData` included in the optimistic block data will result in an error. // TODO: add typed errors here? pub(crate) fn try_into_base_block( self, @@ -102,7 +111,9 @@ impl Optimistic { #[derive(Debug, Clone)] pub(crate) struct Executed { + /// The rollup block metadata that resulted from executing the optimistic block. block: execution::v1::Block, + /// The hash of the sequencer block that was executed optimistically. sequencer_block_hash: [u8; 32], } @@ -151,7 +162,9 @@ impl Executed { #[derive(Debug, Clone)] pub(crate) struct Commitment { + /// The height of the sequencer block that was committed. sequencer_height: u64, + /// The hash of the sequencer block that was committed. sequnecer_block_hash: [u8; 32], } @@ -169,13 +182,6 @@ impl Commitment { }) } - pub(crate) fn into_raw(self) -> raw_optimistic_block::SequencerBlockCommit { - raw_optimistic_block::SequencerBlockCommit { - height: self.sequencer_height, - block_hash: Bytes::copy_from_slice(&self.sequnecer_block_hash), - } - } - pub(crate) fn sequencer_block_hash(&self) -> [u8; 32] { self.sequnecer_block_hash } @@ -192,6 +198,7 @@ pub(crate) struct Current { } impl Current { + /// Creates a new `Current` with the given `optimistic_block`. pub(crate) fn with_optimistic(optimistic_block: Optimistic) -> Self { Self { optimistic: optimistic_block, @@ -200,6 +207,9 @@ impl Current { } } + /// Updates the `Current` with the given `executed_block`. + /// This will fail if the `executed_block` does not match the `optimistic_block`'s sequencer + /// block hash. pub(crate) fn execute(&mut self, executed_block: Executed) -> eyre::Result<()> { if executed_block.sequencer_block_hash() != self.optimistic.sequencer_block_hash() { return Err(eyre!("block hash mismatch")); @@ -209,6 +219,9 @@ impl Current { Ok(()) } + /// Updates the `Current` with the given `block_commitment`. + /// This will fail if the `block_commitment` does not match the `optimistic_block`'s sequencer + /// block hash. pub(crate) fn commitment(&mut self, block_commitment: Commitment) -> eyre::Result<()> { if block_commitment.sequencer_block_hash() != self.optimistic.sequencer_block_hash() { return Err(eyre!("block hash mismatch")); @@ -230,4 +243,27 @@ impl Current { .as_ref() .map(|executed| executed.parent_rollup_block_hash()) } + + /// Ensures that the given `bundle` is valid for the current block state. + pub(crate) fn ensure_bundle_is_valid(&self, bundle: &Bundle) -> eyre::Result<()> { + ensure!( + bundle.base_sequencer_block_hash() == self.sequencer_block_hash(), + "incoming bundle's sequencer block hash {bundle_hash} does not match current \ + sequencer block hash {current_hash}", + bundle_hash = base64(bundle.base_sequencer_block_hash()), + current_hash = base64(self.sequencer_block_hash()) + ); + + if let Some(rollup_parent_block_hash) = self.rollup_parent_block_hash() { + ensure!( + bundle.parent_rollup_block_hash() == rollup_parent_block_hash, + "bundle's rollup parent block hash {bundle_hash} does not match current rollup \ + parent block hash {current_hash}", + bundle_hash = base64(bundle.parent_rollup_block_hash()), + current_hash = base64(rollup_parent_block_hash) + ); + } + + Ok(()) + } } diff --git a/crates/astria-auctioneer/src/block/optimistic_stream.rs b/crates/astria-auctioneer/src/block/optimistic_stream.rs index cd052f4d42..6bbb995422 100644 --- a/crates/astria-auctioneer/src/block/optimistic_stream.rs +++ b/crates/astria-auctioneer/src/block/optimistic_stream.rs @@ -7,28 +7,48 @@ use astria_core::{ use astria_eyre::eyre::{ self, Context, - OptionExt, + OptionExt as _, }; use futures::{ Stream, StreamExt as _, }; +use pin_project_lite::pin_project; use telemetry::display::base64; use tracing::debug; -use super::Optimistic; +use super::{ + executed_stream, + Optimistic, +}; use crate::optimistic_block_client::OptimisticBlockClient; -/// A stream for receiving optimistic blocks from the sequencer. -// TODO: pin project these instead -pub(crate) struct OptimisticBlockStream { - client: Pin>>, +pin_project! { + /// A stream for receiving optimistic blocks from the sequencer. + /// Blocks received optimistically will be checked for validity and a clone will + /// be sent to the rollup's optimistic execution serivce before returning them + /// for further processing. + /// + /// ## Backpressure + /// While blocks are forwarded using an `mpsc` channel, we only receive incoming + /// optimistic blocks from the sequencer when CometBFT proposals are processed. + /// Multiple optimsitic blocks will be received in a short amount of time only in + /// the event that CometBFT receives multiple proposals within a single block's slot. + /// We assume this is relatively rare and that under normal operations a block will + /// be sent optimistically once per slot (~2 seconds). + pub(crate) struct OptimisticBlockStream { + #[pin] + client: tonic::Streaming, + #[pin] + execution_handle: executed_stream::Handle, + } } impl OptimisticBlockStream { pub(crate) async fn connect( rollup_id: RollupId, mut sequencer_client: OptimisticBlockClient, + execution_handle: executed_stream::Handle, ) -> eyre::Result { let optimistic_stream_client = sequencer_client .get_optimistic_block_stream(rollup_id) @@ -36,7 +56,8 @@ impl OptimisticBlockStream { .wrap_err("failed to stream optimistic blocks")?; Ok(OptimisticBlockStream { - client: Box::pin(optimistic_stream_client), + client: optimistic_stream_client, + execution_handle, }) } } @@ -48,16 +69,15 @@ impl Stream for OptimisticBlockStream { mut self: Pin<&mut Self>, cx: &mut std::task::Context, ) -> std::task::Poll> { - // TODO: return none when stream is closed - let rsp = match futures::ready!(self.client.poll_next_unpin(cx)) { + let res = match futures::ready!(self.client.poll_next_unpin(cx)) { Some(raw) => raw, None => return std::task::Poll::Ready(None), }; - // TODO: filter_map on these errors - let raw = rsp.wrap_err("received gRPC error")?.block.ok_or_eyre( - "optimsitic block stream response did not contain filtered sequencer block", - )?; + let raw = res + .wrap_err("received gRPC error")? + .block + .ok_or_eyre("response did not contain filtered sequencer block")?; let optimistic_block = Optimistic::try_from_raw(raw).wrap_err("failed to parse raw to Optimistic")?; @@ -67,6 +87,11 @@ impl Stream for OptimisticBlockStream { "received optimistic block from sequencer" ); + // forward the optimistic block to the rollup's optimistic execution server + self.execution_handle + .try_send_block_to_execute(optimistic_block.clone()) + .wrap_err("failed to send optimistic block for execution")?; + std::task::Poll::Ready(Some(Ok(optimistic_block))) } } diff --git a/crates/astria-auctioneer/src/bundle/mod.rs b/crates/astria-auctioneer/src/bundle/mod.rs index a5f79b3597..a7d9a75d5a 100644 --- a/crates/astria-auctioneer/src/bundle/mod.rs +++ b/crates/astria-auctioneer/src/bundle/mod.rs @@ -21,7 +21,7 @@ use prost::Message as _; mod client; -// TODO: this should probably be moved to astria_core::bundle +// TODO: this should probably be moved to astria_core::bundle? #[derive(Debug, Clone)] pub(crate) struct Bundle { /// The fee that will be charged for this bundle @@ -98,7 +98,7 @@ impl Bundle { self.fee } - pub(crate) fn prev_rollup_block_hash(&self) -> [u8; 32] { + pub(crate) fn parent_rollup_block_hash(&self) -> [u8; 32] { self.prev_rollup_block_hash } diff --git a/crates/astria-auctioneer/src/optimistic_executor/mod.rs b/crates/astria-auctioneer/src/optimistic_executor/mod.rs index 04e9221e56..8eeed8e3d4 100644 --- a/crates/astria-auctioneer/src/optimistic_executor/mod.rs +++ b/crates/astria-auctioneer/src/optimistic_executor/mod.rs @@ -1,4 +1,35 @@ -//! - [ ] mention backpressure here +//! The Optimistic Executor is the component responsible for maintaining the current block +//! state based on the optimistic block stream, the block commitment stream, and the executed +//! block stream. The Optimistic Executior uses its current block state for running an auction +//! per block. Incoming bundles are fed to the current auction after checking them against the +//! current block state. +//! +//! ## Block Lifecycle +//! The Optimistic Executor tracks its current block using the `block:Current` struct, at a high +//! level: +//! 1. Blocks are received optimistically from the sequencer via the optimistic block stream, which +//! also forwards them to the rollup node for execution. +//! 2. Execution results are received from the rollup node via the executed block stream. +//! 3. Commitments are received from the sequencer via the block commitment stream. +//! +//! ## Auction Lifecycle +//! The current block state is used for running an auction per block. Auctions are managed by the +//! `auction::Manager` struct, and the Optimistic Executor advances their state based on its current +//! block state: +//! 1. An auction is created when a new block is received optimistically. +//! 2. The auction will begin processing bids when the executed block is received. +//! 3. The auction's timer is started when a block commitment is received. +//! 4. Bundles are fed to the current auction after checking them against the current block state. +//! +//! ### Bundles and Backpressure +//! Bundles are fed to the current auction via an mpsc channel. Since the auction will only start +//! processing bids after the executed block is received, the channel is used to buffer bundles +//! that are received before the executed block. +//! If too many bundles are received from the rollup node before the block is executed +//! optimistically on the rollup node, the channel will fill up and newly received bundles will be +//! dropped until the auction begins processing bundles. +//! We assume this is highly unlikely, as the rollup node's should filter the bundles it streams +//! by its optimistic head block hash. use astria_core::primitive::v1::RollupId; use astria_eyre::eyre::{ @@ -15,6 +46,7 @@ use tracing::{ error, info, instrument, + warn, }; use crate::{ @@ -65,22 +97,25 @@ impl Startup { auctions, } = self; + let (execution_stream_handle, executed_blocks) = + ExecutedBlockStream::connect(rollup_id, rollup_grpc_endpoint.clone()) + .await + .wrap_err("failed to initialize executed block stream")?; + let sequencer_client = OptimisticBlockClient::new(&sequencer_grpc_endpoint) .wrap_err("failed to initialize sequencer grpc client")?; - let mut optimistic_blocks = - OptimisticBlockStream::connect(rollup_id, sequencer_client.clone()) - .await - .wrap_err("failed to initialize optimsitic block stream")?; + let mut optimistic_blocks = OptimisticBlockStream::connect( + rollup_id, + sequencer_client.clone(), + execution_stream_handle, + ) + .await + .wrap_err("failed to initialize optimsitic block stream")?; let block_commitments = BlockCommitmentStream::connect(sequencer_client) .await .wrap_err("failed to initialize block commitment stream")?; - let (blocks_to_execute_handle, executed_blocks) = - ExecutedBlockStream::connect(rollup_id, rollup_grpc_endpoint.clone()) - .await - .wrap_err("failed to initialize executed block stream")?; - let bundle_stream = BundleStream::connect(rollup_grpc_endpoint) .await .wrap_err("failed to initialize bundle stream")?; @@ -98,7 +133,6 @@ impl Startup { optimistic_blocks, block_commitments, executed_blocks, - blocks_to_execute_handle, bundle_stream, auctions, current_block, @@ -107,12 +141,13 @@ impl Startup { } pub(crate) struct Running { + // TODO: add metrics + #[allow(dead_code)] metrics: &'static crate::Metrics, shutdown_token: CancellationToken, optimistic_blocks: OptimisticBlockStream, block_commitments: BlockCommitmentStream, executed_blocks: ExecutedBlockStream, - blocks_to_execute_handle: block::executed_stream::Handle, bundle_stream: BundleStream, auctions: auction::Manager, current_block: block::Current, @@ -130,8 +165,7 @@ impl Running { }, Some((id, res)) = self.auctions.join_next() => { - // TODO: this seems wrong? - res.wrap_err_with(|| "auction failed for block {id}").map(|_| "auction {id} failed")?; + res.wrap_err_with(|| format!("auction failed for block {}", base64(id)))?; }, res = self.optimistic_blocks.next() => { @@ -140,23 +174,23 @@ impl Running { let _ = self.handle_optimistic_block(res); }, - Some(res) = self.block_commitments.next() => { - let block_commitment = tri!(res.wrap_err("failed to get block commitment")); + res = self.block_commitments.next() => { + let res = break_for_closed_stream!(res, "block commitment stream closed"); - let _ = self.handle_block_commitment(block_commitment); + let _ = self.handle_block_commitment(res); }, - Some(res) = self.executed_blocks.next() => { - let executed_block = res.wrap_err("failed to get executed block")?; + res = self.executed_blocks.next() => { + let res = break_for_closed_stream!(res, "executed block stream closed"); - let _ = self.handle_executed_block(executed_block); + let _ = self.handle_executed_block(res); } Some(res) = self.bundle_stream.next() => { let bundle = res.wrap_err("failed to get bundle")?; - let _ = self.handle_incoming_bundle(bundle); + let _ = self.handle_bundle(bundle); } } } @@ -175,7 +209,7 @@ impl Running { &mut self, optimistic_block: eyre::Result, ) -> eyre::Result<()> { - let optimistic_block = optimistic_block.wrap_err("failed receiving optimistic block")?; + let optimistic_block = optimistic_block.wrap_err("failed to receive optimistic block")?; let old_auction_id = auction::Id::from_sequencer_block_hash(self.current_block.sequencer_block_hash()); @@ -184,7 +218,6 @@ impl Running { .wrap_err("failed to abort auction")?; info!( - // TODO: is this how we display block hashes? optimistic_block.sequencer_block_hash = %base64(optimistic_block.sequencer_block_hash()), "received optimistic block, aborting old auction and starting new auction" ); @@ -194,21 +227,24 @@ impl Running { auction::Id::from_sequencer_block_hash(self.current_block.sequencer_block_hash()); self.auctions.new_auction(new_auction_id); - // forward the optimistic block to the rollup's optimistic execution server - // TODO: don't want to exit on this, just complain with a log and skip the block or smth? - self.blocks_to_execute_handle - .try_send_block_to_execute(optimistic_block) - .wrap_err("failed to send optimistic block for execution")?; - Ok(()) } #[instrument(skip_all, fields(auction.id = %base64(self.current_block.sequencer_block_hash())), err)] - fn handle_block_commitment(&mut self, block_commitment: block::Commitment) -> eyre::Result<()> { - // TODO: handle this with a log instead of exiting? - self.current_block - .commitment(block_commitment) - .wrap_err("failed to set block commitment")?; + fn handle_block_commitment( + &mut self, + block_commitment: eyre::Result, + ) -> eyre::Result<()> { + let block_commitment = block_commitment.wrap_err("failed to receive block commitment")?; + + if let Err(e) = self.current_block.commitment(block_commitment.clone()) { + warn!( + current_block.sequencer_block_hash = %base64(self.current_block.sequencer_block_hash()), + block_commitment.sequencer_block_hash = %base64(block_commitment.sequencer_block_hash()), + "received block commitment for the wrong block" + ); + return Err(e).wrap_err("failed to handle block commitment"); + } let auction_id = auction::Id::from_sequencer_block_hash(self.current_block.sequencer_block_hash()); @@ -221,11 +257,22 @@ impl Running { } #[instrument(skip_all, fields(auction.id = %base64(self.current_block.sequencer_block_hash())))] - fn handle_executed_block(&mut self, executed_block: block::Executed) -> eyre::Result<()> { - // TODO: handle this with a log instead of exiting? - self.current_block - .execute(executed_block) - .wrap_err("failed to set block to executed")?; + fn handle_executed_block( + &mut self, + executed_block: eyre::Result, + ) -> eyre::Result<()> { + let executed_block = executed_block.wrap_err("failed to receive executed block")?; + + if let Err(e) = self.current_block.execute(executed_block.clone()) { + warn!( + // TODO: nicer display for the current block + current_block.sequencer_block_hash = %base64(self.current_block.sequencer_block_hash()), + executed_block.sequencer_block_hash = %base64(executed_block.sequencer_block_hash()), + executed_block.rollup_block_hash = %base64(executed_block.rollup_block_hash()), + "received optimistic execution result for wrong sequencer block" + ); + return Err(e).wrap_err("failed to handle executed block"); + } let auction_id = auction::Id::from_sequencer_block_hash(self.current_block.sequencer_block_hash()); @@ -238,26 +285,15 @@ impl Running { } #[instrument(skip_all, fields(auction.id = %base64(self.current_block.sequencer_block_hash())))] - fn handle_incoming_bundle(&mut self, bundle: Bundle) -> eyre::Result<()> { - // TODO: use ensure! here and provide the hashes in the error - if bundle.base_sequencer_block_hash() != self.current_block.sequencer_block_hash() { - return Err(eyre!( - "incoming bundle's {sequencer_block_hash} does not match current sequencer block \ - hash" - )); - } - - if let Some(rollup_parent_block_hash) = self.current_block.rollup_parent_block_hash() { - if bundle.prev_rollup_block_hash() != rollup_parent_block_hash { - return Err(eyre!( - "bundle's rollup parent block hash does not match current rollup parent block \ - hash" - )); - } - } else { - // TODO: should i buffer these up in the channel until the block is executed and then - // filter them in the auction if the parent hashes dont match? - return Err(eyre!("current block has not been executed yet.")); + fn handle_bundle(&mut self, bundle: Bundle) -> eyre::Result<()> { + if let Err(e) = self.current_block.ensure_bundle_is_valid(&bundle) { + warn!( + curent_block.sequencer_block_hash = %base64(self.current_block.sequencer_block_hash()), + bundle.sequencer_block_hash = %base64(bundle.base_sequencer_block_hash()), + bundle.parent_rollup_block_hash = %base64(bundle.parent_rollup_block_hash()), + "incoming bundle does not match current block, ignoring" + ); + return Err(e).wrap_err("failed to handle bundle"); } let auction_id = From b3fa694f2d049ead6fef47aa62dfd72c5e9fa27d Mon Sep 17 00:00:00 2001 From: itamar Date: Thu, 7 Nov 2024 21:39:03 -0500 Subject: [PATCH 8/9] lint fixes --- crates/astria-auctioneer/Cargo.toml | 16 +++++----- .../src/auction/allocation_rule.rs | 2 +- .../astria-auctioneer/src/auction/builder.rs | 4 +-- crates/astria-auctioneer/src/auction/mod.rs | 29 ++++++++----------- .../astria-auctioneer/src/auctioneer/inner.rs | 8 ++--- .../astria-auctioneer/src/auctioneer/mod.rs | 1 - .../src/block/commitment_stream.rs | 7 ++--- .../src/block/executed_stream.rs | 5 ++-- crates/astria-auctioneer/src/block/mod.rs | 25 +++++++++------- .../src/block/optimistic_stream.rs | 5 ++-- crates/astria-auctioneer/src/bundle/client.rs | 7 +++-- 11 files changed, 54 insertions(+), 55 deletions(-) diff --git a/crates/astria-auctioneer/Cargo.toml b/crates/astria-auctioneer/Cargo.toml index 2d7fe56a72..77e9b6ca58 100644 --- a/crates/astria-auctioneer/Cargo.toml +++ b/crates/astria-auctioneer/Cargo.toml @@ -18,7 +18,7 @@ astria-eyre = { path = "../astria-eyre" } config = { package = "astria-config", path = "../astria-config" } sequencer_client = { package = "astria-sequencer-client", path = "../astria-sequencer-client" } telemetry = { package = "astria-telemetry", path = "../astria-telemetry", features = [ - "display", + "display", ] } async-trait = { workspace = true } @@ -36,11 +36,11 @@ serde_json = { workspace = true } sha2 = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = [ - "macros", - "rt-multi-thread", - "sync", - "time", - "signal", + "macros", + "rt-multi-thread", + "sync", + "time", + "signal", ] } tokio-util = { workspace = true, features = ["rt"] } tracing = { workspace = true, features = ["attributes"] } @@ -51,12 +51,12 @@ tokio-stream = { workspace = true, features = ["net"] } [dev-dependencies] astria-core = { path = "../astria-core", features = ["client"] } config = { package = "astria-config", path = "../astria-config", features = [ - "tests", + "tests", ] } insta = { workspace = true, features = ["json"] } tempfile = { workspace = true } test_utils = { package = "astria-test-utils", path = "../astria-test-utils", features = [ - "geth", + "geth", ] } tokio-test = { workspace = true } wiremock = { workspace = true } diff --git a/crates/astria-auctioneer/src/auction/allocation_rule.rs b/crates/astria-auctioneer/src/auction/allocation_rule.rs index 3bae574daa..4eab5a23d5 100644 --- a/crates/astria-auctioneer/src/auction/allocation_rule.rs +++ b/crates/astria-auctioneer/src/auction/allocation_rule.rs @@ -17,7 +17,7 @@ impl FirstPrice { /// /// Returns `true` if the bid is accepted as the highest bid. pub(crate) fn bid(&mut self, bundle: Bundle) -> bool { - if bundle.bid() > self.highest_bid.as_ref().map_or(0, |b| b.bid()) { + if bundle.bid() > self.highest_bid.as_ref().map_or(0, Bundle::bid) { self.highest_bid = Some(bundle); true } else { diff --git a/crates/astria-auctioneer/src/auction/builder.rs b/crates/astria-auctioneer/src/auction/builder.rs index ae5bf8464e..9038fb76f9 100644 --- a/crates/astria-auctioneer/src/auction/builder.rs +++ b/crates/astria-auctioneer/src/auction/builder.rs @@ -67,8 +67,8 @@ impl Builder { sequencer_abci_client, commands_rx, new_bundles_rx, - auction_id, latency_margin, + id: auction_id, sequencer_key, fee_asset_denomination, sequencer_chain_id, @@ -77,8 +77,8 @@ impl Builder { ( Handle { - new_bundles_tx, commands_tx, + new_bundles_tx, }, auction, ) diff --git a/crates/astria-auctioneer/src/auction/mod.rs b/crates/astria-auctioneer/src/auction/mod.rs index 8bedeadb17..5f66fce7d8 100644 --- a/crates/astria-auctioneer/src/auction/mod.rs +++ b/crates/astria-auctioneer/src/auction/mod.rs @@ -13,8 +13,8 @@ //! `Command::Abort`. This will cause the auction to return early without submitting a winner, //! effectively discarding any bundles that were processed. //! This is used for leveraging optimsitic execution, running an auction for block data that has -//! been proposed in the sequencer network's CometBFT but not yet finalized. -//! We assume that most proposals are adopted in CometBFT, allowing us to buy a few hundred +//! been proposed in the sequencer network's cometBFT but not yet finalized. +//! We assume that most proposals are adopted in cometBFT, allowing us to buy a few hundred //! milliseconds before they are finalized. However, if multiple rounds of voting invalidate a //! proposal, we can abort the auction and avoid submitting a potentially invalid bundle. In this //! case, the auction will abort and a new one will be created for the newly processed proposal @@ -119,8 +119,7 @@ pub(crate) struct Handle { impl Handle { pub(crate) fn try_abort(&mut self) -> eyre::Result<()> { - let _ = self - .commands_tx + self.commands_tx .try_send(Command::Abort) .wrap_err("unable to send abort command to auction")?; @@ -128,16 +127,14 @@ impl Handle { } pub(crate) fn start_processing_bids(&mut self) -> eyre::Result<()> { - let _ = self - .commands_tx + self.commands_tx .try_send(Command::StartProcessingBids) .wrap_err("unable to send command to start processing bids to auction")?; Ok(()) } pub(crate) fn start_timer(&mut self) -> eyre::Result<()> { - let _ = self - .commands_tx + self.commands_tx .try_send(Command::StartTimer) .wrap_err("unable to send command to start time to auction")?; @@ -169,7 +166,7 @@ pub(crate) struct Auction { /// The time between receiving a block commitment latency_margin: Duration, /// The ID of the auction - auction_id: Id, + id: Id, /// The key used to sign transactions on the sequencer sequencer_key: SequencerKey, /// Fee asset for submitting transactions @@ -204,8 +201,7 @@ impl Auction { match cmd { Command::Abort => { // abort the auction early - // TODO: should this be an error? - break Err(eyre!("auction {id} received abort signal", id = base64(&self.auction_id))); + break Err(eyre!("auction {id} received abort signal", id = base64(&self.id))); }, Command::StartProcessingBids => { if auction_is_open { @@ -224,7 +220,7 @@ impl Auction { // we wait for commit because we want the pending nonce from the committed block nonce_fetch = { let client = self.sequencer_grpc_client.clone(); - let address = self.sequencer_key.address().clone(); + let &address = self.sequencer_key.address(); Some(tokio::task::spawn(async move { get_pending_nonce(client, address, self.metrics).await })) }; } @@ -234,13 +230,13 @@ impl Auction { Some(bundle) = self.new_bundles_rx.recv(), if auction_is_open => { if allocation_rule.bid(bundle.clone()) { info!( - auction.id = %base64(self.auction_id), + auction.id = %base64(self.id), bundle.bid = %bundle.bid(), "received new highest bid" ); } else { debug!( - auction.id = %base64(self.auction_id), + auction.id = %base64(self.id), bundle.bid = %bundle.bid(), "received bid lower than current highest bid, discarding" ); @@ -286,11 +282,11 @@ impl Auction { match result { Ok(resp) => { // TODO: handle failed submission instead of just logging the result - info!(auction.id = %base64(self.auction_id), auction.result = %resp.log, "auction result submitted to sequencer"); + info!(auction.id = %base64(self.id), auction.result = %resp.log, "auction result submitted to sequencer"); Ok(()) }, Err(e) => { - error!(auction.id = %base64(self.auction_id), err = %e, "failed to submit auction result to sequencer"); + error!(auction.id = %base64(self.id), err = %e, "failed to submit auction result to sequencer"); Err(e).wrap_err("failed to submit auction result to sequencer") }, } @@ -329,7 +325,6 @@ async fn get_pending_nonce( let nonce = tryhard::retry_fn(|| { let mut client = client.clone(); - let address = address.clone(); async move { client diff --git a/crates/astria-auctioneer/src/auctioneer/inner.rs b/crates/astria-auctioneer/src/auctioneer/inner.rs index f34d44bf49..e2da31f057 100644 --- a/crates/astria-auctioneer/src/auctioneer/inner.rs +++ b/crates/astria-auctioneer/src/auctioneer/inner.rs @@ -113,7 +113,7 @@ impl Auctioneer { Some((name, res)) = self.tasks.join_next() => { flatten_result(res) .wrap_err_with(|| format!("task `{name}` failed")) - .map(|_| "task `{name}` exited unexpectedly") + .map(|()| "task `{name}` exited unexpectedly") } }; @@ -135,10 +135,10 @@ impl Auctioneer { let message = "task shut down"; match flatten_result(res) { Ok(()) => { - info!(name, message) + info!(name, message); } Err(err) => { - error!(name, %err, message) + error!(name, %err, message); } } } @@ -153,7 +153,7 @@ impl Auctioneer { warn!( tasks = format_args!("[{tasks}]"), "aborting all tasks that have not yet shut down" - ) + ); } else { info!("all tasks have shut down regularly"); } diff --git a/crates/astria-auctioneer/src/auctioneer/mod.rs b/crates/astria-auctioneer/src/auctioneer/mod.rs index aed31d40bd..cce3a43586 100644 --- a/crates/astria-auctioneer/src/auctioneer/mod.rs +++ b/crates/astria-auctioneer/src/auctioneer/mod.rs @@ -32,7 +32,6 @@ impl Auctioneer { /// /// # Errors /// Returns an error if the Auctioneer cannot be initialized. - #[must_use] pub fn spawn(cfg: Config, metrics: &'static Metrics) -> eyre::Result { let shutdown_token = CancellationToken::new(); let inner = inner::Auctioneer::new(cfg, metrics, shutdown_token.child_token())?; diff --git a/crates/astria-auctioneer/src/block/commitment_stream.rs b/crates/astria-auctioneer/src/block/commitment_stream.rs index a6c6bcd814..aa603ab074 100644 --- a/crates/astria-auctioneer/src/block/commitment_stream.rs +++ b/crates/astria-auctioneer/src/block/commitment_stream.rs @@ -45,9 +45,8 @@ impl Stream for BlockCommitmentStream { mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { - let res = match futures::ready!(self.client.poll_next_unpin(cx)) { - Some(res) => res, - None => return std::task::Poll::Ready(None), + let Some(res) = futures::ready!(self.client.poll_next_unpin(cx)) else { + return std::task::Poll::Ready(None); }; let raw = res @@ -56,7 +55,7 @@ impl Stream for BlockCommitmentStream { .ok_or_eyre("response did not contain block commitment")?; let commitment = - Commitment::try_from_raw(raw).wrap_err("failed to parse raw to BlockCommitment")?; + Commitment::try_from_raw(&raw).wrap_err("failed to parse raw to BlockCommitment")?; debug!(block_commitment.sequencer_block_hash = %base64(&commitment.sequencer_block_hash()), "received block commitment"); diff --git a/crates/astria-auctioneer/src/block/executed_stream.rs b/crates/astria-auctioneer/src/block/executed_stream.rs index 184d9f02ed..bea5db6ed4 100644 --- a/crates/astria-auctioneer/src/block/executed_stream.rs +++ b/crates/astria-auctioneer/src/block/executed_stream.rs @@ -82,9 +82,8 @@ impl Stream for ExecutedBlockStream { mut self: Pin<&mut Self>, cx: &mut std::task::Context, ) -> std::task::Poll> { - let res = match futures::ready!(self.client.poll_next_unpin(cx)) { - Some(res) => res, - None => return std::task::Poll::Ready(None), + let Some(res) = futures::ready!(self.client.poll_next_unpin(cx)) else { + return std::task::Poll::Ready(None); }; let raw = res.wrap_err("received gRPC Error")?; diff --git a/crates/astria-auctioneer/src/block/mod.rs b/crates/astria-auctioneer/src/block/mod.rs index 0fd30b99c3..3d087241de 100644 --- a/crates/astria-auctioneer/src/block/mod.rs +++ b/crates/astria-auctioneer/src/block/mod.rs @@ -4,13 +4,18 @@ use astria_core::{ bundle::v1alpha1 as raw_bundle, sequencerblock::{ optimisticblock::v1alpha1 as raw_optimistic_block, - v1 as raw_sequencer_block, + v1::{ + self as raw_sequencer_block, + }, }, }, primitive::v1::RollupId, - sequencerblock::v1::block::{ - FilteredSequencerBlock, - FilteredSequencerBlockParts, + sequencerblock::v1::{ + block::{ + FilteredSequencerBlock, + FilteredSequencerBlockParts, + }, + RollupTransactions, }, Protobuf, }; @@ -79,7 +84,7 @@ impl Optimistic { let maybe_serialized_transactions = rollup_transactions .swap_remove(&rollup_id) - .map(|transactions| transactions.into_parts()); + .map(RollupTransactions::into_parts); let transactions = maybe_serialized_transactions.map_or(Ok(vec![]), |serialized_transactions| { @@ -101,7 +106,7 @@ impl Optimistic { } pub(crate) fn sequencer_block_hash(&self) -> [u8; 32] { - self.filtered_sequencer_block.block_hash().clone() + *self.filtered_sequencer_block.block_hash() } pub(crate) fn sequencer_height(&self) -> u64 { @@ -170,7 +175,7 @@ pub(crate) struct Commitment { impl Commitment { pub(crate) fn try_from_raw( - raw: raw_optimistic_block::SequencerBlockCommit, + raw: &raw_optimistic_block::SequencerBlockCommit, ) -> eyre::Result { Ok(Self { sequencer_height: raw.height, @@ -238,10 +243,10 @@ impl Current { self.optimistic.sequencer_block_hash() } - pub(crate) fn rollup_parent_block_hash(&self) -> Option<[u8; 32]> { + pub(crate) fn parent_rollup_block_hash(&self) -> Option<[u8; 32]> { self.executed .as_ref() - .map(|executed| executed.parent_rollup_block_hash()) + .map(Executed::parent_rollup_block_hash) } /// Ensures that the given `bundle` is valid for the current block state. @@ -254,7 +259,7 @@ impl Current { current_hash = base64(self.sequencer_block_hash()) ); - if let Some(rollup_parent_block_hash) = self.rollup_parent_block_hash() { + if let Some(rollup_parent_block_hash) = self.parent_rollup_block_hash() { ensure!( bundle.parent_rollup_block_hash() == rollup_parent_block_hash, "bundle's rollup parent block hash {bundle_hash} does not match current rollup \ diff --git a/crates/astria-auctioneer/src/block/optimistic_stream.rs b/crates/astria-auctioneer/src/block/optimistic_stream.rs index 6bbb995422..76c384dd21 100644 --- a/crates/astria-auctioneer/src/block/optimistic_stream.rs +++ b/crates/astria-auctioneer/src/block/optimistic_stream.rs @@ -69,9 +69,8 @@ impl Stream for OptimisticBlockStream { mut self: Pin<&mut Self>, cx: &mut std::task::Context, ) -> std::task::Poll> { - let res = match futures::ready!(self.client.poll_next_unpin(cx)) { - Some(raw) => raw, - None => return std::task::Poll::Ready(None), + let Some(res) = futures::ready!(self.client.poll_next_unpin(cx)) else { + return std::task::Poll::Ready(None); }; let raw = res diff --git a/crates/astria-auctioneer/src/bundle/client.rs b/crates/astria-auctioneer/src/bundle/client.rs index 50de5bfdf2..2240b3aae5 100644 --- a/crates/astria-auctioneer/src/bundle/client.rs +++ b/crates/astria-auctioneer/src/bundle/client.rs @@ -123,8 +123,11 @@ impl Stream for BundleStream { mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { - let raw = futures::ready!(self.client.poll_next_unpin(cx)) - .ok_or_eyre("stream has been closed")? + let Some(res) = futures::ready!(self.client.poll_next_unpin(cx)) else { + return std::task::Poll::Ready(None); + }; + + let raw = res .wrap_err("received gRPC error")? .bundle .ok_or_eyre("bundle stream response did not contain bundle")?; From 14c7b93cebf734506a5f6bf893762c89eeb09a97 Mon Sep 17 00:00:00 2001 From: itamar Date: Tue, 19 Nov 2024 12:50:40 +0900 Subject: [PATCH 9/9] add signed bundles --- crates/astria-auctioneer/src/auction/mod.rs | 1 + crates/astria-auctioneer/src/bundle/mod.rs | 49 ++++++++++++++++++--- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/crates/astria-auctioneer/src/auction/mod.rs b/crates/astria-auctioneer/src/auction/mod.rs index 5f66fce7d8..208a9c862e 100644 --- a/crates/astria-auctioneer/src/auction/mod.rs +++ b/crates/astria-auctioneer/src/auction/mod.rs @@ -265,6 +265,7 @@ impl Auction { .into_transaction_body( nonce, self.rollup_id, + self.sequencer_key.clone(), self.fee_asset_denomination.clone(), self.sequencer_chain_id, ); diff --git a/crates/astria-auctioneer/src/bundle/mod.rs b/crates/astria-auctioneer/src/bundle/mod.rs index a7d9a75d5a..cc77285350 100644 --- a/crates/astria-auctioneer/src/bundle/mod.rs +++ b/crates/astria-auctioneer/src/bundle/mod.rs @@ -1,4 +1,8 @@ use astria_core::{ + crypto::{ + Signature, + VerificationKey, + }, generated::bundle::v1alpha1::{ self as raw, }, @@ -19,6 +23,8 @@ use bytes::Bytes; pub(crate) use client::BundleStream; use prost::Message as _; +use crate::sequencer_key::SequencerKey; + mod client; // TODO: this should probably be moved to astria_core::bundle? @@ -71,19 +77,18 @@ impl Bundle { self, nonce: u32, rollup_id: RollupId, + sequencer_key: SequencerKey, fee_asset: asset::Denom, chain_id: String, ) -> TransactionBody { - let data = self.into_raw().encode_to_vec(); - - // TODO: sign the bundle data and put it in a `SignedBundle` message or something (need to - // update protos for this) + let allocation = Allocation::new(self, sequencer_key); + let allocation_data = allocation.into_raw().encode_to_vec(); TransactionBody::builder() .actions(vec![ RollupDataSubmission { rollup_id, - data: data.into(), + data: allocation_data.into(), fee_asset, } .into(), @@ -106,3 +111,37 @@ impl Bundle { self.base_sequencer_block_hash } } + +#[derive(Debug)] +pub(crate) struct Allocation { + signature: Signature, + verification_key: VerificationKey, + payload: Bundle, +} + +impl Allocation { + fn new(bundle: Bundle, sequencer_key: SequencerKey) -> Self { + let bundle_data = bundle.clone().into_raw().encode_to_vec(); + let signature = sequencer_key.signing_key().sign(&bundle_data); + let verification_key = sequencer_key.signing_key().verification_key(); + Self { + signature, + verification_key, + payload: bundle, + } + } + + fn into_raw(self) -> raw::Allocation { + let Self { + signature, + verification_key, + payload, + } = self; + + raw::Allocation { + signature: Bytes::copy_from_slice(&signature.to_bytes()), + public_key: Bytes::copy_from_slice(&verification_key.to_bytes()), + payload: Some(payload.into_raw()), + } + } +}