Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion OPERATOR.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ Requires **tip mode** (`node: catch-up complete … tip tracking`). During IBD u

| Line | Level | Use |
|------|-------|-----|
| `ibd: progress` | INFO | Tip rate, `loadq`/`scriptq`/`writeq`, `txs=` (Class A / `tx.idx` count), horizon, tip ETA, **`bq soft=n/win RAM=`** (in-RAM body queue; soft densify: under ~100 MiB free ahead, over that only ~1 min confirm window, at/over 1 GiB assign-stop fill holes in the fetched range only) |
| `ibd: progress` | INFO | Tip rate, `loadq`/`scriptq`/`writeq`, `txs=` (Class A / `tx.idx` count), horizon, tip ETA, **`bq soft=n/win RAM=`** (in-RAM body queue; soft densify: under ~100 MiB free ahead, over that only ~1 min confirm window, at/over 1 GiB assign-stop holes within that window and not past fetched_hi) |
| `ibd: perf` | DEBUG | Inflight + **`bq soft= RAM=`**; **`load=`** is pin+assemble only. **`load_thr pack/stamp/pin/asm/prune`** is the load OS thread. **`stamp=`** nests **`pack=`** (plan HashMap) vs **`head=`** (leftover TipOnly; IBD skeleton keeps this ~0). **`script=`** is verify ns (`jobs=` / `skip=`); recv/send are wait. **`pin_txid=`** is skeleton hits vs leftover `tx.head` |
| `ibd: sizes` | DEBUG | RSS + work path + **`bq soft=` / `RAM=`** + **conf_plans** + confirm pipe |
| `ibd: perf_dbg` | DEBUG | µs/blk load/write, pin/edge detail, **plan_batch** (`us/pin_txid` vs `probe/idx/body us/key`) + **class_a commit** |
Expand Down
26 changes: 14 additions & 12 deletions crates/rbitcoin-net/src/ibd/assign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
//! - BQ payload **≤ ~100 MiB** → usual densify ahead to the height horizon
//! - BQ payload **> ~100 MiB** → only heights confirm will consume in the
//! next **~1 min** at current tip rate ([`rbitcoin_query::soft_densify_band_hi`])
//! - BQ payload **≥ assign-stop** (default 1 GiB) → holes through the
//! already-fetched height horizon only (BQ max / lookup_taken); do not grow
//! - BQ payload **≥ assign-stop** (default 1 GiB) → holes only within the
//! ~1 min tip-rate window **and** not past fetched_hi (do not grow past
//! fetched; do not densify far holes outside the window)
//! - Never request beyond densify horizon; events refuse far bodies too.
//! - One body-queue copy per height (receive path drops duplicates).

Expand Down Expand Up @@ -1661,9 +1662,9 @@ mod tests {
/// Serialize env mutators — parallel suite races `bq_assign_stop_bytes`.
static BQ_ASSIGN_STOP_ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());

/// Over assign-stop: fill holes in the already-fetched range, do not grow past it.
/// Over assign-stop: densify within confirm window ∩ fetched; not past window.
#[test]
fn densify_over_assign_stop_fills_fetched_range_only() {
fn densify_over_assign_stop_clamps_window_and_fetched() {
let _g = BQ_ASSIGN_STOP_ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
Expand Down Expand Up @@ -1694,7 +1695,7 @@ mod tests {
cfg.window = 128;
cfg.per_peer = 64;

for ht in 1u32..=200 {
for ht in 1u32..=500 {
let hash = h(ht);
st.record_height(hash, ht);
st.height_to_hash.insert(ht, hash);
Expand All @@ -1710,13 +1711,14 @@ mod tests {
.unwrap();
st.body.mark_pending(h(ht));
}
// Far fetched_hi=500 trips assign-stop; rate 5 → confirm window 300.
let chunk = vec![0u8; 4096];
hub.query
.block_queue_enqueue(80, h(80).to_byte_array(), 80, &chunk)
.block_queue_enqueue(500, h(500).to_byte_array(), 500, &chunk)
.unwrap();
st.body.mark_pending(h(80));
st.body.mark_pending(h(500));
assert!(hub.query.block_queue_stats().1 >= 2048);
assert_eq!(hub.query.block_queue_max_height(), Some(80));
assert_eq!(hub.query.block_queue_max_height(), Some(500));

assign_work_ordered(&mut st, &hub, &cfg, &stats, 1, AssignDepth::Full, Some(5.0));

Expand All @@ -1728,12 +1730,12 @@ mod tests {
assert!(
issued_hts
.iter()
.any(|&ht| ht > TIP_HOLE_MAX as u32 && ht < 80),
"assign-stop must still densify holes inside fetched range; issued={issued_hts:?}"
.any(|&ht| ht > TIP_HOLE_MAX as u32 && ht <= 300),
"assign-stop must densify holes inside confirm window; issued={issued_hts:?}"
);
assert!(
issued_hts.iter().all(|&ht| ht <= 80),
"assign-stop must not grow past fetched horizon 80; issued={issued_hts:?}"
issued_hts.iter().all(|&ht| ht <= 300),
"assign-stop must not issue past window 300 (fetched_hi=500); issued={issued_hts:?}"
);

let _ = std::fs::remove_dir_all(dir);
Expand Down
38 changes: 29 additions & 9 deletions crates/rbitcoin-query/src/soft_densify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ pub const BQ_SOFT_FREE_BYTES: u64 = 100 * 1024 * 1024;
/// rate. Tunable constant — no hysteresis band.
pub const BQ_SOFT_CONFIRM_SECS: f64 = 60.0;

/// Default densify assign-stop (1 GiB). Over this, fill holes in the already
/// fetched height range; do not grow past that horizon. Override with
/// Default densify assign-stop (1 GiB). At/over this, densify only holes within
/// the ~1 min tip-rate confirm window **and** not past `fetched_hi` (do not grow
/// past fetched; do not densify far holes outside the window). Override with
/// `RBITCOIN_BLOCK_QUEUE_BYTES` / `_GB` (`0` = unlimited).
pub const BQ_ASSIGN_STOP_BYTES: u64 = 1024 * 1024 * 1024;

Expand Down Expand Up @@ -61,8 +62,10 @@ pub fn bq_assign_stop_bytes() -> u64 {

/// Inclusive densify band high height for getdata assign.
///
/// - **At/over** `assign_stop_bytes`: holes through `fetched_hi` only (do not
/// grow past the already-fetched horizon). No fetched range → empty band.
/// - **At/over** `assign_stop_bytes`: holes only within the ~1 min tip-rate
/// confirm window **and** not past `fetched_hi` (do not grow past fetched;
/// do not densify far holes outside the window). No usable fetched range →
/// empty band.
/// - **Under** [`BQ_SOFT_FREE_BYTES`]: full `densify_hi` (usual densify ahead).
/// - **Over** free bytes (and under assign-stop): only heights confirm will
/// pick up within [`BQ_SOFT_CONFIRM_SECS`] at current rate —
Expand All @@ -82,8 +85,14 @@ pub fn soft_densify_band_hi(
return densify_hi;
}
if soft_assign_stopped(depth_bytes, assign_stop_bytes) {
let n = soft_confirm_window_n(rate_blocks_per_s);
let window_hi = if n == 0 {
path_lo.min(densify_hi)
} else {
path_lo.saturating_add(n.saturating_sub(1)).min(densify_hi)
};
return match fetched_hi {
Some(h) if h >= path_lo => h.min(densify_hi),
Some(h) if h >= path_lo => window_hi.min(h),
_ => path_lo.saturating_sub(1),
};
}
Expand Down Expand Up @@ -168,7 +177,7 @@ mod tests {
}

#[test]
fn assign_stop_clamps_to_fetched_horizon() {
fn assign_stop_clamps_to_confirm_window_and_fetched() {
let stop = 2048u64;
let over = 4096u64;
let free = BQ_SOFT_FREE_BYTES;
Expand All @@ -182,15 +191,26 @@ mod tests {
6,
"over free / under 1 GiB still uses confirm window"
);
// Over stop: fill through fetched_hi, do not grow, ignore 1-min window.
// Over stop: min(confirm_window_hi, fetched_hi, densify_hi); rate 5 → window 300.
assert_eq!(
soft_densify_band_hi(1, 1000, over, Some(5.0), stop, Some(80)),
80
80,
"fetched_hi below window → fetched_hi"
);
assert_eq!(
soft_densify_band_hi(1, 1000, over, Some(5.0), stop, Some(500)),
300,
"fetched_hi above window → confirm window, not fetched_hi"
);
assert_eq!(
soft_densify_band_hi(1, 50, over, Some(5.0), stop, Some(80)),
50,
"fetched_hi clamped to densify_hi"
"densify_hi below fetched and window → densify_hi"
);
assert_eq!(
soft_densify_band_hi(1, 1000, over, None, stop, Some(80)),
1,
"rate cold → path_lo only, not full fetched"
);
assert_eq!(
soft_densify_band_hi(1, 1000, over, Some(5.0), stop, None),
Expand Down
8 changes: 4 additions & 4 deletions crates/rbitcoin-store/src/block_queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
//!
//! **Primary capacity** is soft densify assign in the net layer (no hysteresis):
//! under ~100 MiB free densify ahead; over ~100 MiB only the next ~1 min of
//! confirm work at tip rate; over the 1 GiB assign-stop (`RBITCOIN_BLOCK_QUEUE_GB`
//! / `_BYTES`) densify fills holes inside the already-fetched height range
//! and does not grow past that horizon. This type **always** accepts
//! already-requested payloads (OOM aside).
//! confirm work at tip rate; at/over the 1 GiB assign-stop (`RBITCOIN_BLOCK_QUEUE_GB`
//! / `_BYTES`) densify holes only within that ~1 min window **and** not past
//! fetched_hi (do not grow past fetched; do not densify far holes outside the
//! window). This type **always** accepts already-requested payloads (OOM aside).

use crate::error::StoreError;
use std::collections::{BTreeMap, HashMap, HashSet};
Expand Down
4 changes: 2 additions & 2 deletions docs/ibd-memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ IBD path. It is **not** about kernel page cache under FdOnly store files

| Structure | Cap / bound | Production clear / evict |
|-----------|-------------|---------------------------|
| **In-RAM body queue** | Soft densify assign (no hysteresis): under ~100 MiB free densify ahead; over ~100 MiB only heights confirm will consume in the next ~1 min at tip rate; at/over 1 GiB assign-stop (`RBITCOIN_BLOCK_QUEUE_GB` / `_BYTES`, `0` = unlimited) fill holes through the already-fetched height horizon only (BQ max / lookup_taken) — do not grow past it. **Never** refuse enqueue. `bytes()` is **raw only** | Peer **BlockFramed** enqueues **raw** frame payload and stamps Σ `tx.input` via a CompactSize walk (no `Block` decode). Lookup packs/holds on that count; **dequeues** after load-batch send. Decoded `Arc<Block>` + `TxPrecompute` live on **loadq** (cap 14), then scriptq/writeq. **Have-body** (hole / densify / receive) is confirmed ∨ BQ hash ∨ `H ≤ lookup_taken_hi`. **Never both** raw and decoded. **RAM-only by design**. Restart empties BQ+loadq. Logs: `bq soft=n/win RAM=` `loadq=n/14`. |
| **In-RAM body queue** | Soft densify assign (no hysteresis): under ~100 MiB free densify ahead; over ~100 MiB only heights confirm will consume in the next ~1 min at tip rate; at/over 1 GiB assign-stop (`RBITCOIN_BLOCK_QUEUE_GB` / `_BYTES`, `0` = unlimited) holes only within that ~1 min window **and** not past fetched_hi (do not grow past fetched; do not densify far holes outside the window). **Never** refuse enqueue. `bytes()` is **raw only** | Peer **BlockFramed** enqueues **raw** frame payload and stamps Σ `tx.input` via a CompactSize walk (no `Block` decode). Lookup packs/holds on that count; **dequeues** after load-batch send. Decoded `Arc<Block>` + `TxPrecompute` live on **loadq** (cap 14), then scriptq/writeq. **Have-body** (hole / densify / receive) is confirmed ∨ BQ hash ∨ `H ≤ lookup_taken_hi`. **Never both** raw and decoded. **RAM-only by design**. Restart empties BQ+loadq. Logs: `bq soft=n/win RAM=` `loadq=n/14`. |
| **Body densify height horizon** | `CONTIG_DENSIFY_AHEAD` (64 k past tip) | Safety max walk/receive; primary gate is soft assign (100 MiB free / 1 min confirm window). |
| **Confirm feed** | readiness (height/hash), no wire retain | Load pack / lookup-wave caps: [`concurrency.md`](./concurrency.md). Requeue / finish on outcome |

Expand Down Expand Up @@ -74,7 +74,7 @@ is over target.
| Allowed | Forbidden |
|---------|-----------|
| Limit **densify getdata assign** when BQ payload is over ~100 MiB to heights confirm will consume in the next ~1 min at tip rate | Await a soft gate **before** the next TCP read on a peer |
| At/over 1 GiB assign-stop, densify **holes in the already-fetched height range only** (do not grow past BQ max / lookup_taken) | Drop a body we already received solely for soft budget |
| At/over 1 GiB assign-stop, densify holes only within the ~1 min tip-rate window **and** not past fetched_hi (do not grow past fetched; do not densify far holes outside the window) | Drop a body we already received solely for soft budget |
| Free densify ahead while BQ payload is under ~100 MiB | Make healthy peers look stalled by parking the reader on soft backpressure |
| Overshoot soft limits while in-flight requests complete; accept all in-flight bodies via `block_queue_offer` (assign-stop is ignored on offer) | Bound process RAM by refusing peer bytes already on the wire |

Expand Down
Loading