diff --git a/changelog.d/clock-step2.md b/changelog.d/clock-step2.md new file mode 100644 index 000000000..cfe14f839 --- /dev/null +++ b/changelog.d/clock-step2.md @@ -0,0 +1,3 @@ +Changed + +- **net: PeerHub uses NodeClock, drop second mock battery (Step 2).** PeerHub no longer has its own mock clock; it holds NodeClock. `set_mock` sets the clock and calls `on_clock_jump()` which requests tx INVs, runs heartbeat, then queues self-announce on live peers. TxRelay still has its own mock clock; setmocktime still notifies it. That is Step 3. diff --git a/crates/rbitcoin-net/src/chain.rs b/crates/rbitcoin-net/src/chain.rs index 61e0cb6d0..4ecbe2ab2 100644 --- a/crates/rbitcoin-net/src/chain.rs +++ b/crates/rbitcoin-net/src/chain.rs @@ -8,6 +8,8 @@ use crate::error::NetError; use bitcoin::block::Header; use bitcoin::hashes::Hash; use bitcoin::{Block, BlockHash, CompactTarget, ScriptBuf, Target, Transaction, Txid, Work}; +#[allow(unused_imports)] +use rbitcoin_consensus::NodeClock; use rbitcoin_consensus::{ accept_and_connect_block_preverified, confirm_wire_load_from_plan as consensus_load_from_plan, confirm_wire_load_phase_pipelined, confirm_write_phase, genesis_block, header_to_record, @@ -360,7 +362,7 @@ impl ChainHub { generate_lock: std::sync::Mutex::new(()), mempool: std::sync::OnceLock::new(), self_weak: std::sync::OnceLock::new(), - clock: rbitcoin_consensus::NodeClock::new(), + clock: NodeClock::new(), invalidated: Invalidated::new(), held_bodies: RwLock::new(HeldBodies::new()), precious: RwLock::new(None), diff --git a/crates/rbitcoin-net/src/ephemeral.rs b/crates/rbitcoin-net/src/ephemeral.rs index ac24b4722..4e0b3e22d 100644 --- a/crates/rbitcoin-net/src/ephemeral.rs +++ b/crates/rbitcoin-net/src/ephemeral.rs @@ -422,7 +422,7 @@ mod tests { relay: true, }; let (tx, mut rx) = mpsc::unbounded_channel(); - let peers = crate::peers::PeerHub::new(); + let peers = crate::peers::PeerHub::new(rbitcoin_consensus::NodeClock::new()); let sess = peers.register( addr, addr, diff --git a/crates/rbitcoin-net/src/overlay_addrman_journey.rs b/crates/rbitcoin-net/src/overlay_addrman_journey.rs index a2cb36c84..0bbfd5c2e 100644 --- a/crates/rbitcoin-net/src/overlay_addrman_journey.rs +++ b/crates/rbitcoin-net/src/overlay_addrman_journey.rs @@ -174,7 +174,7 @@ fn only_net_dials_and_peers_file(am: &Mutex, overlays: [c #[test] fn overlay_config() { dial_targets_roundtrip(); - let hub = PeerHub::new(); + let hub = PeerHub::new(rbitcoin_consensus::NodeClock::new()); let am = Arc::new(Mutex::new(crate::seeds::AddrMan::new())); hub.set_addrman(am.clone()); let pk = [ diff --git a/crates/rbitcoin-net/src/peer_blocksonly_journey.rs b/crates/rbitcoin-net/src/peer_blocksonly_journey.rs index 7be1a998b..da4efcf4a 100644 --- a/crates/rbitcoin-net/src/peer_blocksonly_journey.rs +++ b/crates/rbitcoin-net/src/peer_blocksonly_journey.rs @@ -106,7 +106,7 @@ fn getdata_txs(rx: &mut mpsc::UnboundedReceiver) -> Vec { } fn set_relay_clock(hub: &crate::chain::ChainHub, peers: &crate::peers::PeerHub, now: u64) { - peers.set_mock_now(now); + peers.set_mock(now); hub.mempool().unwrap().note_mock_now(now); } @@ -609,7 +609,7 @@ fn peer_blocksonly_and_orphan_tx() { assert!(!hub.in_ibd(), "blocksonly is not IBD"); let mp = crate::tx_relay::MempoolHub::open(dir.join("mp"), Arc::clone(&hub.query)).unwrap(); mp.set_relay_enabled(false); - let peers = crate::peers::PeerHub::new(); + let peers = crate::peers::PeerHub::new(rbitcoin_consensus::NodeClock::new()); peers.attach_mempool(&mp); assert!(hub.attach_mempool(mp).is_ok()); let t0 = 1_700_000_000u64; diff --git a/crates/rbitcoin-net/src/peer_catchup_journey.rs b/crates/rbitcoin-net/src/peer_catchup_journey.rs index 9f522b545..3b7e14de9 100644 --- a/crates/rbitcoin-net/src/peer_catchup_journey.rs +++ b/crates/rbitcoin-net/src/peer_catchup_journey.rs @@ -78,7 +78,7 @@ async fn peer_catchup_compact_reorg() { mp.set_relay_enabled(true); assert!(hub.attach_mempool(mp).is_ok()); - let peers = crate::peers::PeerHub::new(); + let peers = crate::peers::PeerHub::new(rbitcoin_consensus::NodeClock::new()); let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 18444); let ver = bitcoin::p2p::message_network::VersionMessage { version: 70016, diff --git a/crates/rbitcoin-net/src/peer_header_dos_journey.rs b/crates/rbitcoin-net/src/peer_header_dos_journey.rs index 56b42281f..056a728f5 100644 --- a/crates/rbitcoin-net/src/peer_header_dos_journey.rs +++ b/crates/rbitcoin-net/src/peer_header_dos_journey.rs @@ -1093,7 +1093,7 @@ async fn peer_header_dos_and_self_announce() { params.apply_test_activation_height("cltv", 111).unwrap(); let hub = ChainHub::new(q, params, Milestone::NONE); hub.ensure_genesis().unwrap(); - let peers = crate::peers::PeerHub::new(); + let peers = crate::peers::PeerHub::new(rbitcoin_consensus::NodeClock::new()); handshake_verack_and_addrv2(&hub, &peers).await; unknown_parent_bodies(&hub, &peers).await; diff --git a/crates/rbitcoin-net/src/peer_hostile_journey.rs b/crates/rbitcoin-net/src/peer_hostile_journey.rs index 4e626b954..551a366c4 100644 --- a/crates/rbitcoin-net/src/peer_hostile_journey.rs +++ b/crates/rbitcoin-net/src/peer_hostile_journey.rs @@ -45,7 +45,7 @@ async fn hostile_peer_session() { .unwrap() .block_hash(); - let peers = crate::peers::PeerHub::new(); + let peers = crate::peers::PeerHub::new(rbitcoin_consensus::NodeClock::new()); let bind = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 18444); let ver = hostile_ver(bind); let peer = peers.register( diff --git a/crates/rbitcoin-net/src/peer_tests.rs b/crates/rbitcoin-net/src/peer_tests.rs index 964349756..39c7c24cd 100644 --- a/crates/rbitcoin-net/src/peer_tests.rs +++ b/crates/rbitcoin-net/src/peer_tests.rs @@ -1,6 +1,8 @@ use super::*; use crate::peers::{CappedSet, PeerOut}; use bitcoin::BlockHash; +#[allow(unused_imports)] +use rbitcoin_consensus::NodeClock; use rbitcoin_consensus::{ChainParams, Milestone}; use rbitcoin_query::Query; use std::collections::{HashMap, HashSet}; @@ -426,7 +428,7 @@ async fn partial_for_a_block_connected_elsewhere_frees_the_slot() { vec![spend(1)], ); - let peers = crate::peers::PeerHub::new(); + let peers = crate::peers::PeerHub::new(NodeClock::new()); let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 18447); let ver = VersionMessage { version: 70016, @@ -585,7 +587,7 @@ fn same_peer_pending_cmpct_does_not_getblocktxn_again() { block.header.merkle_root = block.compute_merkle_root().unwrap(); let hsi = HeaderAndShortIds::from_block(&block, 0xbeef, 2, &[]).unwrap(); let hash = block.block_hash(); - let peers = crate::peers::PeerHub::new(); + let peers = crate::peers::PeerHub::new(NodeClock::new()); let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 18446); let ver = VersionMessage { version: 70016, @@ -1337,7 +1339,7 @@ fn recent_reject_skips_atmp_on_second_send(via_cidr: bool) { mp.set_relay_enabled(true); assert!(hub.attach_mempool(mp).is_ok()); - let peers = crate::peers::PeerHub::new(); + let peers = crate::peers::PeerHub::new(NodeClock::new()); if via_cidr { let mut table = crate::net_permissions::NetPermTable::default(); table @@ -2181,7 +2183,7 @@ fn getdata_skips_reconstruct_when_serve_inflight_at_cap() { .unwrap(); assert!(hashes.len() >= 20); - let peers = crate::peers::PeerHub::new(); + let peers = crate::peers::PeerHub::new(NodeClock::new()); let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 18444); let ver = bitcoin::p2p::message_network::VersionMessage { version: 70016, @@ -2388,7 +2390,7 @@ fn snapshot_omits_peer_after_tcp_fin() { use std::net::{IpAddr, Ipv4Addr, Shutdown, SocketAddr, TcpListener, TcpStream}; use std::time::Duration; - let hub = PeerHub::new(); + let hub = PeerHub::new(NodeClock::new()); let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 18444); let ver = VersionMessage { version: 70016, @@ -2860,7 +2862,7 @@ async fn inv_and_getdata_at_cap_stay_one_past_disconnects() { } #[tokio::test] async fn over_budget_reader_waits_until_one_byte_is_written() { - let peers = crate::peers::PeerHub::new(); + let peers = crate::peers::PeerHub::new(NodeClock::new()); let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 1)); let ver = bitcoin::p2p::message_network::VersionMessage { version: 70016, diff --git a/crates/rbitcoin-net/src/peer_tip_announce_journey.rs b/crates/rbitcoin-net/src/peer_tip_announce_journey.rs index 634cefdba..f7318031d 100644 --- a/crates/rbitcoin-net/src/peer_tip_announce_journey.rs +++ b/crates/rbitcoin-net/src/peer_tip_announce_journey.rs @@ -284,7 +284,7 @@ async fn tip_announce_compact_requires_parent(hub: &crate::chain::ChainHub) { .block_hash(); let parent = hub.tip_header().unwrap().prev_blockhash; let ev = tip_event(hub, 0); - let peers = crate::peers::PeerHub::new(); + let peers = crate::peers::PeerHub::new(rbitcoin_consensus::NodeClock::new()); let sess_behind = live_peer(&peers, 18444, 1, false); sess_behind.note_best_known(behind); let (out_tx, mut out_rx) = mpsc::unbounded_channel(); @@ -389,7 +389,7 @@ async fn tip_announce_depth_and_fill_slot(hub: &crate::chain::ChainHub) { .query .reconstruct_block_at_height(Height(tip_h)) .unwrap(); - let peers = crate::peers::PeerHub::new(); + let peers = crate::peers::PeerHub::new(rbitcoin_consensus::NodeClock::new()); let session = live_peer(&peers, 18446, 3, true); assert!(session.try_cmpct_fill(hash)); let mut follow = PeerFollowState::new(); @@ -415,7 +415,7 @@ async fn tip_announce_depth_and_fill_slot(hub: &crate::chain::ChainHub) { async fn tip_announce_serve_inflight_untouched(hub: &crate::chain::ChainHub) { use std::sync::atomic::Ordering; let hash = hub.tip_hash().unwrap(); - let peers = crate::peers::PeerHub::new(); + let peers = crate::peers::PeerHub::new(rbitcoin_consensus::NodeClock::new()); let (out_tx, mut out_rx) = mpsc::unbounded_channel(); let wrap = live_peer(&peers, 18447, 4, false); @@ -600,7 +600,7 @@ async fn tip_announce_hb_relays_before_connect(hub: &crate::chain::ChainHub) { let pref: Vec = (0..block.txdata.len()).collect(); let hsi = HeaderAndShortIds::from_block(&block, 1, 2, &pref).expect("hsi"); - let peers = crate::peers::PeerHub::new(); + let peers = crate::peers::PeerHub::new(rbitcoin_consensus::NodeClock::new()); let a = live_peer(&peers, 18449, 6, false); let b = live_peer(&peers, 18450, 7, false); b.set_hb_to(true); @@ -970,7 +970,7 @@ async fn tip_announce_merkle_second_cmpct_disconnects(hub: &crate::chain::ChainH }; let hash = hsi.header.block_hash(); assert!(!hub.has_block(&hash)); - let peers = crate::peers::PeerHub::new(); + let peers = crate::peers::PeerHub::new(rbitcoin_consensus::NodeClock::new()); let session = live_peer(&peers, 18451, 8, true); let (out_tx, _out_rx) = mpsc::unbounded_channel(); let mut follow = PeerFollowState::new(); diff --git a/crates/rbitcoin-net/src/peers.rs b/crates/rbitcoin-net/src/peers.rs index 7afc22d34..6edeb7123 100644 --- a/crates/rbitcoin-net/src/peers.rs +++ b/crates/rbitcoin-net/src/peers.rs @@ -6,6 +6,8 @@ use bitcoin::p2p::message::NetworkMessage; use bitcoin::p2p::message_network::VersionMessage; use bitcoin::p2p::ServiceFlags; use bitcoin::{BlockHash, Wtxid}; +#[allow(unused_imports)] +use rbitcoin_consensus::NodeClock; use std::collections::{HashMap, HashSet, VecDeque}; use std::hash::Hash; use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; @@ -1245,8 +1247,8 @@ pub struct PeerHub { dial_tx: Mutex>>, /// Peers we asked to send us compact (BIP152 HB, max 3, prefer outbound). hb_selected: Mutex>, - /// `setmocktime` seconds; `0` means wall clock. - mock_now: AtomicU64, + /// Unified clock - wire to basement master (NodeClock). Single source of truth. + pub clock: std::sync::Arc, /// Count of sessions currently in headers-sync. headers_sync_peers: AtomicU64, /// Last inv hash that started headers sync with a not-yet-sync peer. @@ -1344,7 +1346,7 @@ fn ip_is_advertisable(ip: &IpAddr, cjdns_reachable: bool) -> bool { } impl PeerHub { - pub fn new() -> Arc { + pub fn new(clock: std::sync::Arc) -> Arc { Arc::new(Self { next_id: AtomicU64::new(0), live: RwLock::new(HashMap::new()), @@ -1354,7 +1356,7 @@ impl PeerHub { connect_default_port: AtomicU16::new(0), dial_tx: Mutex::new(None), hb_selected: Mutex::new(Vec::new()), - mock_now: AtomicU64::new(0), + clock, headers_sync_peers: AtomicU64::new(0), last_inv_headers_sync: Mutex::new(None), noban: AtomicBool::new(false), @@ -1842,33 +1844,30 @@ impl PeerHub { } pub fn now_secs(&self) -> u64 { - let mock = self.mock_now.load(Ordering::Acquire); - if mock != 0 { - return mock; - } - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_secs()) - .unwrap_or(0) + self.clock.now_secs() } - /// Ask every live session to flush due tx INVs (`p2p_blocksonly` RPC relay). - pub fn request_all_tx_inv(&self) { + #[cfg(test)] + pub fn set_mock(&self, ts: u64) { + self.clock.set_mock(ts as i64); + self.on_clock_jump(); + } + + pub fn on_clock_jump(&self) { + self.request_all_tx_inv(); + self.on_session_heartbeat(); let g = self.live.read().unwrap_or_else(|e| e.into_inner()); for p in g.values() { - p.request_tx_inv(); + p.queue_self_announce_if_due(); } } - pub fn set_mock_now(&self, ts: u64) { - self.mock_now.store(ts, Ordering::Release); + /// Ask every live session to flush due tx INVs (`p2p_blocksonly` RPC relay). + pub fn request_all_tx_inv(&self) { let g = self.live.read().unwrap_or_else(|e| e.into_inner()); for p in g.values() { p.request_tx_inv(); - p.queue_self_announce_if_due(); } - drop(g); - self.on_session_heartbeat(); } /// Session 50ms heartbeat: replace a stalling initial-headers-sync peer. @@ -2792,7 +2791,7 @@ mod tests { drop(far); let _ = local.shutdown(std::net::Shutdown::Both); - let hub = PeerHub::new(); + let hub = PeerHub::new(NodeClock::new()); let a = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 18444); let connecting = hub.register_connecting(a, a, true, PeerConnType::Inbound); connecting.attach_tcp_shutdown(local.try_clone().unwrap()); @@ -2828,7 +2827,7 @@ mod tests { far.set_read_timeout(Some(Duration::from_millis(200))) .unwrap(); - let hub = PeerHub::new(); + let hub = PeerHub::new(NodeClock::new()); let a = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 18444); let b = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 18445); let p = hub.register( @@ -2868,15 +2867,16 @@ mod tests { #[test] fn connecting_peer_v2_timeout_log_before_transport() { rbitcoin_log::capture_logs(true); - let hub = PeerHub::new(); + let hub = PeerHub::new(NodeClock::new()); hub.set_peer_timeout_secs(3); - hub.set_mock_now(1_700_000_000); + hub.set_mock(1_700_000_000); let a = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 1); let p = hub.register_connecting(a, a, true, PeerConnType::Inbound); assert!(!p.handshake_complete()); - hub.set_mock_now(1_700_000_002); + hub.set_mock(1_700_000_002); + hub.on_session_heartbeat(); assert!(!p.stop.load(Ordering::SeqCst), "still inside peertimeout"); - hub.set_mock_now(1_700_000_003); + hub.set_mock(1_700_000_003); let logs = rbitcoin_log::take_logs(); rbitcoin_log::capture_logs(false); assert!( @@ -2897,9 +2897,9 @@ mod tests { #[test] fn p2p_timeouts_v2_logs_all_three_connecting_peer_ids() { rbitcoin_log::capture_logs(true); - let hub = PeerHub::new(); + let hub = PeerHub::new(NodeClock::new()); hub.set_peer_timeout_secs(3); - hub.set_mock_now(1_700_000_000); + hub.set_mock(1_700_000_000); let addr = |p| SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), p); let peers: Vec<_> = (1..=3) .map(|p| { @@ -2909,7 +2909,7 @@ mod tests { }) .collect(); assert_eq!(peers.iter().map(|p| p.id).collect::>(), [0, 1, 2]); - hub.set_mock_now(1_700_000_003); + hub.set_mock(1_700_000_003); let logs = rbitcoin_log::take_logs(); rbitcoin_log::capture_logs(false); let got: Vec = logs @@ -2932,15 +2932,15 @@ mod tests { #[test] fn handshake_timeout_tick_logs_when_stop_already_set() { rbitcoin_log::capture_logs(true); - let hub = PeerHub::new(); + let hub = PeerHub::new(NodeClock::new()); hub.set_peer_timeout_secs(3); - hub.set_mock_now(1_700_000_000); + hub.set_mock(1_700_000_000); let a = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 1); let p = hub.register_connecting(a, a, true, PeerConnType::Inbound); p.mark_v2_transport_ready(); p.request_disconnect(); let _ = rbitcoin_log::take_logs(); - hub.mock_now.store(1_700_000_003, Ordering::Release); + hub.set_mock(1_700_000_003); let policy = crate::peer::HandshakePolicy { hub: None, peers: Some(&hub), @@ -2968,7 +2968,7 @@ mod tests { #[allow(clippy::cognitive_complexity)] // one hub, many peer-state arms #[test] fn peerhub_hb_select() { - let hub = PeerHub::new(); + let hub = PeerHub::new(NodeClock::new()); let join = |port: u16, inbound: bool| { let a = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port); let conn = if inbound { @@ -3126,7 +3126,7 @@ mod tests { #[test] fn session_heartbeat_keeps_sole_preferred_headers_sync_peer() { - let hub = PeerHub::new(); + let hub = PeerHub::new(NodeClock::new()); let a = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 1); let outbound = hub.register( a, @@ -3153,7 +3153,7 @@ mod tests { } fn pin_noban_headers_timeout_keep(via_cidr: bool) { - let hub = PeerHub::new(); + let hub = PeerHub::new(NodeClock::new()); if via_cidr { let mut t = crate::NetPermTable::default(); t.whitelist @@ -3182,7 +3182,7 @@ mod tests { inbound.note_awaiting_headers(); assert!(inbound.is_awaiting_headers()); let deadline = crate::chain::headers_download_timeout_secs(now, best); - hub.set_mock_now(deadline + 1); + hub.set_mock(deadline + 1); assert!( !inbound.stop.load(Ordering::SeqCst), "noban stall must keep the TCP session (via_cidr={via_cidr})" @@ -3203,7 +3203,7 @@ mod tests { #[test] fn snapshot_permissions_are_cidr_table_not_hub_noban() { - let hub = PeerHub::new(); + let hub = PeerHub::new(NodeClock::new()); let a = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 18444); let bind = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 18445); hub.register( @@ -3227,7 +3227,7 @@ mod tests { #[test] fn cidr_noban_is_per_peer_not_hub_wide() { - let hub = PeerHub::new(); + let hub = PeerHub::new(NodeClock::new()); let bind = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 18445); let local = hub.register( SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 18444), @@ -3262,7 +3262,7 @@ mod tests { #[test] fn addnode_unknown_command() { - let hub = PeerHub::new(); + let hub = PeerHub::new(NodeClock::new()); let a = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 1); assert!(hub.addnode(a, "nope").is_err()); } @@ -3293,7 +3293,7 @@ mod tests { #[test] fn addnode_add_keeps_unresolved_host_for_redial() { - let hub = PeerHub::new(); + let hub = PeerHub::new(NodeClock::new()); let (tx, mut rx) = mpsc::unbounded_channel(); hub.set_dialer(tx); hub.addnode_host("not-a-real-host.invalid", "add", 18444) @@ -3311,7 +3311,7 @@ mod tests { #[test] fn redial_same_endpoint_in_addnode_and_connect_dials_once() { - let hub = PeerHub::new(); + let hub = PeerHub::new(NodeClock::new()); let (tx, mut rx) = mpsc::unbounded_channel(); hub.set_dialer(tx); hub.addnode_host("127.0.0.1:18444", "add", 18444).unwrap(); @@ -3329,7 +3329,7 @@ mod tests { #[tokio::test] async fn slow_redial_resolve_does_not_stall_runtime() { - let hub = PeerHub::new(); + let hub = PeerHub::new(NodeClock::new()); hub.set_connect_hosts(vec!["slow.example".into()], 18444); let flag = Arc::new(AtomicBool::new(false)); let flag2 = Arc::clone(&flag); @@ -3357,8 +3357,8 @@ mod tests { /// `setmocktime` jump cannot drop peers (`feature_bip68_sequence.py`). #[test] fn ping_timeout_waits_for_the_peer_timeout() { - let hub = PeerHub::new(); - hub.set_mock_now(1_700_000_000); + let hub = PeerHub::new(NodeClock::new()); + hub.set_mock(1_700_000_000); let a = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 18444); let p = hub.register(a, a, &ver("/rbitcoin:0.1.0/"), true, PeerConnType::Inbound); let now = hub.now_secs(); @@ -3413,7 +3413,7 @@ mod tests { #[test] fn outbound_full_relay_ids_skips_inbound_and_noban() { - let hub = PeerHub::new(); + let hub = PeerHub::new(NodeClock::new()); let a = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 1); let b = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 2); let c = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 3); @@ -3462,8 +3462,8 @@ mod tests { #[test] fn getaddr_cache_bind_key_and_ttl() { - let hub = PeerHub::new(); - hub.set_mock_now(1_700_000_000); + let hub = PeerHub::new(NodeClock::new()); + hub.set_mock(1_700_000_000); hub.set_addrman(Arc::new(Mutex::new(fill_addrman(5_000)))); let a = addr_ips(&hub.addr_response_for_bind(SocketAddr::from(([127, 0, 0, 1], 18444)))); let b = addr_ips(&hub.addr_response_for_bind(SocketAddr::from(([127, 0, 0, 1], 18445)))); @@ -3482,12 +3482,56 @@ mod tests { assert_ne!(a, b); assert_ne!(a, c); assert_ne!(b, c); - hub.set_mock_now(1_700_000_000 + 24 * 60 * 60); + hub.set_mock(1_700_000_000 + 24 * 60 * 60); let expired = addr_ips(&hub.addr_response_for_bind(SocketAddr::from(([127, 0, 0, 1], 18444)))); assert_eq!(expired.len(), 1000); assert_ne!(a, expired); } + #[test] + fn self_announce_due_on_peerhub_clock_jump() { + use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + use std::sync::atomic::Ordering; + + let hub = PeerHub::new(NodeClock::new()); + let start = 1_700_000_000u64; + hub.set_mock(start); + // clearnet only - tests advertise_local_socket(), not onion bypass + hub.discover.store(true, Ordering::Relaxed); + hub.set_clearnet_listen(true); + hub.listen_port.store(8333, Ordering::Relaxed); + hub.set_external_ips(vec![IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4))]); + + let a = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 18444); + let peer = hub.register( + a, + a, + &ver("/rbitcoin:0.1.0/"), + false, + PeerConnType::OutboundFullRelay, + ); + + // Pin the 24h deadline: next send = start + DAY, so not due yet + peer.next_local_addr_send + .store(start + 86400, Ordering::Relaxed); + + let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); + *peer.out_tx.lock().unwrap_or_else(|e| e.into_inner()) = Some(tx); + while rx.try_recv().is_ok() {} + + // 1s jump would NOT be due - this would fail if test used 0 + // 25h jump IS due - proves on_clock_jump restores self-announce after mock jump past interval + hub.set_mock(start + 25 * 3600); + + let msg = rx + .try_recv() + .expect("self-announce should be queued after 25h jump past 24h interval"); + match msg { + PeerOut::Msg(NetworkMessage::Addr(_)) | PeerOut::Msg(NetworkMessage::AddrV2(_)) => {} + other => panic!("expected Addr/AddrV2, got {other:?}"), + } + } + include!("overlay_addrman_journey.rs"); } diff --git a/crates/rbitcoin-net/src/service.rs b/crates/rbitcoin-net/src/service.rs index 50d4aacbf..477d7ff58 100644 --- a/crates/rbitcoin-net/src/service.rs +++ b/crates/rbitcoin-net/src/service.rs @@ -158,7 +158,7 @@ impl P2PNode { (None, SocketAddr::from(([127, 0, 0, 1], 0))) }; - let peers = PeerHub::new(); + let peers = PeerHub::new(hub.clock.clone()); let (dial_tx, mut dial_rx) = tokio::sync::mpsc::unbounded_channel::(); peers.set_dialer(dial_tx); diff --git a/crates/rbitcoin-node/src/run.rs b/crates/rbitcoin-node/src/run.rs index aa2976406..9ea77de59 100644 --- a/crates/rbitcoin-node/src/run.rs +++ b/crates/rbitcoin-node/src/run.rs @@ -2384,7 +2384,7 @@ mod tests { #[test] fn proxy_seed_bootstrap_queues_domain_addrfetch() { - let peers = rbitcoin_net::PeerHub::new(); + let peers = rbitcoin_net::PeerHub::new(rbitcoin_consensus::NodeClock::new()); let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::(); peers.set_dialer(tx); let n = queue_proxy_seed_addrfetch(&peers, rbitcoin_primitives::Network::Signet); diff --git a/crates/rbitcoin-rpc/src/methods/mine.rs b/crates/rbitcoin-rpc/src/methods/mine.rs index 1cddc3878..b2328bab1 100644 --- a/crates/rbitcoin-rpc/src/methods/mine.rs +++ b/crates/rbitcoin-rpc/src/methods/mine.rs @@ -444,7 +444,7 @@ pub(crate) fn setmocktime(ctx: &RpcContext, params: &RpcParams) -> Result rbitcoin_net::V2Plain ) .await; - node.peers.set_mock_now(node.peers.now_secs() + 121); + node.peers + .clock + .set_mock((node.peers.now_secs() as i64) + 121); next_ping(&mut raw).await; assert!(row().pingwait.is_some(), "{:?}", row()); raw.write_contents(&pong(0)).await.expect("write pong"); @@ -3142,7 +3144,7 @@ async fn mocktime_generate_keeps_ponging_peer() { let t0 = a.peers.now_secs(); let script = bitcoin::ScriptBuf::from_bytes(vec![0x51]); for i in 1..=10u64 { - a.peers.set_mock_now(t0 + i * 600); + a.peers.clock.set_mock((t0 + i * 600) as i64); let hub = a.hub.clone(); let script = script.clone(); tokio::task::spawn_blocking(move || { diff --git a/docs/clock-inventory.md b/docs/clock-inventory.md new file mode 100644 index 000000000..b59948de2 --- /dev/null +++ b/docs/clock-inventory.md @@ -0,0 +1,120 @@ +# Clock inventory for #821 Step 1 + +HEAD: 886463ef master (2026-09-27) Merge #823 - latest, 100 sites +Command: rg -n "SystemTime::now()" crates/ --type rust -g '!*tests*' --no-heading | sort + +Summary from your run: +- 10 crates/rbitcoin-store/src/bulk_io.rs -> log/perf LEAVE +- 6 crates/rbitcoin-node/src/run.rs -> mostly log/perf +- 5 crates/rbitcoin-store/src/bdz.rs -> log/perf LEAVE +- 4+4+4 peer.rs, spender_table, scripthash_head -> mix +- 3 peers.rs:1249 mock_now Acquire/Release + set_mock_now() bypasses hub.clock -> CRITICAL MIGRATE (split-brain), tx_relay.rs:625 mock_now Relaxed + note_mock_now() bypasses hub.clock -> CRITICAL MIGRATE, service.rs/seeds.rs -> node-time +- 2 tx_relay.rs -> 1x node-time (928 manual mock -> NodeClock), 1x log/perf (3753 tmp uniqueness) LEAVE + +Legend per Rearden #688 item 2.1: +- log/perf: log stamps, metrics, perf timers - LEAVE as SystemTime::now() +- node-time: consensus / net / mempool / rpc - MIGRATE to NodeClock +- wall: explicitly wants wall even under mocktime (INV age gate) + +| File | Line | Code | Class | Notes | +|------|------|------|-------|-------| +| crates/rbitcoin-cli/src/lib.rs | 328 | `let n = SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-consensus/src/clock.rs | 44 | `unix_secs(SystemTime::now())` | log/perf - LEAVE (root wall source - impl of NodeClock::now()) | | +| crates/rbitcoin-consensus/src/header.rs | 353 | `let n = SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-consensus/src/lib.rs | 372 | `std::time::SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-consensus/src/params.rs | 738 | `let n = SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-consensus/src/regtest_pad.rs | 152 | `let n = SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-consensus/src/script/core_fixture.rs | 130 | `std::time::SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-electrum/src/tweaks.rs | 661 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-electrum/src/tweaks.rs | 767 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-electrum/src/tweaks.rs | 855 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-esplora/src/tx_json.rs | 584 | `let n = SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-esplora/src/tx_json.rs | 726 | `let n = SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-log/src/api_log.rs | 134 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-log/src/api_log.rs | 77 | `let ts = format_timestamp(SystemTime::now());` | log/perf - LEAVE | | +| crates/rbitcoin-log/src/lib.rs | 195 | `let ts = format_timestamp(SystemTime::now());` | log/perf - LEAVE | | +| crates/rbitcoin-mempool/src/accept.rs | 118 | `std::time::SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-mempool/src/orphanage.rs | 415 | `std::time::SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-net/src/asmap.rs | 500 | `std::time::SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-net/src/i2p_sam.rs | 871 | `SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-net/src/peer_header_dos_journey.rs | 730 | `std::time::SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-net/src/peer_header_dos_journey.rs | 903 | `std::time::SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-net/src/peer.rs | 1116 | `let tick = SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-net/src/peer.rs | 2785 | `let now_ms = std::time::SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-net/src/peer.rs | 835 | `let now = SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-net/src/peer.rs | 925 | `let now = SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-net/src/peers.rs | 1165 | `let tick = SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-net/src/peers.rs | 1849 | `std::time::SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-net/src/peers.rs | 920 | `std::time::SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-net/src/seeds.rs | 1236 | `std::time::SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-net/src/seeds.rs | 1487 | `std::time::SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-net/src/seeds.rs | 1580 | `std::time::SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-net/src/service.rs | 823 | `std::time::SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-net/src/service.rs | 892 | `let n = std::time::SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-net/src/service.rs | 959 | `let n = std::time::SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-net/src/tx_relay.rs | 3753 | `let n = SystemTime::now()` | log/perf - LEAVE (tmp path uniqueness) | | +| crates/rbitcoin-net/src/tx_relay.rs | 928 | `std::time::SystemTime::now()` | node-time - MIGRATE to NodeClock (manual mock check -> use NodeClock) | | +| crates/rbitcoin-node/src/cli.rs | 533 | `let n = SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-node/src/config.rs | 1430 | `let n = SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-node/src/lock.rs | 101 | `let n = SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-node/src/regtest_rpc.rs | 53 | `let n = SystemTime::now()` | node-time - MIGRATE (setmocktime path) | | +| crates/rbitcoin-node/src/run.rs | 2117 | `std::time::SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-node/src/run.rs | 2132 | `std::time::SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-node/src/run.rs | 2154 | `std::time::SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-node/src/run.rs | 2413 | `let nanos = SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-node/src/run.rs | 2458 | `let nanos = SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-node/src/run.rs | 2516 | `let nanos = SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-query/src/run_builder_core.rs | 74 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-query/src/sh_builder.rs | 247 | `let n = SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-rpc/src/auth.rs | 114 | `let n = SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-rpc/src/methods/mine.rs | 609 | `std::time::SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-rpc/src/methods/mine.rs | 751 | `std::time::SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-rpc/src/methods/net.rs | 13 | `let timemillis = std::time::SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-rpc/src/methods/net.rs | 290 | `let now = std::time::SystemTime::now()` | node-time - MIGRATE to NodeClock | | +| crates/rbitcoin-store/src/address_head.rs | 1833 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/array_table.rs | 345 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/bdz.rs | 1293 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/bdz.rs | 1514 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/bdz.rs | 1575 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/bdz.rs | 1656 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/bdz.rs | 1723 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/bulk_io.rs | 1005 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/bulk_io.rs | 1070 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/bulk_io.rs | 1147 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/bulk_io.rs | 1230 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/bulk_io.rs | 651 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/bulk_io.rs | 720 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/bulk_io.rs | 798 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/bulk_io.rs | 850 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/bulk_io.rs | 910 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/bulk_io.rs | 975 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/chain.rs | 594 | `std::time::SystemTime::now()` | node-time - MIGRATE (tip time) | | +| crates/rbitcoin-store/src/chain.rs | 847 | `std::time::SystemTime::now()` | node-time - MIGRATE (tip time) | | +| crates/rbitcoin-store/src/fuse8_filter.rs | 235 | `let n = SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/hashhead.rs | 984 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/header_table.rs | 504 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/integrity.rs | 471 | `let n = SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/io_session_iocp.rs | 254 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/point_table.rs | 152 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/readonly_map.rs | 203 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/scripthash_head.rs | 790 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/scripthash_head.rs | 819 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/scripthash_head.rs | 860 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/scripthash_head.rs | 921 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/scripthash_materialize.rs | 2386 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/scripthash_materialize.rs | 2416 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/scripthash_mphf.rs | 370 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/scripthash_sorted_head.rs | 330 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/segmented_head.rs | 1263 | `let n = SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/spend_durable.rs | 75 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/spender_table.rs | 129 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/spender_table.rs | 174 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/spender_table.rs | 203 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/spender_table.rs | 229 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/testutil.rs | 27 | `let nanos = SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/tx_head_mphf.rs | 225 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/txid_body.rs | 391 | `let n = SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/uring_session.rs | 1834 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/var_table.rs | 631 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/var_table.rs | 676 | `std::time::SystemTime::now()` | log/perf - LEAVE | | +| crates/rbitcoin-store/src/var_table.rs | 739 | `std::time::SystemTime::now()` | log/perf - LEAVE | |