Skip to content

Commit 7a9d99b

Browse files
authored
fix(taiko-client-rs): support enode:// bootnode URLs in whitelist preconfirmation driver (#21309)
1 parent 4414a7c commit 7a9d99b

File tree

1 file changed

+53
-2
lines changed
  • packages/taiko-client-rs/crates/whitelist-preconfirmation-driver/src

1 file changed

+53
-2
lines changed

packages/taiko-client-rs/crates/whitelist-preconfirmation-driver/src/network.rs

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,20 @@ pub(crate) fn build_gossipsub() -> Result<gossipsub::Behaviour> {
373373
gossipsub::Behaviour::new(gossipsub::MessageAuthenticity::Anonymous, config).map_err(to_p2p_err)
374374
}
375375

376+
/// Parse an `enode://` URL into a multiaddr for direct dialing.
377+
///
378+
/// Accepts `enode://<hex-pubkey>@<ip>:<tcp-port>[?discport=<udp>]` and returns
379+
/// `/ip4/{ip}/tcp/{port}` (or `/ip6/…`). The pubkey and optional discport query
380+
/// are intentionally ignored — we only need the TCP dial address.
381+
fn parse_enode_url(url: &str) -> Option<Multiaddr> {
382+
let rest = url.strip_prefix("enode://")?;
383+
let (_, host_part) = rest.split_once('@')?;
384+
let host_port = host_part.split('?').next()?;
385+
let sock: std::net::SocketAddr = host_port.parse().ok()?;
386+
let scheme = if sock.ip().is_ipv4() { "ip4" } else { "ip6" };
387+
format!("/{scheme}/{}/tcp/{}", sock.ip(), sock.port()).parse().ok()
388+
}
389+
376390
/// Classify bootnodes into direct-dial multiaddrs and discovery ENRs.
377391
fn classify_bootnodes(bootnodes: Vec<String>) -> ClassifiedBootnodes {
378392
let mut classified = ClassifiedBootnodes::default();
@@ -388,13 +402,21 @@ fn classify_bootnodes(bootnodes: Vec<String>) -> ClassifiedBootnodes {
388402
continue;
389403
}
390404

405+
if value.starts_with("enode://") {
406+
match parse_enode_url(value) {
407+
Some(addr) => classified.dial_addrs.push(addr),
408+
None => warn!(bootnode = %value, "failed to parse enode:// URL"),
409+
}
410+
continue;
411+
}
412+
391413
match value.parse::<Multiaddr>() {
392414
Ok(addr) => classified.dial_addrs.push(addr),
393415
Err(err) => {
394416
warn!(
395417
bootnode = %value,
396418
error = %err,
397-
"invalid bootnode entry; expected ENR or multiaddr"
419+
"invalid bootnode entry; expected ENR, enode://, or multiaddr"
398420
);
399421
}
400422
}
@@ -726,20 +748,49 @@ mod tests {
726748
assert_ne!(valid_id, changed_topic_id);
727749
}
728750

751+
#[test]
752+
fn parse_enode_url_valid_ipv4() {
753+
let url = "enode://a3f84d16471e6d8a0dc1e2d62f7a9c5b3e4f5678901234567890abcdef123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567@10.0.1.5:30303?discport=30304";
754+
let addr = parse_enode_url(url).expect("should parse valid enode URL");
755+
assert_eq!(addr.to_string(), "/ip4/10.0.1.5/tcp/30303");
756+
}
757+
758+
#[test]
759+
fn parse_enode_url_valid_ipv4_no_query() {
760+
let url = "enode://abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890@192.168.1.1:30303";
761+
let addr = parse_enode_url(url).expect("should parse enode URL without query");
762+
assert_eq!(addr.to_string(), "/ip4/192.168.1.1/tcp/30303");
763+
}
764+
765+
#[test]
766+
fn parse_enode_url_invalid_inputs() {
767+
assert!(parse_enode_url("enr:-IS4QO3Qh8n0").is_none(), "ENR should not parse as enode");
768+
assert!(parse_enode_url("enode://no-at-sign").is_none(), "missing @ should fail");
769+
assert!(
770+
parse_enode_url("enode://abc@not-a-socket-addr").is_none(),
771+
"bad host:port should fail"
772+
);
773+
assert!(parse_enode_url("/ip4/127.0.0.1/tcp/9000").is_none(), "multiaddr should fail");
774+
assert!(parse_enode_url("").is_none(), "empty string should fail");
775+
}
776+
729777
#[test]
730778
fn classify_bootnodes_splits_enr_and_multiaddr_entries() {
731779
let input = vec![
732780
"/ip4/127.0.0.1/tcp/9000/p2p/12D3KooWEhXfLw7BrTHr2VfVki6jPiKG8AqfXw3hNziR6mM2Mz4s"
733781
.to_string(),
734782
"enr:-IS4QO3Qh8n0cxb5KJ9f5Xx8t9wq2fS28uFh8gJQ6KxJxRk6J1V1kWQ5g6nAiJmK8P8e9Z5hY3rP0mFf6vM1Sxg6W4qGAYN1ZHCCdl8"
735783
.to_string(),
784+
"enode://a3f84d16471e6d8a0dc1e2d62f7a9c5b3e4f5678901234567890abcdef123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567@10.0.1.5:30303?discport=30304"
785+
.to_string(),
736786
"not-a-valid-bootnode".to_string(),
737787
];
738788

739789
let parsed = classify_bootnodes(input);
740790

741-
assert_eq!(parsed.dial_addrs.len(), 1);
791+
assert_eq!(parsed.dial_addrs.len(), 2, "should have multiaddr + enode dial addresses");
742792
assert_eq!(parsed.discovery_enrs.len(), 1);
793+
assert_eq!(parsed.dial_addrs[1].to_string(), "/ip4/10.0.1.5/tcp/30303");
743794
}
744795

745796
#[tokio::test]

0 commit comments

Comments
 (0)