Skip to content

Commit d22c29b

Browse files
authored
Finish block building process even when cancel request is found (#606)
## 📝 Summary This PR introduces three changes: - It refactors some of the common utilities shared among the tests into a single `TestHarness` struct which spins the test framework with all the components. Note that it does not update the tests to use this new utility. - It fixes an issue with the block builder that would stop the block building process and not return any block if a cancel request was found. This happens when an FCU and a getPayload request are called to close to each other, the getPayload cancels the block building process, and getPayload waits forever for a block that will never be built. Now, the block building finishes. - It adds an integration test to cover this use case with the new utility. ## 💡 Motivation and Context <!--- (Optional) Why is this change required? What problem does it solve? Remove this section if not applicable. --> --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable)
1 parent 9a4f6b6 commit d22c29b

File tree

7 files changed

+207
-36
lines changed

7 files changed

+207
-36
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/op-rbuilder/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ metrics.workspace = true
8080
serde_json.workspace = true
8181
tokio-util.workspace = true
8282
thiserror.workspace = true
83+
parking_lot.workspace = true
8384

8485
time = { version = "0.3.36", features = ["macros", "formatting", "parsing"] }
8586
chrono = "0.4"

crates/op-rbuilder/src/integration/integration_test.rs

+28-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#[cfg(all(test, feature = "integration"))]
22
mod tests {
33
use crate::{
4-
integration::{op_rbuilder::OpRbuilderConfig, op_reth::OpRethConfig, IntegrationFramework},
4+
integration::{
5+
op_rbuilder::OpRbuilderConfig, op_reth::OpRethConfig, IntegrationFramework, TestHarness,
6+
},
57
tester::{BlockGenerator, EngineApi},
68
tx_signer::Signer,
79
};
@@ -66,7 +68,7 @@ mod tests {
6668
let engine_api = EngineApi::new("http://localhost:1234").unwrap();
6769
let validation_api = EngineApi::new("http://localhost:1236").unwrap();
6870

69-
let mut generator = BlockGenerator::new(&engine_api, Some(&validation_api), false, 1, None);
71+
let mut generator = BlockGenerator::new(engine_api, Some(validation_api), false, 1, None);
7072
generator.init().await?;
7173

7274
let provider = ProviderBuilder::<Identity, Identity, Optimism>::default()
@@ -138,7 +140,7 @@ mod tests {
138140
let engine_api = EngineApi::new("http://localhost:1244").unwrap();
139141
let validation_api = EngineApi::new("http://localhost:1246").unwrap();
140142

141-
let mut generator = BlockGenerator::new(&engine_api, Some(&validation_api), false, 1, None);
143+
let mut generator = BlockGenerator::new(engine_api, Some(validation_api), false, 1, None);
142144
let latest_block = generator.init().await?;
143145

144146
let provider = ProviderBuilder::<Identity, Identity, Optimism>::default()
@@ -263,7 +265,7 @@ mod tests {
263265
let engine_api = EngineApi::new("http://localhost:1264").unwrap();
264266
let validation_api = EngineApi::new("http://localhost:1266").unwrap();
265267

266-
let mut generator = BlockGenerator::new(&engine_api, Some(&validation_api), false, 1, None);
268+
let mut generator = BlockGenerator::new(engine_api, Some(validation_api), false, 1, None);
267269
let latest_block = generator.init().await?;
268270

269271
let provider = ProviderBuilder::<Identity, Identity, Optimism>::default()
@@ -349,6 +351,25 @@ mod tests {
349351
Ok(())
350352
}
351353

354+
#[tokio::test]
355+
#[cfg(not(feature = "flashblocks"))]
356+
async fn integration_test_get_payload_close_to_fcu() -> eyre::Result<()> {
357+
let test_harness = TestHarness::new("integration_test_get_payload_close_to_fcu").await;
358+
let mut block_generator = test_harness.block_generator().await?;
359+
360+
// add some transactions to the pool so that the builder is busy when we send the fcu/getPayload requests
361+
for _ in 0..10 {
362+
// Note, for this test it is okay if they are not valid
363+
test_harness.send_valid_transaction().await?;
364+
}
365+
366+
// TODO: In the fail case scenario, this hangs forever, but it should return an error
367+
// Figure out how to do timeout (i.e. 1s) on the engine api.
368+
block_generator.submit_payload(None, 0, true).await?;
369+
370+
Ok(())
371+
}
372+
352373
#[tokio::test]
353374
#[cfg(feature = "flashblocks")]
354375
async fn integration_test_chain_produces_blocks() -> eyre::Result<()> {
@@ -410,7 +431,7 @@ mod tests {
410431
let engine_api = EngineApi::new("http://localhost:1234").unwrap();
411432
let validation_api = EngineApi::new("http://localhost:1236").unwrap();
412433

413-
let mut generator = BlockGenerator::new(&engine_api, Some(&validation_api), false, 2, None);
434+
let mut generator = BlockGenerator::new(engine_api, Some(validation_api), false, 2, None);
414435
generator.init().await?;
415436

416437
let provider = ProviderBuilder::<Identity, Identity, Optimism>::default()
@@ -539,8 +560,8 @@ mod tests {
539560
let validation_api = EngineApi::new("http://localhost:1246").unwrap();
540561

541562
let mut generator = BlockGenerator::new(
542-
&engine_api,
543-
Some(&validation_api),
563+
engine_api,
564+
Some(validation_api),
544565
false,
545566
block_time_ms / 1000,
546567
None,

crates/op-rbuilder/src/integration/mod.rs

+136
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
1+
use alloy_consensus::TxEip1559;
2+
use alloy_eips::BlockNumberOrTag;
3+
use alloy_eips::{eip1559::MIN_PROTOCOL_BASE_FEE, eip2718::Encodable2718};
4+
use alloy_provider::{Identity, Provider, ProviderBuilder};
5+
use op_alloy_consensus::OpTypedTransaction;
6+
use op_alloy_network::Optimism;
7+
use op_rbuilder::OpRbuilderConfig;
8+
use op_reth::OpRethConfig;
9+
use parking_lot::Mutex;
10+
use std::cmp::max;
11+
use std::collections::HashSet;
112
use std::future::Future;
13+
use std::net::TcpListener;
214
use std::path::Path;
15+
use std::sync::LazyLock;
316
use std::{
417
fs::{File, OpenOptions},
518
io,
@@ -10,6 +23,10 @@ use std::{
1023
};
1124
use time::{format_description, OffsetDateTime};
1225
use tokio::time::sleep;
26+
use uuid::Uuid;
27+
28+
use crate::tester::{BlockGenerator, EngineApi};
29+
use crate::tx_signer::Signer;
1330

1431
/// Default JWT token for testing purposes
1532
pub const DEFAULT_JWT_TOKEN: &str =
@@ -195,3 +212,122 @@ impl Drop for IntegrationFramework {
195212
}
196213
}
197214
}
215+
216+
const BUILDER_PRIVATE_KEY: &str =
217+
"0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d";
218+
219+
pub struct TestHarness {
220+
_framework: IntegrationFramework,
221+
builder_auth_rpc_port: u16,
222+
builder_http_port: u16,
223+
validator_auth_rpc_port: u16,
224+
}
225+
226+
impl TestHarness {
227+
pub async fn new(name: &str) -> Self {
228+
let mut framework = IntegrationFramework::new(name).unwrap();
229+
230+
// we are going to use a genesis file pre-generated before the test
231+
let mut genesis_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
232+
genesis_path.push("../../genesis.json");
233+
assert!(genesis_path.exists());
234+
235+
// create the builder
236+
let builder_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string());
237+
let builder_auth_rpc_port = get_available_port();
238+
let builder_http_port = get_available_port();
239+
let op_rbuilder_config = OpRbuilderConfig::new()
240+
.chain_config_path(genesis_path.clone())
241+
.data_dir(builder_data_dir)
242+
.auth_rpc_port(builder_auth_rpc_port)
243+
.network_port(get_available_port())
244+
.http_port(builder_http_port)
245+
.with_builder_private_key(BUILDER_PRIVATE_KEY);
246+
247+
// create the validation reth node
248+
249+
let reth_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string());
250+
let validator_auth_rpc_port = get_available_port();
251+
let reth = OpRethConfig::new()
252+
.chain_config_path(genesis_path)
253+
.data_dir(reth_data_dir)
254+
.auth_rpc_port(validator_auth_rpc_port)
255+
.network_port(get_available_port());
256+
257+
framework.start("op-reth", &reth).await.unwrap();
258+
259+
let _ = framework
260+
.start("op-rbuilder", &op_rbuilder_config)
261+
.await
262+
.unwrap();
263+
264+
Self {
265+
_framework: framework,
266+
builder_auth_rpc_port,
267+
builder_http_port,
268+
validator_auth_rpc_port,
269+
}
270+
}
271+
272+
pub async fn send_valid_transaction(&self) -> eyre::Result<()> {
273+
// Get builder's address
274+
let known_wallet = Signer::try_from_secret(BUILDER_PRIVATE_KEY.parse()?)?;
275+
let builder_address = known_wallet.address;
276+
277+
let url = format!("http://localhost:{}", self.builder_http_port);
278+
let provider =
279+
ProviderBuilder::<Identity, Identity, Optimism>::default().on_http(url.parse()?);
280+
281+
// Get current nonce includeing the ones from the txpool
282+
let nonce = provider
283+
.get_transaction_count(builder_address)
284+
.pending()
285+
.await?;
286+
287+
let latest_block = provider
288+
.get_block_by_number(BlockNumberOrTag::Latest)
289+
.await?
290+
.unwrap();
291+
292+
let base_fee = max(
293+
latest_block.header.base_fee_per_gas.unwrap(),
294+
MIN_PROTOCOL_BASE_FEE,
295+
);
296+
297+
// Transaction from builder should succeed
298+
let tx_request = OpTypedTransaction::Eip1559(TxEip1559 {
299+
chain_id: 901,
300+
nonce,
301+
gas_limit: 210000,
302+
max_fee_per_gas: base_fee.into(),
303+
..Default::default()
304+
});
305+
let signed_tx = known_wallet.sign_tx(tx_request)?;
306+
let _ = provider
307+
.send_raw_transaction(signed_tx.encoded_2718().as_slice())
308+
.await?;
309+
310+
Ok(())
311+
}
312+
313+
pub async fn block_generator(&self) -> eyre::Result<BlockGenerator> {
314+
let engine_api = EngineApi::new_with_port(self.builder_auth_rpc_port).unwrap();
315+
let validation_api = Some(EngineApi::new_with_port(self.validator_auth_rpc_port).unwrap());
316+
317+
let mut generator = BlockGenerator::new(engine_api, validation_api, false, 1, None);
318+
generator.init().await?;
319+
320+
Ok(generator)
321+
}
322+
}
323+
324+
pub fn get_available_port() -> u16 {
325+
static CLAIMED_PORTS: LazyLock<Mutex<HashSet<u16>>> =
326+
LazyLock::new(|| Mutex::new(HashSet::new()));
327+
loop {
328+
let port: u16 = rand::random_range(1000..20000);
329+
if TcpListener::bind(("127.0.0.1", port)).is_ok() && CLAIMED_PORTS.lock().insert(port) {
330+
return port;
331+
}
332+
}
333+
}

crates/op-rbuilder/src/payload_builder_vanilla.rs

+7-12
Original file line numberDiff line numberDiff line change
@@ -514,18 +514,13 @@ impl<Txs> OpBuilder<'_, Txs> {
514514
ctx.metrics
515515
.transaction_pool_fetch_duration
516516
.record(best_txs_start_time.elapsed());
517-
if ctx
518-
.execute_best_transactions(
519-
&mut info,
520-
state,
521-
best_txs,
522-
block_gas_limit,
523-
block_da_limit,
524-
)?
525-
.is_some()
526-
{
527-
return Ok(BuildOutcomeKind::Cancelled);
528-
}
517+
ctx.execute_best_transactions(
518+
&mut info,
519+
state,
520+
best_txs,
521+
block_gas_limit,
522+
block_da_limit,
523+
)?;
529524
}
530525

531526
// Add builder tx to the block

crates/op-rbuilder/src/tester/main.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ async fn main() -> eyre::Result<()> {
6262
}
6363
Commands::Deposit { address, amount } => {
6464
let engine_api = EngineApi::builder().build().unwrap();
65-
let mut generator = BlockGenerator::new(&engine_api, None, false, 1, None);
65+
let mut generator = BlockGenerator::new(engine_api, None, false, 1, None);
6666

6767
generator.init().await?;
6868

0 commit comments

Comments
 (0)