Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

introduce max_block_value_to_verify in constraints config #23

Merged
Show file tree
Hide file tree
Changes from 2 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
95 changes: 60 additions & 35 deletions crates/api/src/builder/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -679,42 +679,23 @@ where
.await?;

// Fetch constraints, and if available verify inclusion proofs and save them to cache
if let Some(constraints) = api.auctioneer.get_constraints(payload.slot()).await? {
let transactions_root: B256 = payload
.transactions()
.clone()
.hash_tree_root()?
.to_vec()
.as_slice()
.try_into()
.map_err(|e| {
error!(error = %e, "failed to convert root to hash32");
BuilderApiError::InternalError
})?;
let proofs = payload.proofs().ok_or(BuilderApiError::InclusionProofsNotFound)?;
let constraints_proofs: Vec<_> = constraints.iter().map(|c| &c.proof_data).collect();

verify_multiproofs(constraints_proofs.as_slice(), proofs, transactions_root).map_err(
|e| {
error!(error = %e, "failed to verify inclusion proofs");
BuilderApiError::InclusionProofVerificationFailed(e)
},
)?;

// Save inclusion proof to auctioneer.
api.save_inclusion_proof(
payload.slot(),
payload.proposer_public_key(),
payload.block_hash(),
proofs,
&request_id,
)
.await?;

info!(request_id = %request_id, head_slot, "inclusion proofs verified and saved to auctioneer");
let skip_inclusion_proof_verify_and_save = api
.relay_config
.constraints_api_config
.max_block_value_to_verify_wei
.map_or(false, |max_block_value_to_verify| payload.value() > max_block_value_to_verify);
if !skip_inclusion_proof_verify_and_save {
if let Err(err) = api.verify_and_save_inclusion_proofs(&payload, &request_id).await {
warn!(request_id = %request_id, error = %err, "failed to verify and save inclusion proofs");
return Err(err)
}
thedevbirb marked this conversation as resolved.
Show resolved Hide resolved
} else {
info!(request_id = %request_id, "no constraints found for slot, proof verification is not needed");
};
info!(
request_id = %request_id,
block_value = %payload.value(),
"block value is greater than max value to verify, inclusion proof verification and saving is skipped",
);
}

// If cancellations are enabled, then abort now if there is a later submission
if is_cancellations_enabled {
Expand Down Expand Up @@ -2197,6 +2178,50 @@ where
);
}
}

/// Fetch constraints, and if available verify inclusion proofs and save them to cache.
async fn verify_and_save_inclusion_proofs(
&self,
payload: &SignedBidSubmission,
request_id: &Uuid,
) -> Result<(), BuilderApiError> {
if let Some(constraints) = self.auctioneer.get_constraints(payload.slot()).await? {
let transactions_root: B256 = payload
.transactions()
.clone()
.hash_tree_root()?
.to_vec()
.as_slice()
.try_into()
.map_err(|error| {
error!(?error, "failed to convert root to hash32");
BuilderApiError::InternalError
})?;
let proofs = payload.proofs().ok_or(BuilderApiError::InclusionProofsNotFound)?;
let constraints_proofs: Vec<_> = constraints.iter().map(|c| &c.proof_data).collect();

verify_multiproofs(constraints_proofs.as_slice(), proofs, transactions_root).map_err(
|e| {
error!(error = %e, "failed to verify inclusion proofs");
BuilderApiError::InclusionProofVerificationFailed(e)
},
)?;

// Save inclusion proof to auctioneer.
self.save_inclusion_proof(
payload.slot(),
payload.proposer_public_key(),
payload.block_hash(),
proofs,
request_id,
)
.await?;
info!(%request_id, "inclusion proofs verified and saved to auctioneer");
} else {
info!(%request_id, "no constraints found for slot, proof verification is not needed");
};
Ok(())
}
}

// STATE SYNC
Expand Down
40 changes: 29 additions & 11 deletions crates/api/src/proposer/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ use helix_common::{
signed_proposal::VersionedSignedProposal,
try_execution_header_from_payload,
versioned_payload::PayloadAndBlobs,
BidRequest, Filtering, GetHeaderTrace, GetPayloadTrace, RegisterValidatorsTrace,
BidRequest, Filtering, GetHeaderTrace, GetPayloadTrace, RegisterValidatorsTrace, RelayConfig,
ValidatorPreferences,
};
use helix_database::DatabaseService;
Expand Down Expand Up @@ -88,7 +88,7 @@ where
chain_info: Arc<ChainInfo>,
validator_preferences: Arc<ValidatorPreferences>,

target_get_payload_propagation_duration_ms: u64,
relay_config: RelayConfig,
}

impl<A, DB, M, G> ProposerApi<A, DB, M, G>
Expand All @@ -107,8 +107,8 @@ where
chain_info: Arc<ChainInfo>,
slot_update_subscription: Sender<Sender<ChainUpdate>>,
validator_preferences: Arc<ValidatorPreferences>,
target_get_payload_propagation_duration_ms: u64,
gossip_receiver: Receiver<GossipedMessage>,
relay_config: RelayConfig,
) -> Self {
let api = Self {
auctioneer,
Expand All @@ -119,7 +119,7 @@ where
curr_slot_info: Arc::new(RwLock::new((0, None))),
chain_info,
validator_preferences,
target_get_payload_propagation_duration_ms,
relay_config,
};

// Spin up gossip processing task
Expand Down Expand Up @@ -535,25 +535,42 @@ where
return Err(ProposerApiError::BidValueZero)
}

// Get inclusion proofs
let proofs = proposer_api
.auctioneer
.get_inclusion_proof(slot, &bid_request.public_key, bid.block_hash())
.await?;

// Save trace to DB
proposer_api
.save_get_header_call(
slot,
bid_request.parent_hash,
bid_request.public_key,
bid_request.public_key.clone(),
bid.block_hash().clone(),
trace,
request_id,
user_agent,
)
.await;

// If the block value is greater than the max value to verify, return the bid
// without proofs.
let value_above_max_to_verify = proposer_api
.relay_config
.constraints_api_config
.max_block_value_to_verify_wei
.map_or(false, |max| bid.value() > max);
if value_above_max_to_verify {
info!(
%request_id,
slot,
value = ?bid.value(),
"block value is greater than max value to verify, returning bid without proofs",
);
return Ok(axum::Json(bid))
}

// Get inclusion proofs
let proofs = proposer_api
.auctioneer
.get_inclusion_proof(slot, &bid_request.public_key, bid.block_hash())
.await?;

// Attach the proofs to the bid before sending it back
if let Some(proofs) = proofs {
bid.set_inclusion_proofs(proofs);
Expand Down Expand Up @@ -937,6 +954,7 @@ where
let elapsed_since_propagate_start_ms =
(get_nanos_timestamp()?.saturating_sub(trace.beacon_client_broadcast)) / 1_000_000;
let remaining_sleep_ms = self
.relay_config
.target_get_payload_propagation_duration_ms
.saturating_sub(elapsed_since_propagate_start_ms);
if remaining_sleep_ms > 0 {
Expand Down
2 changes: 1 addition & 1 deletion crates/api/src/proposer/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -997,8 +997,8 @@ mod proposer_api_tests {
Arc::new(ChainInfo::for_holesky()),
slot_update_sender.clone(),
Arc::new(ValidatorPreferences::default()),
0,
gossip_receiver,
Arc::new(Default::default()),
);

let mut x = gen_signed_vr();
Expand Down
2 changes: 1 addition & 1 deletion crates/api/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,8 @@ impl ApiService {
chain_info.clone(),
slot_update_sender.clone(),
validator_preferences.clone(),
config.target_get_payload_propagation_duration_ms,
proposer_gossip_receiver,
config.clone(),
));

let data_api = Arc::new(DataApiProd::new(validator_preferences.clone(), db.clone()));
Expand Down
4 changes: 2 additions & 2 deletions crates/api/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ pub fn app() -> Router {
Arc::new(ChainInfo::for_mainnet()),
slot_update_sender,
Arc::new(ValidatorPreferences::default()),
0,
gossip_receiver,
Arc::new(Default::default()),
));

let data_api = Arc::new(DataApi::<MockDatabaseService>::new(
Expand Down Expand Up @@ -218,8 +218,8 @@ pub fn proposer_api_app() -> (
Arc::new(ChainInfo::for_mainnet()),
slot_update_sender.clone(),
Arc::new(ValidatorPreferences::default()),
0,
gossip_receiver,
Arc::new(Default::default()),
thedevbirb marked this conversation as resolved.
Show resolved Hide resolved
));

let router = Router::new()
Expand Down
13 changes: 11 additions & 2 deletions crates/common/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::{api::*, BuilderInfo, ValidatorPreferences};
use clap::Parser;
use ethereum_consensus::{primitives::BlsPublicKey, ssz::prelude::Node};
use ethereum_consensus::{
primitives::BlsPublicKey,
ssz::prelude::{Node, U256},
};
use helix_utils::{
request_encoding::Encoding,
serde::{default_bool, deserialize_url, serialize_url},
Expand Down Expand Up @@ -136,11 +139,17 @@ pub struct ConstraintsApiConfig {
/// [`/constraints/v1/builder/constraints`](https://docs.boltprotocol.xyz/technical-docs/api/builder#constraints)
/// endpoint will not be checked.
pub check_constraints_signature: bool,
/// Only verify and save inclusion proofs if the block value is less than this threshold.
/// We do this to ensure that high value blocks are not rejected.
pub max_block_value_to_verify_wei: Option<U256>,
}

impl Default for ConstraintsApiConfig {
fn default() -> Self {
ConstraintsApiConfig { check_constraints_signature: true }
ConstraintsApiConfig {
check_constraints_signature: true,
max_block_value_to_verify_wei: None,
}
}
}

Expand Down