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
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ before 1.0).
- **Electrum `blockchain.tweaks.subscribe`:** pre-taproot empty heights go out
as **one notify** with ≤1024 keys (Cake last-key progress), not one line per
height. Cake `historicalMode=false` (param `[2]`) **cut-through**: omit
confirmed-spent P2TR outs (and txs with none left). `true` keeps spent outs
confirmed-spent P2TR outs (and txs with none left). Spentness is one
`spent.idx` batch plus one spent-body walk per create, not a serial
idx+body per eligible tx. `true` keeps spent outs
for restore. Probe `[0,1,false]` is still `{"0": {}}`. `{"message":"done"}`
ends a **chunk** (60s wall at a wave boundary, or the requested `count` if
sooner) so Cake resubscribes; it is not “`count` through tip”.
Expand Down Expand Up @@ -98,7 +100,7 @@ before 1.0).
unchanged). No `sp_tweaks` schema change.

- **Parallel `tx.head` wipe-rebuild:** ranges seal concurrently,
min(CPUs, free RAM / **750 MiB**, range count). Distinct from SH
min(CPUs, free RAM / **1 GiB**, range count). Distinct from SH
materialize (1.5 GiB/worker). `RBITCOIN_TX_HEAD_REBUILD_WORKERS`
overrides (`1` = serial).

Expand Down
2 changes: 1 addition & 1 deletion SCHEMA.md
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ seals it. Two unsealed non-tails is **Corrupt**.
**Capacity @ 0.80 load (25-bit):** ≈ **26.8 M creates/segment**, ~29 MiB fuse8 when sealed (~6.1 B total sealed storage per create including head slots).

**Wipe / empty-head rebuild:** writes MPHF+fuse8 **directly** from `txid.body`
(no historical OA). Ranges seal in parallel: min(CPUs, free RAM / 750 MiB,
(no historical OA). Ranges seal in parallel: min(CPUs, free RAM / 1 GiB,
range count); `RBITCOIN_TX_HEAD_REBUILD_WORKERS` overrides (`1` = serial).
Default range **2²⁵ keys** (`RBITCOIN_TX_HEAD_REBUILD_SEAL_BITS=25`); **26** is
wider. Remainder is sealed; an empty open tail is created. Live IBD rolls OA
Expand Down
20 changes: 17 additions & 3 deletions crates/rbitcoin-consensus/src/script/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1000,16 +1000,16 @@ fn tapscript_sig_result(
pubkey: &[u8],
ctx: &EvalContext<'_>,
) -> Result<TapSigResult, ConsensusError> {
if pubkey.is_empty() {
return Err(ConsensusError::Script("tapscript empty pubkey".into()));
}
if !sig.is_empty() {
let left = ctx.validation_weight_left.get() - TAPSCRIPT_VALIDATION_WEIGHT_PER_SIGOP;
ctx.validation_weight_left.set(left);
if left < 0 {
return Err(ConsensusError::Script("tapscript validation weight".into()));
}
}
if pubkey.is_empty() {
return Err(ConsensusError::Script("tapscript empty pubkey".into()));
}
// Unknown public key type (not 32 bytes): treat signature as valid (soft-fork hook).
if pubkey.len() != 32 {
if sig.is_empty() {
Expand Down Expand Up @@ -1794,6 +1794,20 @@ mod success_and_disabled_tests {
assert!(format!("{err}").contains("empty pubkey"));
}

#[test]
fn tapscript_empty_pubkey_reports_weight_when_budget_exhausted() {
// OP_1 OP_1 CHECKSIG then OP_1 OP_0 CHECKSIG.
// Dummy witness init weight is 51; first non-empty sig burns 50.
// Core decrements weight before the empty-pubkey fail, so the second
// CHECKSIG is TAPSCRIPT_VALIDATION_WEIGHT, not empty pubkey.
let script = vec![0x51, 0x51, 0xac, 0x51, 0x00, 0xac];
let err = eval(&script, SigVersion::TapScript).unwrap_err();
assert!(
format!("{err}").contains("validation weight"),
"Core order: weight before empty pubkey, got {err}"
);
}

#[test]
fn op_1sub_and_unary_arith() {
// OP_3 OP_1SUB → 2; OP_2 EQUAL
Expand Down
73 changes: 56 additions & 17 deletions crates/rbitcoin-net/src/peer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use bitcoin::p2p::{Magic, ServiceFlags, PROTOCOL_VERSION};
use bitcoin::{Block, BlockHash, Transaction};
use rbitcoin_primitives::Height;
use rbitcoin_query::Query;
use std::collections::{HashMap, HashSet};
use std::collections::{HashMap, HashSet, VecDeque};
use std::net::SocketAddr;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
Expand Down Expand Up @@ -123,17 +123,56 @@ pub(crate) fn insert_capped_txid(
#[cfg(test)]
pub(crate) const MAX_PENDING_BLOCKS_FOR_TEST: usize = MAX_PENDING_BLOCKS;

fn stash_pending_block(
pending: &mut HashMap<BlockHash, bitcoin::Block>,
hash: BlockHash,
block: bitcoin::Block,
) {
if pending.len() >= MAX_PENDING_BLOCKS && !pending.contains_key(&hash) {
if let Some(k) = pending.keys().next().copied() {
pending.remove(&k);
/// Tip-follow decoded bodies waiting for a connectable parent. Cap 128;
/// insert evicts the oldest hash (FIFO), not `HashMap::keys().next()`.
#[derive(Default)]
pub(crate) struct PendingBlocks {
map: HashMap<BlockHash, bitcoin::Block>,
fifo: VecDeque<BlockHash>,
}

impl PendingBlocks {
pub(crate) fn new() -> Self {
Self::default()
}

pub(crate) fn contains_key(&self, hash: &BlockHash) -> bool {
self.map.contains_key(hash)
}

pub(crate) fn values(
&self,
) -> std::collections::hash_map::Values<'_, BlockHash, bitcoin::Block> {
self.map.values()
}

pub(crate) fn keys(&self) -> std::collections::hash_map::Keys<'_, BlockHash, bitcoin::Block> {
self.map.keys()
}

pub(crate) fn insert(&mut self, hash: BlockHash, block: bitcoin::Block) {
stash_pending_block(self, hash, block);
}

pub(crate) fn remove(&mut self, hash: &BlockHash) -> Option<bitcoin::Block> {
let b = self.map.remove(hash)?;
if let Some(i) = self.fifo.iter().position(|h| h == hash) {
self.fifo.remove(i);
}
Some(b)
}
}

fn stash_pending_block(pending: &mut PendingBlocks, hash: BlockHash, block: bitcoin::Block) {
if pending.map.len() >= MAX_PENDING_BLOCKS && !pending.map.contains_key(&hash) {
if let Some(k) = pending.fifo.pop_front() {
pending.map.remove(&k);
}
}
if !pending.map.contains_key(&hash) {
pending.fifo.push_back(hash);
}
pending.insert(hash, block);
pending.map.insert(hash, block);
}

/// Services we advertise once store-backed reconstruct serve is available.
Expand Down Expand Up @@ -645,7 +684,7 @@ pub async fn peer_session_with(
// relay peer getdata CMPCT and broke tests that only serve `msg_block`.
let mut peer_cmpct_version: u32 = 0;
let mut pending_headers: HashMap<BlockHash, bitcoin::block::Header> = HashMap::new();
let mut pending_blocks: HashMap<BlockHash, bitcoin::Block> = HashMap::new();
let mut pending_blocks = PendingBlocks::new();
let mut pending_cmpct: HashMap<BlockHash, PendingCmpct> = HashMap::new();
let mut from_this_peer: HashMap<bitcoin::Txid, ()> = HashMap::new();
let mut requested_blocks: HashSet<BlockHash> = HashSet::new();
Expand Down Expand Up @@ -1374,7 +1413,7 @@ async fn handle_peer_frame(
peer_send_cmpct: &mut bool,
peer_cmpct_version: &mut u32,
pending_headers: &mut HashMap<BlockHash, bitcoin::block::Header>,
pending_blocks: &mut HashMap<BlockHash, bitcoin::Block>,
pending_blocks: &mut PendingBlocks,
pending_cmpct: &mut HashMap<BlockHash, PendingCmpct>,
from_this_peer: &mut HashMap<bitcoin::Txid, ()>,
requested_blocks: &mut HashSet<BlockHash>,
Expand Down Expand Up @@ -1883,7 +1922,7 @@ async fn handle_peer_frame(
requested_blocks.remove(&hash);
pending_headers.entry(hash).or_insert(block.header);
if !any_header_path_meets_minwork(hub, pending_headers, hash) {
stash_pending_block(pending_blocks, hash, block.clone());
pending_blocks.insert(hash, block.clone());
return Ok(());
}
match hub.accept_received_block(block.clone()) {
Expand Down Expand Up @@ -2555,7 +2594,7 @@ fn fetchable_header_path_bodies(
hub: &ChainHub,
pending: &HashMap<BlockHash, bitcoin::block::Header>,
tip: BlockHash,
pending_blocks: &HashMap<BlockHash, bitcoin::Block>,
pending_blocks: &PendingBlocks,
requested: &HashSet<BlockHash>,
) -> Vec<BlockHash> {
if !header_path_meets_minwork(hub, pending, tip) {
Expand All @@ -2575,7 +2614,7 @@ fn missing_blocks_on_header_path(
hub: &ChainHub,
pending: &HashMap<BlockHash, bitcoin::block::Header>,
tip: BlockHash,
pending_blocks: &HashMap<BlockHash, bitcoin::Block>,
pending_blocks: &PendingBlocks,
requested: &HashSet<BlockHash>,
) -> Vec<BlockHash> {
let mut path = Vec::new();
Expand Down Expand Up @@ -2687,7 +2726,7 @@ fn pending_header_leaves(pending: &HashMap<BlockHash, bitcoin::block::Header>) -
fn drain_pending(
hub: &ChainHub,
out: &mpsc::UnboundedSender<NetworkMessage>,
pending_blocks: &mut HashMap<BlockHash, bitcoin::Block>,
pending_blocks: &mut PendingBlocks,
pending_headers: &mut HashMap<BlockHash, bitcoin::block::Header>,
requested_blocks: &mut HashSet<BlockHash>,
compact: bool,
Expand Down Expand Up @@ -2737,7 +2776,7 @@ fn drain_pending(
/// download window, not a second most-work assembler.
fn drain_pending_once(
hub: &ChainHub,
pending_blocks: &mut HashMap<BlockHash, bitcoin::Block>,
pending_blocks: &mut PendingBlocks,
pending_headers: &mut HashMap<BlockHash, bitcoin::block::Header>,
) -> Result<(), NetError> {
let mut progress = true;
Expand Down
Loading
Loading