Skip to content

Commit d2d8529

Browse files
committed
feat: ctrl c ctrl v
1 parent eb09673 commit d2d8529

File tree

13 files changed

+1338
-1
lines changed

13 files changed

+1338
-1
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,8 @@ Cargo.lock
1818
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
1919
# and can be added to the global gitignore or merged into this file. For a more nuclear
2020
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
21-
#.idea/
21+
#.idea/
22+
23+
# Added by cargo
24+
25+
/target

Cargo.toml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
[package]
2+
name = "zenith-builder-example"
3+
version = "0.1.0"
4+
description = "Zenith Builder Example"
5+
6+
edition = "2021"
7+
rust-version = "1.81"
8+
authors = ["init4"]
9+
license = "Apache-2.0 OR MIT"
10+
homepage = "https://github.com/init4tt/zenith"
11+
repository = "https://github.com/init4tt/zenith"
12+
13+
[lib]
14+
name = "builder"
15+
16+
[[bin]]
17+
name = "zenith-builder-example"
18+
path = "bin/builder.rs"
19+
20+
[dependencies]
21+
zenith-types = { git = "https://github.com/init4tech/zenith-rs", branch = "main" }
22+
23+
alloy-primitives = { version = "=0.8.8", features = ["serde", "tiny-keccak"] }
24+
alloy-sol-types = { version = "=0.8.8", features = ["json"] }
25+
alloy-rlp = { version = "0.3.4" }
26+
27+
alloy = { version = "0.5.4", features = ["full", "json-rpc", "signer-aws"] }
28+
29+
aws-config = "1.1.7"
30+
aws-sdk-kms = "1.15.0"
31+
32+
hex = { package = "const-hex", version = "1", default-features = false, features = [
33+
"alloc",
34+
] }
35+
serde = { version = "1.0.197", features = ["derive"] }
36+
tracing = "0.1.40"
37+
38+
axum = "0.7.5"
39+
eyre = "0.6.12"
40+
openssl = { version = "0.10", features = ["vendored"] }
41+
reqwest = { version = "0.11.24", features = ["blocking", "json"] }
42+
ruint = "1.12.1"
43+
serde_json = "1.0"
44+
thiserror = "1.0.58"
45+
tokio = { version = "1.36.0", features = ["full", "macros", "rt-multi-thread"] }
46+
tracing-subscriber = "0.3.18"
47+
async-trait = "0.1.80"
48+
oauth2 = "4.4.2"

bin/builder.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#![allow(dead_code)]
2+
3+
use builder::config::BuilderConfig;
4+
use builder::service::serve_builder_with_span;
5+
use builder::tasks::tx_poller::TxPoller;
6+
7+
use tokio::select;
8+
9+
#[tokio::main]
10+
async fn main() -> eyre::Result<()> {
11+
tracing_subscriber::fmt::try_init().unwrap();
12+
let span = tracing::info_span!("zenith-builder");
13+
14+
let config = BuilderConfig::load_from_env()?;
15+
let provider = config.connect_provider().await?;
16+
17+
tracing::debug!(rpc_url = config.host_rpc_url.as_ref(), "instantiated provider");
18+
19+
let sequencer_signer = config.connect_sequencer_signer().await?;
20+
let zenith = config.connect_zenith(provider.clone());
21+
22+
let port = config.builder_port;
23+
24+
let tx_poller = TxPoller::new(&config);
25+
let builder = builder::tasks::block::BlockBuilder::new(&config);
26+
27+
let submit = builder::tasks::submit::SubmitTask {
28+
provider,
29+
zenith,
30+
client: reqwest::Client::new(),
31+
sequencer_signer,
32+
config,
33+
};
34+
35+
let (submit_channel, submit_jh) = submit.spawn();
36+
let (build_channel, build_jh) = builder.spawn(submit_channel);
37+
let tx_poller_jh = tx_poller.spawn(build_channel.clone());
38+
39+
let server = serve_builder_with_span(build_channel, ([0, 0, 0, 0], port), span);
40+
41+
select! {
42+
_ = submit_jh => {
43+
tracing::info!("submit finished");
44+
},
45+
_ = build_jh => {
46+
tracing::info!("build finished");
47+
}
48+
_ = server => {
49+
tracing::info!("server finished");
50+
}
51+
_ = tx_poller_jh => {
52+
tracing::info!("tx_poller finished");
53+
}
54+
}
55+
56+
tracing::info!("shutting down");
57+
58+
Ok(())
59+
}

src/config.rs

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
use crate::signer::{LocalOrAws, SignerError};
2+
use alloy::network::{Ethereum, EthereumWallet};
3+
use alloy::providers::fillers::BlobGasFiller;
4+
use alloy::providers::{
5+
fillers::{ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, WalletFiller},
6+
Identity, ProviderBuilder, RootProvider,
7+
};
8+
use alloy::transports::BoxTransport;
9+
use alloy_primitives::Address;
10+
use std::{borrow::Cow, env, num, str::FromStr};
11+
use zenith_types::Zenith;
12+
13+
// Keys for .env variables that need to be set to configure the builder.
14+
const HOST_CHAIN_ID: &str = "HOST_CHAIN_ID";
15+
const RU_CHAIN_ID: &str = "RU_CHAIN_ID";
16+
const HOST_RPC_URL: &str = "HOST_RPC_URL";
17+
const ZENITH_ADDRESS: &str = "ZENITH_ADDRESS";
18+
const QUINCEY_URL: &str = "QUINCEY_URL";
19+
const BUILDER_PORT: &str = "BUILDER_PORT";
20+
const SEQUENCER_KEY: &str = "SEQUENCER_KEY"; // empty (to use Quincey) OR AWS key ID (to use AWS signer) OR raw private key (to use local signer)
21+
const BUILDER_KEY: &str = "BUILDER_KEY"; // AWS key ID (to use AWS signer) OR raw private key (to use local signer)
22+
const INCOMING_TRANSACTIONS_BUFFER: &str = "INCOMING_TRANSACTIONS_BUFFER";
23+
const BLOCK_CONFIRMATION_BUFFER: &str = "BLOCK_CONFIRMATION_BUFFER";
24+
const BUILDER_REWARDS_ADDRESS: &str = "BUILDER_REWARDS_ADDRESS";
25+
const ROLLUP_BLOCK_GAS_LIMIT: &str = "ROLLUP_BLOCK_GAS_LIMIT";
26+
const TX_POOL_URL: &str = "TX_POOL_URL";
27+
const TX_POOL_POLL_INTERVAL: &str = "TX_POOL_POLL_INTERVAL";
28+
const TX_POOL_CACHE_DURATION: &str = "TX_POOL_CACHE_DURATION";
29+
const OAUTH_CLIENT_ID: &str = "OAUTH_CLIENT_ID";
30+
const OAUTH_CLIENT_SECRET: &str = "OAUTH_CLIENT_SECRET";
31+
const OAUTH_AUTHENTICATE_URL: &str = "OAUTH_AUTHENTICATE_URL";
32+
const OAUTH_TOKEN_URL: &str = "OAUTH_TOKEN_URL";
33+
const OAUTH_AUDIENCE: &str = "OAUTH_AUDIENCE";
34+
35+
/// Configuration for a builder running a specific rollup on a specific host
36+
/// chain.
37+
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
38+
pub struct BuilderConfig {
39+
/// The chain ID of the host chain
40+
pub host_chain_id: u64,
41+
/// The chain ID of the host chain
42+
pub ru_chain_id: u64,
43+
/// URL for Host RPC node.
44+
pub host_rpc_url: Cow<'static, str>,
45+
/// address of the Zenith contract on Host.
46+
pub zenith_address: Address,
47+
/// URL for remote Quincey Sequencer server to sign blocks.
48+
/// Disregarded if a sequencer_signer is configured.
49+
pub quincey_url: Cow<'static, str>,
50+
/// Port for the Builder server.
51+
pub builder_port: u16,
52+
/// Key to access Sequencer Wallet - AWS Key ID _OR_ local private key.
53+
/// Set IFF using local Sequencer signing instead of remote Quincey signing.
54+
pub sequencer_key: Option<String>,
55+
/// Key to access Builder transaction submission wallet - AWS Key ID _OR_ local private key.
56+
pub builder_key: String,
57+
/// Buffer in seconds that Builder will wait & accept incoming transactions before bundling them and submitting as a block.
58+
pub incoming_transactions_buffer: u64,
59+
/// Buffer in seconds in which the `submitBlock` transaction must confirm on the Host chain.
60+
pub block_confirmation_buffer: u64,
61+
/// Address on Rollup to which Builder will receive user transaction fees.
62+
pub builder_rewards_address: Address,
63+
/// Gas limit for RU block.
64+
/// NOTE: a "smart" builder would determine this programmatically by simulating the block.
65+
pub rollup_block_gas_limit: u64,
66+
/// URL of the tx pool to poll for incoming transactions.
67+
pub tx_pool_url: Cow<'static, str>,
68+
//// Interval in seconds to poll the tx-pool for new transactions.
69+
pub tx_pool_poll_interval: u64,
70+
/// Duration in seconds transactions can live in the tx-pool cache.
71+
pub tx_pool_cache_duration: u64,
72+
/// OAuth client ID for the builder.
73+
pub oauth_client_id: String,
74+
/// OAuth client secret for the builder.
75+
pub oauth_client_secret: String,
76+
/// OAuth authenticate URL for the builder for performing OAuth logins.
77+
pub oauth_authenticate_url: String,
78+
/// OAuth token URL for the builder to get an OAuth2 access token
79+
pub oauth_token_url: String,
80+
/// OAuth audience for the builder.
81+
pub oauth_audience: String,
82+
}
83+
84+
#[derive(Debug, thiserror::Error)]
85+
pub enum ConfigError {
86+
/// Error loading from environment variable
87+
#[error("missing or non-unicode environment variable: {0}")]
88+
Var(String),
89+
/// Error parsing environment variable
90+
#[error("failed to parse environment variable: {0}")]
91+
Parse(#[from] num::ParseIntError),
92+
/// Error parsing boolean environment variable
93+
#[error("failed to parse boolean environment variable")]
94+
ParseBool,
95+
/// Error parsing hex from environment variable
96+
#[error("failed to parse hex: {0}")]
97+
Hex(#[from] hex::FromHexError),
98+
/// Error connecting to the provider
99+
#[error("failed to connect to provider: {0}")]
100+
Provider(#[from] alloy::transports::TransportError),
101+
/// Error connecting to the signer
102+
#[error("failed to connect to signer: {0}")]
103+
Signer(#[from] SignerError),
104+
}
105+
106+
impl ConfigError {
107+
/// Missing or non-unicode env var.
108+
pub fn missing(s: &str) -> Self {
109+
ConfigError::Var(s.to_string())
110+
}
111+
}
112+
113+
/// Provider type used by this transaction
114+
pub type Provider = FillProvider<
115+
JoinFill<
116+
JoinFill<
117+
Identity,
118+
JoinFill<GasFiller, JoinFill<BlobGasFiller, JoinFill<NonceFiller, ChainIdFiller>>>,
119+
>,
120+
WalletFiller<EthereumWallet>,
121+
>,
122+
RootProvider<BoxTransport>,
123+
BoxTransport,
124+
Ethereum,
125+
>;
126+
127+
pub type ZenithInstance = Zenith::ZenithInstance<BoxTransport, Provider>;
128+
129+
impl BuilderConfig {
130+
/// Load the builder configuration from environment variables.
131+
pub fn load_from_env() -> Result<BuilderConfig, ConfigError> {
132+
Ok(BuilderConfig {
133+
host_chain_id: load_u64(HOST_CHAIN_ID)?,
134+
ru_chain_id: load_u64(RU_CHAIN_ID)?,
135+
host_rpc_url: load_url(HOST_RPC_URL)?,
136+
zenith_address: load_address(ZENITH_ADDRESS)?,
137+
quincey_url: load_url(QUINCEY_URL)?,
138+
builder_port: load_u16(BUILDER_PORT)?,
139+
sequencer_key: load_string_option(SEQUENCER_KEY),
140+
builder_key: load_string(BUILDER_KEY)?,
141+
incoming_transactions_buffer: load_u64(INCOMING_TRANSACTIONS_BUFFER)?,
142+
block_confirmation_buffer: load_u64(BLOCK_CONFIRMATION_BUFFER)?,
143+
builder_rewards_address: load_address(BUILDER_REWARDS_ADDRESS)?,
144+
rollup_block_gas_limit: load_u64(ROLLUP_BLOCK_GAS_LIMIT)?,
145+
tx_pool_url: load_url(TX_POOL_URL)?,
146+
tx_pool_poll_interval: load_u64(TX_POOL_POLL_INTERVAL)?,
147+
tx_pool_cache_duration: load_u64(TX_POOL_CACHE_DURATION)?,
148+
oauth_client_id: load_string(OAUTH_CLIENT_ID)?,
149+
oauth_client_secret: load_string(OAUTH_CLIENT_SECRET)?,
150+
oauth_authenticate_url: load_string(OAUTH_AUTHENTICATE_URL)?,
151+
oauth_token_url: load_string(OAUTH_TOKEN_URL)?,
152+
oauth_audience: load_string(OAUTH_AUDIENCE)?,
153+
})
154+
}
155+
156+
pub async fn connect_builder_signer(&self) -> Result<LocalOrAws, ConfigError> {
157+
LocalOrAws::load(&self.builder_key, Some(self.host_chain_id)).await.map_err(Into::into)
158+
}
159+
160+
pub async fn connect_sequencer_signer(&self) -> Result<Option<LocalOrAws>, ConfigError> {
161+
match &self.sequencer_key {
162+
Some(sequencer_key) => LocalOrAws::load(sequencer_key, Some(self.host_chain_id))
163+
.await
164+
.map_err(Into::into)
165+
.map(Some),
166+
None => Ok(None),
167+
}
168+
}
169+
170+
/// Connect to the provider using the configuration.
171+
pub async fn connect_provider(&self) -> Result<Provider, ConfigError> {
172+
let builder_signer = self.connect_builder_signer().await?;
173+
ProviderBuilder::new()
174+
.with_recommended_fillers()
175+
.wallet(EthereumWallet::from(builder_signer))
176+
.on_builtin(&self.host_rpc_url)
177+
.await
178+
.map_err(Into::into)
179+
}
180+
181+
pub fn connect_zenith(&self, provider: Provider) -> ZenithInstance {
182+
Zenith::new(self.zenith_address, provider)
183+
}
184+
}
185+
186+
fn load_string(key: &str) -> Result<String, ConfigError> {
187+
env::var(key).map_err(|_| ConfigError::missing(key))
188+
}
189+
190+
fn load_string_option(key: &str) -> Option<String> {
191+
load_string(key).ok()
192+
}
193+
194+
fn load_u64(key: &str) -> Result<u64, ConfigError> {
195+
let val = load_string(key)?;
196+
val.parse::<u64>().map_err(Into::into)
197+
}
198+
199+
fn load_u16(key: &str) -> Result<u16, ConfigError> {
200+
let val = load_string(key)?;
201+
val.parse::<u16>().map_err(Into::into)
202+
}
203+
204+
fn load_url(key: &str) -> Result<Cow<'static, str>, ConfigError> {
205+
load_string(key).map_err(Into::into).map(Into::into)
206+
}
207+
208+
fn load_address(key: &str) -> Result<Address, ConfigError> {
209+
let address = load_string(key)?;
210+
Address::from_str(&address).map_err(Into::into)
211+
}

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
pub mod config;
2+
pub mod service;
3+
pub mod signer;
4+
pub mod tasks;

0 commit comments

Comments
 (0)