Skip to content

Commit 32a6998

Browse files
authored
feat: filter confirmed transactions from the block (#31)
* feat: filter confirmed transactions from the block * add tracing * rm mut self * fix: imports * update log levels * update: don't connect signer to rollup provider * fix: no need to clone * comments * fix: use dummy rpcs
1 parent dfa90a0 commit 32a6998

File tree

7 files changed

+102
-38
lines changed

7 files changed

+102
-38
lines changed

bin/builder.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,21 @@ async fn main() -> eyre::Result<()> {
1515
let span = tracing::info_span!("zenith-builder");
1616

1717
let config = BuilderConfig::load_from_env()?.clone();
18-
let provider = config.connect_provider().await?;
18+
let host_provider = config.connect_host_provider().await?;
19+
let ru_provider = config.connect_ru_provider().await?;
1920
let authenticator = Authenticator::new(&config);
2021

2122
PrometheusBuilder::new().install().expect("failed to install prometheus exporter");
2223

2324
tracing::debug!(rpc_url = config.host_rpc_url.as_ref(), "instantiated provider");
2425

2526
let sequencer_signer = config.connect_sequencer_signer().await?;
26-
let zenith = config.connect_zenith(provider.clone());
27+
let zenith = config.connect_zenith(host_provider.clone());
2728

28-
let builder = BlockBuilder::new(&config, authenticator.clone());
29+
let builder = BlockBuilder::new(&config, authenticator.clone(), ru_provider);
2930
let submit = SubmitTask {
3031
authenticator: authenticator.clone(),
31-
provider,
32+
host_provider,
3233
zenith,
3334
client: reqwest::Client::new(),
3435
sequencer_signer,

src/config.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use zenith_types::Zenith;
1414
const HOST_CHAIN_ID: &str = "HOST_CHAIN_ID";
1515
const RU_CHAIN_ID: &str = "RU_CHAIN_ID";
1616
const HOST_RPC_URL: &str = "HOST_RPC_URL";
17+
const ROLLUP_RPC_URL: &str = "ROLLUP_RPC_URL";
1718
const TX_BROADCAST_URLS: &str = "TX_BROADCAST_URLS";
1819
const ZENITH_ADDRESS: &str = "ZENITH_ADDRESS";
1920
const QUINCEY_URL: &str = "QUINCEY_URL";
@@ -44,6 +45,8 @@ pub struct BuilderConfig {
4445
pub ru_chain_id: u64,
4546
/// URL for Host RPC node.
4647
pub host_rpc_url: Cow<'static, str>,
48+
/// URL for the Rollup RPC node.
49+
pub ru_rpc_url: Cow<'static, str>,
4750
/// Additional RPC URLs to which to broadcast transactions.
4851
pub tx_broadcast_urls: Vec<Cow<'static, str>>,
4952
/// address of the Zenith contract on Host.
@@ -116,7 +119,7 @@ impl ConfigError {
116119
}
117120
}
118121

119-
/// Provider type used by this transaction
122+
/// Provider type used to read & write.
120123
pub type Provider = FillProvider<
121124
JoinFill<
122125
JoinFill<
@@ -130,6 +133,17 @@ pub type Provider = FillProvider<
130133
Ethereum,
131134
>;
132135

136+
/// Provider type used to read-only.
137+
pub type WalletlessProvider = FillProvider<
138+
JoinFill<
139+
Identity,
140+
JoinFill<GasFiller, JoinFill<BlobGasFiller, JoinFill<NonceFiller, ChainIdFiller>>>,
141+
>,
142+
RootProvider<BoxTransport>,
143+
BoxTransport,
144+
Ethereum,
145+
>;
146+
133147
pub type ZenithInstance = Zenith::ZenithInstance<BoxTransport, Provider>;
134148

135149
impl BuilderConfig {
@@ -139,6 +153,7 @@ impl BuilderConfig {
139153
host_chain_id: load_u64(HOST_CHAIN_ID)?,
140154
ru_chain_id: load_u64(RU_CHAIN_ID)?,
141155
host_rpc_url: load_url(HOST_RPC_URL)?,
156+
ru_rpc_url: load_url(ROLLUP_RPC_URL)?,
142157
tx_broadcast_urls: env::var(TX_BROADCAST_URLS)
143158
.unwrap_or_default()
144159
.split(',')
@@ -180,8 +195,17 @@ impl BuilderConfig {
180195
}
181196
}
182197

183-
/// Connect to the provider using the configuration.
184-
pub async fn connect_provider(&self) -> Result<Provider, ConfigError> {
198+
/// Connect to the Rollup rpc provider.
199+
pub async fn connect_ru_provider(&self) -> Result<WalletlessProvider, ConfigError> {
200+
ProviderBuilder::new()
201+
.with_recommended_fillers()
202+
.on_builtin(&self.ru_rpc_url)
203+
.await
204+
.map_err(Into::into)
205+
}
206+
207+
/// Connect to the Host rpc provider.
208+
pub async fn connect_host_provider(&self) -> Result<Provider, ConfigError> {
185209
let builder_signer = self.connect_builder_signer().await?;
186210
ProviderBuilder::new()
187211
.with_recommended_fillers()

src/tasks/block.rs

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
use super::bundler::{Bundle, BundlePoller};
2+
use super::oauth::Authenticator;
3+
use super::tx_poller::TxPoller;
4+
use crate::config::{BuilderConfig, WalletlessProvider};
5+
use alloy::providers::Provider;
16
use alloy::{
27
consensus::{SidecarBuilder, SidecarCoder, TxEnvelope},
38
eips::eip2718::Decodable2718,
@@ -10,19 +15,13 @@ use tokio::{sync::mpsc, task::JoinHandle};
1015
use tracing::Instrument;
1116
use zenith_types::{encode_txns, Alloy2718Coder};
1217

13-
use super::bundler::{Bundle, BundlePoller};
14-
use super::oauth::Authenticator;
15-
use super::tx_poller::TxPoller;
16-
use crate::config::BuilderConfig;
17-
1818
/// Ethereum's slot time in seconds.
1919
pub const ETHEREUM_SLOT_TIME: u64 = 12;
2020

2121
#[derive(Debug, Default, Clone)]
2222
/// A block in progress.
2323
pub struct InProgressBlock {
2424
transactions: Vec<TxEnvelope>,
25-
2625
raw_encoding: OnceLock<Bytes>,
2726
hash: OnceLock<B256>,
2827
}
@@ -57,15 +56,22 @@ impl InProgressBlock {
5756

5857
/// Ingest a transaction into the in-progress block. Fails
5958
pub fn ingest_tx(&mut self, tx: &TxEnvelope) {
60-
tracing::info!(hash = %tx.tx_hash(), "ingesting tx");
59+
tracing::trace!(hash = %tx.tx_hash(), "ingesting tx");
6160
self.unseal();
6261
self.transactions.push(tx.clone());
6362
}
6463

64+
/// Remove a transaction from the in-progress block.
65+
pub fn remove_tx(&mut self, tx: &TxEnvelope) {
66+
tracing::trace!(hash = %tx.tx_hash(), "removing tx");
67+
self.unseal();
68+
self.transactions.retain(|t| t.tx_hash() != tx.tx_hash());
69+
}
70+
6571
/// Ingest a bundle into the in-progress block.
6672
/// Ignores Signed Orders for now.
6773
pub fn ingest_bundle(&mut self, bundle: Bundle) {
68-
tracing::info!(bundle = %bundle.id, "ingesting bundle");
74+
tracing::trace!(bundle = %bundle.id, "ingesting bundle");
6975

7076
let txs = bundle
7177
.bundle
@@ -113,26 +119,32 @@ impl InProgressBlock {
113119
/// BlockBuilder is a task that periodically builds a block then sends it for signing and submission.
114120
pub struct BlockBuilder {
115121
pub config: BuilderConfig,
122+
pub ru_provider: WalletlessProvider,
116123
pub tx_poller: TxPoller,
117124
pub bundle_poller: BundlePoller,
118125
}
119126

120127
impl BlockBuilder {
121128
// create a new block builder with the given config.
122-
pub fn new(config: &BuilderConfig, authenticator: Authenticator) -> Self {
129+
pub fn new(
130+
config: &BuilderConfig,
131+
authenticator: Authenticator,
132+
ru_provider: WalletlessProvider,
133+
) -> Self {
123134
Self {
124135
config: config.clone(),
136+
ru_provider,
125137
tx_poller: TxPoller::new(config),
126138
bundle_poller: BundlePoller::new(config, authenticator),
127139
}
128140
}
129141

130142
async fn get_transactions(&mut self, in_progress: &mut InProgressBlock) {
131-
tracing::info!("query transactions from cache");
143+
tracing::trace!("query transactions from cache");
132144
let txns = self.tx_poller.check_tx_cache().await;
133145
match txns {
134146
Ok(txns) => {
135-
tracing::info!("got transactions response");
147+
tracing::trace!("got transactions response");
136148
for txn in txns.into_iter() {
137149
in_progress.ingest_tx(&txn);
138150
}
@@ -145,11 +157,11 @@ impl BlockBuilder {
145157
}
146158

147159
async fn _get_bundles(&mut self, in_progress: &mut InProgressBlock) {
148-
tracing::info!("query bundles from cache");
160+
tracing::trace!("query bundles from cache");
149161
let bundles = self.bundle_poller.check_bundle_cache().await;
150162
match bundles {
151163
Ok(bundles) => {
152-
tracing::info!("got bundles response");
164+
tracing::trace!("got bundles response");
153165
for bundle in bundles {
154166
in_progress.ingest_bundle(bundle);
155167
}
@@ -161,15 +173,36 @@ impl BlockBuilder {
161173
self.bundle_poller.evict();
162174
}
163175

176+
async fn filter_transactions(&self, in_progress: &mut InProgressBlock) {
177+
// query the rollup node to see which transaction(s) have been included
178+
let mut confirmed_transactions = Vec::new();
179+
for transaction in in_progress.transactions.iter() {
180+
let tx = self
181+
.ru_provider
182+
.get_transaction_by_hash(*transaction.tx_hash())
183+
.await
184+
.expect("failed to get receipt");
185+
if tx.is_some() {
186+
confirmed_transactions.push(transaction.clone());
187+
}
188+
}
189+
tracing::trace!(confirmed = confirmed_transactions.len(), "found confirmed transactions");
190+
191+
// remove already-confirmed transactions
192+
for transaction in confirmed_transactions {
193+
in_progress.remove_tx(&transaction);
194+
}
195+
}
196+
164197
// calculate the duration in seconds until the beginning of the next block slot.
165-
fn secs_to_next_slot(&mut self) -> u64 {
198+
fn secs_to_next_slot(&self) -> u64 {
166199
let curr_timestamp: u64 = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
167200
let current_slot_time = (curr_timestamp - self.config.chain_offset) % ETHEREUM_SLOT_TIME;
168201
(ETHEREUM_SLOT_TIME - current_slot_time) % ETHEREUM_SLOT_TIME
169202
}
170203

171204
// add a buffer to the beginning of the block slot.
172-
fn secs_to_next_target(&mut self) -> u64 {
205+
fn secs_to_next_target(&self) -> u64 {
173206
self.secs_to_next_slot() + self.config.target_slot_time
174207
}
175208

@@ -190,16 +223,19 @@ impl BlockBuilder {
190223
// TODO: Implement bundle ingestion #later
191224
// self.get_bundles(&mut in_progress).await;
192225

226+
// Filter confirmed transactions from the block
227+
self.filter_transactions(&mut in_progress).await;
228+
193229
// submit the block if it has transactions
194230
if !in_progress.is_empty() {
195-
tracing::info!(txns = in_progress.len(), "sending block to submit task");
231+
tracing::debug!(txns = in_progress.len(), "sending block to submit task");
196232
let in_progress_block = std::mem::take(&mut in_progress);
197233
if outbound.send(in_progress_block).is_err() {
198-
tracing::debug!("downstream task gone");
234+
tracing::error!("downstream task gone");
199235
break;
200236
}
201237
} else {
202-
tracing::info!("no transactions, skipping block submission");
238+
tracing::debug!("no transactions, skipping block submission");
203239
}
204240
}
205241
}

src/tasks/oauth.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ mod tests {
148148
let config = BuilderConfig {
149149
host_chain_id: 17000,
150150
ru_chain_id: 17001,
151-
host_rpc_url: "http://rpc.holesky.signet.sh".into(),
151+
host_rpc_url: "host-rpc.example.com".into(),
152+
ru_rpc_url: "ru-rpc.example.com".into(),
152153
zenith_address: Address::default(),
153154
quincey_url: "http://localhost:8080".into(),
154155
builder_port: 8080,

src/tasks/submit.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ pub enum ControlFlow {
5252
/// Submits sidecars in ethereum txns to mainnet ethereum
5353
pub struct SubmitTask {
5454
/// Ethereum Provider
55-
pub provider: Provider,
55+
pub host_provider: Provider,
5656
/// Zenith
5757
pub zenith: ZenithInstance,
5858
/// Reqwest
@@ -127,7 +127,7 @@ impl SubmitTask {
127127
}
128128

129129
async fn next_host_block_height(&self) -> eyre::Result<u64> {
130-
let result = self.provider.get_block_number().await?;
130+
let result = self.host_provider.get_block_number().await?;
131131
let next = result.checked_add(1).ok_or_else(|| eyre!("next host block height overflow"))?;
132132
Ok(next)
133133
}
@@ -152,12 +152,12 @@ impl SubmitTask {
152152

153153
let tx = self
154154
.build_blob_tx(header, v, r, s, in_progress)?
155-
.with_from(self.provider.default_signer_address())
155+
.with_from(self.host_provider.default_signer_address())
156156
.with_to(self.config.zenith_address)
157157
.with_gas_limit(1_000_000);
158158

159159
if let Err(TransportError::ErrorResp(e)) =
160-
self.provider.call(&tx).block(BlockNumberOrTag::Pending.into()).await
160+
self.host_provider.call(&tx).block(BlockNumberOrTag::Pending.into()).await
161161
{
162162
error!(
163163
code = e.code,
@@ -186,16 +186,16 @@ impl SubmitTask {
186186
"sending transaction to network"
187187
);
188188

189-
let SendableTx::Envelope(tx) = self.provider.fill(tx).await? else {
189+
let SendableTx::Envelope(tx) = self.host_provider.fill(tx).await? else {
190190
bail!("failed to fill transaction")
191191
};
192192

193-
// Send the tx via the primary provider
194-
let fut = spawn_provider_send!(&self.provider, &tx);
193+
// Send the tx via the primary host_provider
194+
let fut = spawn_provider_send!(&self.host_provider, &tx);
195195

196-
// Spawn send_tx futures for all additional broadcast providers
197-
for provider in self.config.connect_additional_broadcast().await? {
198-
spawn_provider_send!(&provider, &tx);
196+
// Spawn send_tx futures for all additional broadcast host_providers
197+
for host_provider in self.config.connect_additional_broadcast().await? {
198+
spawn_provider_send!(&host_provider, &tx);
199199
}
200200

201201
// question mark unwraps join error, which would be an internal panic

tests/bundle_poller_test.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ mod tests {
2020
let config = BuilderConfig {
2121
host_chain_id: 17000,
2222
ru_chain_id: 17001,
23-
host_rpc_url: "http://rpc.holesky.signet.sh".into(),
23+
host_rpc_url: "host-rpc.example.com".into(),
24+
ru_rpc_url: "ru-rpc.example.com".into(),
2425
zenith_address: Address::default(),
2526
quincey_url: "http://localhost:8080".into(),
2627
builder_port: 8080,

tests/tx_poller_test.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ mod tests {
6767
let config = BuilderConfig {
6868
host_chain_id: 17000,
6969
ru_chain_id: 17001,
70-
host_rpc_url: "http://rpc.holesky.signet.sh".into(),
70+
host_rpc_url: "host-rpc.example.com".into(),
71+
ru_rpc_url: "ru-rpc.example.com".into(),
7172
tx_broadcast_urls: vec!["http://localhost:9000".into()],
7273
zenith_address: Address::default(),
7374
quincey_url: "http://localhost:8080".into(),

0 commit comments

Comments
 (0)