Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ eyre = "0.6.12"
openssl = { version = "0.10", features = ["vendored"] }
reqwest = { version = "0.12.22", features = ["blocking", "json"] }
serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.145"
tracing = "0.1.41"
tokio = { version = "1.36.0", features = ["full", "macros", "rt-multi-thread"] }
tokio-stream = "0.1.17"
url = "2.5.4"
thiserror = "2.0.17"

[dev-dependencies]
alloy-hardforks = "0.4.0"
Expand Down
63 changes: 45 additions & 18 deletions src/quincey.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,35 @@ use alloy::{
primitives::{Address, B256, U256},
signers::Signer,
};
use eyre::bail;
use init4_bin_base::{perms::SharedToken, utils::signer::LocalOrAws};
use reqwest::Client;
use signet_types::{SignRequest, SignResponse};
use tracing::{debug, info, instrument, trace};
use tracing::{info, instrument};

type Result<T> = core::result::Result<T, QuinceyError>;

/// Errors that can occur when interacting with the Quincey API.
#[derive(thiserror::Error, Debug)]
pub enum QuinceyError {
/// Error indicating that the auth token is not available.
#[error("Auth token not available")]
Auth(#[from] tokio::sync::watch::error::RecvError),

/// Error indicating that the request occurered during a slot is not
/// assigned to this builder.
#[error(
"Quincey returned a 403 error, indicating that the request occurered during a slot is not assigned to this builder"
)]
NotOurSlot,

/// Error contacting the remote quincey API.
#[error("Error contacting quincey API: {0}")]
Remote(#[from] reqwest::Error),

/// Error with the owned signet.
#[error("Error with owned signet: {0}")]
Owned(#[from] eyre::Report),
}

/// A quincey client for making requests to the Quincey API.
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -49,7 +73,7 @@ impl Quincey {
}

async fn sup_owned(&self, sig_request: &SignRequest) -> eyre::Result<SignResponse> {
let Self::Owned(signer) = &self else { eyre::bail!("not an owned client") };
let Self::Owned(signer) = &self else { panic!("not an owned client") };

info!("signing with owned quincey");
signer
Expand All @@ -59,34 +83,37 @@ impl Quincey {
.map(|sig| SignResponse { sig, req: *sig_request })
}

async fn sup_remote(&self, sig_request: &SignRequest) -> eyre::Result<SignResponse> {
let Self::Remote { client, url, token } = &self else { bail!("not a remote client") };
#[instrument(skip_all)]
async fn sup_remote(&self, sig_request: &SignRequest) -> Result<SignResponse> {
let Self::Remote { client, url, token } = &self else { panic!("not a remote client") };

let token =
token.secret().await.map_err(|e| eyre::eyre!("failed to retrieve token: {e}"))?;
let token = token.secret().await?;

let resp: reqwest::Response = client
let resp = client
.post(url.clone())
.json(sig_request)
.bearer_auth(token)
.send()
.await?
.error_for_status()?;

let body = resp.bytes().await?;
.await
.map_err(QuinceyError::Remote)?;

debug!(bytes = body.len(), "retrieved response body");
trace!(body = %String::from_utf8_lossy(&body), "response body");
if resp.status() == reqwest::StatusCode::FORBIDDEN {
return Err(QuinceyError::NotOurSlot);
}

serde_json::from_slice(&body).map_err(Into::into)
resp.error_for_status()
.map_err(QuinceyError::Remote)?
.json::<SignResponse>()
.await
.map_err(QuinceyError::Remote)
}

/// Get a signature for the provided request, by either using the owned
/// or remote client.
#[instrument(skip(self))]
pub async fn get_signature(&self, sig_request: &SignRequest) -> eyre::Result<SignResponse> {
pub async fn get_signature(&self, sig_request: &SignRequest) -> Result<SignResponse> {
match self {
Self::Owned(_) => self.sup_owned(sig_request).await,
Self::Owned(_) => self.sup_owned(sig_request).await.map_err(Into::into),
Self::Remote { .. } => self.sup_remote(sig_request).await,
}
}
Expand All @@ -95,7 +122,7 @@ impl Quincey {
/// be able to sign a request with the provided parameters at this
/// point in time.
#[instrument(skip(self))]
pub async fn preflight_check(&self, host_block_number: u64) -> eyre::Result<()> {
pub async fn preflight_check(&self, host_block_number: u64) -> Result<()> {
if self.is_local() {
return Ok(());
}
Expand Down
25 changes: 19 additions & 6 deletions src/tasks/env.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
config::{BuilderConfig, HostProvider, RuProvider},
quincey::Quincey,
quincey::{Quincey, QuinceyError},
tasks::block::cfg::SignetCfgEnv,
};
use alloy::{
Expand Down Expand Up @@ -330,11 +330,24 @@ impl EnvTask {
self.quincey.preflight_check(host_block_number + 1).in_current_span(),
);

res_unwrap_or_continue!(
quincey_res,
span,
error!("error checking quincey slot - skipping block submission"),
);
match quincey_res {
Err(QuinceyError::NotOurSlot) => {
span_debug!(
span,
"not our slot according to quincey - skipping block submission"
);
continue;
}
Err(err) => {
span_error!(
span,
%err,
"error during quincey preflight check - skipping block submission"
);
continue;
}
Ok(_) => {}
}

let host_block_opt = res_unwrap_or_continue!(
host_block_res,
Expand Down
31 changes: 20 additions & 11 deletions src/tasks/submit/prep.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
config::{BuilderConfig, HostProvider},
quincey::Quincey,
quincey::{Quincey, QuinceyError},
utils,
};
use alloy::{
Expand All @@ -15,7 +15,7 @@ use init4_bin_base::deps::metrics::counter;
use signet_sim::BuiltBlock;
use signet_types::{SignRequest, SignResponse};
use signet_zenith::Zenith;
use tracing::{Instrument, debug, instrument};
use tracing::{Instrument, debug, error, instrument, warn};

/// Preparation logic for transactions issued to the host chain by the
/// [`SubmitTask`].
Expand Down Expand Up @@ -74,15 +74,24 @@ impl<'a> SubmitPrep<'a> {
/// Get the quincey signature response for the block.
async fn quincey_resp(&self) -> eyre::Result<&SignResponse> {
self.quincey_resp
.get_or_try_init(|| async {
let sig_request = self.sig_request();
self.quincey
.get_signature(sig_request)
.await
.inspect(|_| counter!("signet.builder.quincey_signatures").increment(1))
.inspect_err(|_| {
counter!("signet.builder.quincey_signature_failures").increment(1)
})
.get_or_try_init(|| {
async {
let sig_request = self.sig_request();
self.quincey
.get_signature(sig_request)
.await
.inspect(|_| counter!("signet.builder.quincey_signatures").increment(1))
.inspect_err(|err| {
counter!("signet.builder.quincey_signature_failures").increment(1);
if let QuinceyError::NotOurSlot = err {
warn!("Quincey indicated not our slot to sign");
} else {
error!(%err, "Error obtaining signature from Quincey");
}
})
.map_err(Into::into)
}
.in_current_span()
})
.await
}
Expand Down