Skip to content
Draft
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
4 changes: 2 additions & 2 deletions .github/workflows/ghcr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
type=raw,value=latest,enable={{is_default_branch}}
type=raw,value=latest,enable=true

- name: Build and push Docker image by digest
id: build
Expand Down Expand Up @@ -115,7 +115,7 @@ jobs:
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
type=raw,value=latest,enable={{is_default_branch}}
type=raw,value=latest,enable=true

- name: Create manifest list and push
working-directory: /tmp/digests
Expand Down
29 changes: 25 additions & 4 deletions Cargo.lock

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

6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ axum = "0.7.5"
backon = { version = "1.6.0", features = ["tokio-sleep"] }
eyre = "0.6.12"
futures-util = "0.3.31"
git-version = "0.3.9"
itertools = "0.14.0"
openssl = { version = "0.10", features = ["vendored"] }
reqwest = { version = "0.12.22", features = ["blocking", "json"] }
serde = { version = "1.0.197", features = ["derive"] }
Expand All @@ -64,7 +66,7 @@ alloy-hardforks = "0.4.0"
alloy-chains = "0.2"

# comment / uncomment for local dev
# [patch.crates-io]
[patch.crates-io]
# signet-constants = { path = "../signet-sdk/crates/constants" }
# signet-types = { path = "../signet-sdk/crates/types" }
# signet-zenith = { path = "../signet-sdk/crates/zenith" }
Expand All @@ -74,4 +76,4 @@ alloy-chains = "0.2"
# signet-journal = { path = "../signet-sdk/crates/journal" }
# signet-tx-cache = { path = "../signet-sdk/crates/tx-cache" }
# signet-bundle = { path = "../signet-sdk/crates/bundle" }
# init4-bin-base = { path = "../bin-base" }
init4-bin-base = { git = "https://github.com/init4tech/bin-base", branch = "fraser/eng-1943/various-updates" }
51 changes: 47 additions & 4 deletions bin/builder.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,66 @@
#![recursion_limit = "256"]

use builder::{
config::{BuilderConfig, env_var_info},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cleanliness while we're here, we should consider following the lib+main convention as that should remove the need to add anonymous imports for bin-only deps

src/
|- lib.rs
|- main.rs

service::serve_builder,
tasks::{
block::sim::SimulatorTask, cache::CacheTasks, env::EnvTask, metrics::MetricsTask,
submit::FlashbotsTask,
},
};
use init4_bin_base::deps::tracing::{info, info_span};
use eyre::bail;
use git_version::git_version;
use init4_bin_base::{
deps::tracing::{info, info_span},
utils::from_env::FromEnv,
};
use tokio::select;

const GIT_COMMIT: &str =
git_version!(args = ["--always", "--match=", "--abbrev=7"], fallback = "unknown");
const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");

fn should_print_help() -> bool {
std::env::args().any(|arg| {
let lowercase_arg = arg.to_ascii_lowercase();
lowercase_arg == "-h" || lowercase_arg == "--help"
})
}

fn print_help() {
let version = env!("CARGO_PKG_VERSION");
let env_vars = env_var_info();
println!(
r#"Signet block builder v{version}

Run with no args. Configuration is via the following environment variables:
{env_vars}
"#
)
}

// Note: Must be set to `multi_thread` to support async tasks.
// See: https://docs.rs/tokio/latest/tokio/attr.main.html
#[tokio::main(flavor = "multi_thread")]
async fn main() -> eyre::Result<()> {
let _guard = init4_bin_base::init4();
if should_print_help() {
print_help();
return Ok(());
}

if let Err(e) = BuilderConfig::check_inventory() {
for item in e {
eprintln!("missing environment variable: {}: {}", item.var, item.description);
}
bail!(
"missing at least one required environment variable; run with '--help' to see the list"
);
}

let config = builder::config_from_env();
let init_span_guard = info_span!("builder initialization").entered();

builder::config_from_env();
info!(pkg_version = PKG_VERSION, git_commit = GIT_COMMIT, "starting builder");

// Pre-load the KZG settings in a separate thread.
//
Expand Down Expand Up @@ -45,7 +88,7 @@ async fn main() -> eyre::Result<()> {
let build_jh = simulator_task.spawn_simulator_task(cache_system.sim_cache, submit_channel);

// Start the healthcheck server
let server = serve_builder(([0, 0, 0, 0], builder::config().builder_port));
let server = serve_builder(([0, 0, 0, 0], config.builder_port));

// We have finished initializing the builder, so we can drop the init span
// guard.
Expand Down
79 changes: 62 additions & 17 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use alloy::{
network::{Ethereum, EthereumWallet},
primitives::Address,
providers::{
self, Identity, ProviderBuilder, RootProvider,
Identity, ProviderBuilder, RootProvider,
fillers::{
BlobGasFiller, ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller,
SimpleNonceManager, WalletFiller,
Expand All @@ -12,14 +12,18 @@ use alloy::{
};
use eyre::Result;
use init4_bin_base::{
Init4Config,
perms::{Authenticator, OAuthConfig, SharedToken, pylon},
utils::{
calc::SlotCalculator,
from_env::FromEnv,
from_env::{EnvItemInfo, FromEnv, OptionalU8WithDefault, OptionalU64WithDefault},
metrics::MetricsConfig,
provider::{ProviderConfig, PubSubConfig},
signer::LocalOrAws,
tracing::TracingConfig,
},
};
use itertools::Itertools;
use signet_constants::SignetSystemConstants;
use signet_zenith::Zenith;
use std::borrow::Cow;
Expand Down Expand Up @@ -58,7 +62,7 @@ pub type FlashbotsProvider = FillProvider<
>,
WalletFiller<EthereumWallet>,
>,
providers::RootProvider,
RootProvider,
>;

/// The default concurrency limit for the builder if the system call
Expand Down Expand Up @@ -145,35 +149,36 @@ pub struct BuilderConfig {
/// The max number of simultaneous block simulations to run.
#[from_env(
var = "CONCURRENCY_LIMIT",
desc = "The max number of simultaneous block simulations to run"
desc = "The max number of simultaneous block simulations to run [default: std::thread::available_parallelism]",
optional
)]
pub concurrency_limit: Option<usize>,

/// Optional maximum host gas coefficient to use when building blocks.
/// Defaults to 80% (80) if not set.
#[from_env(
var = "MAX_HOST_GAS_COEFFICIENT",
desc = "Optional maximum host gas coefficient, as a percentage, to use when building blocks",
default = 80
desc = "Optional maximum host gas coefficient, as a percentage, to use when building blocks [default: 80]",
optional
)]
pub max_host_gas_coefficient: Option<u8>,
pub max_host_gas_coefficient: OptionalU8WithDefault<80>,

/// Number of milliseconds before the end of the slot to stop querying for new blocks and start the block signing and submission process.
#[from_env(
var = "BLOCK_QUERY_CUTOFF_BUFFER",
desc = "Number of milliseconds before the end of the slot to stop querying for new transactions and start the block signing and submission process. Quincey will stop accepting signature requests 2000ms before the end of the slot, so this buffer should be no less than 2000ms to match.",
default = 3000
desc = "Number of milliseconds before the end of the slot to stop querying for new transactions and start the block signing and submission process. Quincey will stop accepting signature requests 2000ms before the end of the slot, so this buffer should be no less than 2000ms to match. [default: 3000]",
optional
)]
pub block_query_cutoff_buffer: u64,
pub block_query_cutoff_buffer: OptionalU64WithDefault<3000>,

/// Number of milliseconds before the end of the slot by which bundle submission to Flashbots must complete.
/// If submission completes after this deadline, a warning is logged.
#[from_env(
var = "SUBMIT_DEADLINE_BUFFER",
desc = "Number of milliseconds before the end of the slot by which bundle submission must complete. Submissions that miss this deadline will be logged as warnings.",
default = 500
desc = "Number of milliseconds before the end of the slot by which bundle submission must complete. Submissions that miss this deadline will be logged as warnings. [default: 500]",
optional
)]
pub submit_deadline_buffer: u64,
pub submit_deadline_buffer: OptionalU64WithDefault<500>,

/// The slot calculator for the builder.
pub slot_calculator: SlotCalculator,
Expand All @@ -184,6 +189,22 @@ pub struct BuilderConfig {
/// URL for the Pylon blob server API.
#[from_env(var = "PYLON_URL", desc = "URL for the Pylon blob server API")]
pub pylon_url: url::Url,

/// Tracing and OTEL configuration.
pub tracing: TracingConfig,

/// Metrics configuration.
pub metrics: MetricsConfig,
}

impl Init4Config for BuilderConfig {
fn tracing(&self) -> &TracingConfig {
&self.tracing
}

fn metrics(&self) -> &MetricsConfig {
&self.metrics
}
}

impl BuilderConfig {
Expand Down Expand Up @@ -266,7 +287,7 @@ impl BuilderConfig {
}

/// Get an oauth2 token for the builder, starting the authenticator if it
// is not already running.
/// is not already running.
pub fn oauth_token(&self) -> SharedToken {
static ONCE: std::sync::OnceLock<SharedToken> = std::sync::OnceLock::new();

Expand Down Expand Up @@ -311,10 +332,9 @@ impl BuilderConfig {
}

/// Returns the maximum host gas to use for block building based on the configured max host gas coefficient.
pub fn max_host_gas(&self, gas_limit: u64) -> u64 {
pub const fn max_host_gas(&self, gas_limit: u64) -> u64 {
// Set max host gas to a percentage of the host block gas limit
((gas_limit as u128 * (self.max_host_gas_coefficient.unwrap_or(80) as u128)) / 100u128)
as u64
((gas_limit as u128 * self.max_host_gas_coefficient.into_inner() as u128) / 100u128) as u64
}

/// Connect to the Pylon blob server.
Expand All @@ -323,6 +343,31 @@ impl BuilderConfig {
}
}

/// Get a list of the env vars used to configure the app.
pub fn env_var_info() -> String {
// We need to remove the `SlotCalculator` env vars from the list. `SignetSystemConstants`
// already requires `CHAIN_NAME`, so we don't want to include `CHAIN_NAME` twice. That also
// means the other `SlotCalculator` env vars are ignored since `CHAIN_NAME` must be set.
let is_not_from_slot_calc = |env_item: &&EnvItemInfo| match env_item.var {
"CHAIN_NAME" if env_item.optional => false,
"START_TIMESTAMP" | "SLOT_OFFSET" | "SLOT_DURATION" => false,
_ => true,
};
let inventory_iter = BuilderConfig::inventory().into_iter().filter(is_not_from_slot_calc);
let max_width = inventory_iter.clone().map(|env_item| env_item.var.len()).max().unwrap_or(0);
inventory_iter
.map(|env_item| {
format!(
" {:width$} {}{}",
env_item.var,
env_item.description,
if env_item.optional { " [optional]" } else { "" },
width = max_width
)
})
.join("\n")
}

#[cfg(test)]
mod tests {
/// Tests that URL sanitization correctly handles trailing slashes for url.join() behavior.
Expand Down
Loading