-
Notifications
You must be signed in to change notification settings - Fork 0
08 block out protocols
Companion to
06-sessions-and-protocolsand07-header-protocols.Block-out is the block-serving side: respond to peer requests (
get_blocks,get_data) with inventory / block bodies, and announce newly-confirmed blocks viainvso peers know to come ask. Two versions:
- 106 (
protocol_block_out_106) — full implementation: servesget_blocks/get_data, announces new blocks viainv.- 70012 (
protocol_block_out_70012) — minimal extension that suppresses inv-based announcement when the peer has opted intosendheaders(BIP130). The header-out path takes over announcement; this protocol then only serves data on request.A common confusion: 70012 is not BIP152 (compact blocks). BIP152 is not implemented in this repo (see
session_peer.ipp:92-97comment). The 70012 number reflects the handshake level at which BIP130 attaches, nothing more.
| File | Lines | Role |
|---|---|---|
src/protocols/protocol_block_out_106.cpp |
262 | Full block-serving + inv announcement |
src/protocols/protocol_block_out_70012.cpp |
75 | Adds sendheaders handler → sets superseded_ flag |
classDiagram
class protocol_peer
class protocol_block_out_106 {
+start
+stopping
+handle_event
+do_announce
+superseded() default false
+handle_receive_get_blocks
+handle_receive_get_data
+send_block
-backlog_ : deque~inventory_item~
-node_witness_, allow_overlapped_
}
class protocol_block_out_70012 {
+start (override)
+superseded() override
+handle_receive_send_headers
-superseded_ : atomic_bool
}
protocol_peer <|-- protocol_block_out_106
protocol_block_out_106 <|-- protocol_block_out_70012
Invariant (BlockOut-Inherit-1). 70012 is a strict superset of 106. All 106 behaviour remains in effect; 70012 only adds the "supersede" gate on outbound announcement.
// :41-53
void start() {
subscribe_events(BIND(handle_event, _1, _2, _3)); // bus
SUBSCRIBE_CHANNEL(get_data, handle_receive_get_data, _1, _2);
SUBSCRIBE_CHANNEL(get_blocks, handle_receive_get_blocks, _1, _2);
protocol_peer::start();
}
// :55-61
void stopping(ec) {
unsubscribe_events();
protocol_peer::stopping(ec);
}The protocol does three things:
- Listens for
chase::blockfrom the bus → emitinvto peer. - Listens for
get_blocksfrom peer → reply withinv. - Listens for
get_datafrom peer → start a streaming send of blocks.
// :99-121
bool do_announce(header_t link) {
const auto hash = archive().get_header_key(link);
if (was_announced(hash)) // anti-echo
return true;
if (hash == null_hash) return true; // store inconsistency; logged only
SEND(inventory{ { { type_id::block, hash } } }, handle_send, _1);
return true;
}Invariant (BlockOut-Announce-1). A block is announced only if (a) it wasn't received from this peer (
was_announcedreturns false), and (b)get_header_key(link)returned a non-null hash. Anti-echo discipline matchesprotocol_header_out_70012(07 §5.3).
Invariant (BlockOut-Announce-2). Announcement is via a single
invcontaining{ type_id::block, hash }. The peer then sendsget_datato retrieve the block body if it wants it. See the get-data path in §2.4.
Note for the spec.
chase::blockis consumed by three protocols (chaser_snapshot,protocol_block_out_106, andprotocol_header_out_70012— see01-event-bus §2.6). All three react independently; per-channel anti-echo prevents duplicate sends.
// :126-138
bool handle_receive_get_blocks(ec, message) {
SEND(create_inventory(*message), handle_send, _1);
return true;
}
// :244-256
inventory create_inventory(get_blocks& locator) {
if (!is_current(true)) return {};
return inventory::factory
(
archive().get_blocks(locator.start_hashes, locator.stop_hash,
messages::peer::max_get_blocks),
type_id::block
);
}Invariant (BlockOut-Serve-1). Like header-out, block-out is gated on
is_current(true)— non-current nodes reply empty (HeaderOut-Serve-1 mirror). This prevents propagating stale block sets.
This is the streaming path with a per-channel backlog. The reasoning is
documented inline (:170-181):
"Satoshi sends overlapping get_data requests, but assumes that the recipient is blocking all traffic until the previous is completed. So to prevent frequent drops of satoshi peers, and not let one protocol block all others, we must accumulate the requests into a backlog. If the backlog exceeds the individual message limit we drop the peer."
// :143-191
bool handle_receive_get_data(ec, message) {
if (!node_witness_ && message->any_witness()) {
stop(network::error::protocol_violation); // ← A
return false;
}
const auto size = message->count(get_data::selector::blocks);
if (is_zero(size)) return true; // no blocks; ignore
const auto total = ceilinged_add(backlog_.size(), size);
if (total > network::messages::peer::max_inventory) {
stop(network::error::protocol_violation); // ← B
return false;
}
const auto idle = backlog_.empty();
if (!allow_overlapped_ && !idle) {
stop(network::error::protocol_violation); // ← C
return false;
}
merge_inventory(message->items); // append block-typed items only
if (idle)
send_block(error::success); // kick off send loop
return true;
}Three peer-drop conditions:
- A — peer requested witness data but node doesn't advertise witness service.
-
B — total queued requests exceed
max_inventory(= upper bound for a singleinvmessage; reused here as a defensive bound). -
C — overlapping request when
allow_overlapped == false.
Invariant (BlockOut-Backlog-1).
backlog_.size() ≤ max_inventoryalways. Oncemerge_inventorywould push it over, the peer is dropped first. The bound is therefore strict, not "eventually consistent".
Invariant (BlockOut-Backlog-2).
merge_inventorydiscards non-block items (:236-242). The deque holds only block requests.
// :196-231
void send_block(ec) {
if (stopped(ec)) return;
if (backlog_.empty()) return;
const auto& item = backlog_.front();
const auto witness = item.is_witness_type();
if (!node_witness_ && witness) {
stop(network::error::protocol_violation);
return;
}
const auto link = query.to_header(item.hash);
node::messages::block out{ query.get_wire_block(link, witness), witness };
if (out.block_data.empty()) {
stop(system::error::not_found);
return;
}
backlog_.pop_front();
span<microseconds>(events::block_usecs, start);
SEND(std::move(out), send_block, _1); // ← recursive: callback is send_block
}The continuation is send_block itself: each completed SEND triggers
the next iteration. The loop terminates when backlog_ is empty (or
on error / stop).
Invariant (BlockOut-Stream-1). At any time, at most one
SEND(block, ...)is in flight per channel. The next is started only from the previous's completion callback. This is the natural back-pressure: a slow peer slows the send rate.
Invariant (BlockOut-Stream-2). A
not_foundfromget_wire_blockdrops the peer (:218-225). The comment notes "This block could not have been advertised to the peer" — only blocks we (or our peer set) previously announced should be requested. So anot_foundis a protocol violation, not a store consistency issue.
Two separate witness-gating sites:
-
handle_receive_get_data(request-time): drop if peer requests witness data we don't advertise. -
send_block(per-item): drop if a queued item turns out to want witness data (defensive, sincehandle_receive_get_dataalready vetted).
node_witness_ = network_settings().witness_node() — set at
construction (protocol_block_out_106.hpp:39).
Invariant (BlockOut-Witness-1). A non-witness-serving node will drop any peer that requests witness data, at either the inv-batch level or the per-item level. There is no path that serves non-witness data in response to a witness request.
Only ~30 lines of substantive code.
// :39-48
void start() {
SUBSCRIBE_CHANNEL(send_headers, handle_receive_send_headers, _1, _2);
protocol_block_out_106::start();
}
// :53-63
bool handle_receive_send_headers(ec, message) {
superseded_ = true;
return false; // ← one-shot
}
// :66-69
bool superseded() const { return superseded_; }The base handle_event checks superseded() first:
// protocol_block_out_106.cpp:66-71
if (stopped() || superseded())
return false; // ← unsubscribe from busWhen superseded_ flips true:
- The very next
chase::blockfrom the bus triggershandle_event. -
handle_eventseessuperseded() == trueand returns false. - The bus desubscriber removes this protocol from the event list.
- Subsequent
chase::blockevents are not delivered here at all.
Invariant (BlockOut-Supersede-1). After the peer sends
sendheaders, this protocol no longer emitsinv-based block announcements. Header-based announcement (viaprotocol_header_out_70012) takes over.
Invariant (BlockOut-Supersede-2). Supersede is one-way: once true, never reset. The peer cannot "un-opt-in" mid-channel.
Note for the spec. The combined behaviour after
sendheaders:
protocol_header_out_70012: subscribes tochase::block, sendsheaders[1]announcement.protocol_block_out_70012(this class): unsubscribes fromchase::block. Still servesget_data(block bodies) andget_blocks(inventory locator queries).Net effect: announce via headers; serve via 106 paths.
protocol_block_out_70012::superseded() is protected, so the
derived class (protocol_block_out_70012) exposes read access to
its own base (protocol_block_out_106::handle_event) which uses it
as the supersede gate. The base reads superseded() from its bus
handler context; the derived class writes superseded_ from its
handle_receive_send_headers channel handler. Making the flag
atomic allows that read to happen without posting through the
channel strand — i.e. the gate is non-stranded by design, and the
atomic is what makes that safe.
sequenceDiagram
autonumber
participant PEER as peer
participant POUT as protocol_block_out_106
participant Q as query
PEER->>POUT: get_blocks (locator)
POUT->>Q: get_blocks(start_hashes, stop_hash, max)
POUT->>PEER: inv (block hashes)
PEER->>POUT: get_data (block hashes)
Note over POUT: backlog_ = [items], idle so send_block()
loop until backlog empty
POUT->>Q: get_wire_block(link, witness)
POUT->>PEER: block (full body)
Note over POUT: send completion → send_block() again
end
sequenceDiagram
autonumber
participant CNF as chaser_confirm
participant Bus as event bus
participant POUT as protocol_block_out_106
participant PEER as peer
CNF->>Bus: notify(chase::block, link)
Bus-->>POUT: handle_event
POUT->>POUT: do_announce(link)
alt was_announced(hash)
Note over POUT: suppress
else
POUT->>PEER: inv (one block hash)
end
opt peer wants it
PEER->>POUT: get_data
Note over POUT: enters send_block loop as in §4.1
end
sequenceDiagram
autonumber
participant PEER as peer (BIP130)
participant POUT as protocol_block_out_70012
participant HOUT as protocol_header_out_70012
participant Bus as event bus
participant CNF as chaser_confirm
PEER->>POUT: send_headers
POUT->>POUT: superseded_ = true
PEER->>HOUT: send_headers
HOUT->>HOUT: subscribe_events(handle_event)
Note over POUT,HOUT: future chase::block routes to HOUT only
CNF->>Bus: notify(chase::block, link)
Bus-->>POUT: handle_event → returns false (supersede unsubscribes)
Bus-->>HOUT: handle_event → POST(do_announce)
HOUT->>PEER: headers[1]
opt peer wants body
PEER->>POUT: get_data
Note over POUT: 106 serving path
end
| Protocol | Subscribes to | Reaction |
|---|---|---|
protocol_block_out_106 |
chase::block |
do_announce(link) ⇒ send inv
|
protocol_block_out_70012 |
chase::block (inherited) |
Same as 106 until superseded_ == true; then handle_event returns false → unsubscribed |
Both protocols also subscribe to the bus during construction (via
protocol_block_out_106::start). 70012 inherits that and adds a
condition under which it self-unsubscribes.
| Code | Site | Trigger |
|---|---|---|
network::error::protocol_violation |
:152-155, :163-168, :176-181, :206-210 |
witness/inv-limit/overlap/witness-on-send violations |
system::error::not_found |
:218-225 |
requested block has no body in store |
No node-faults from this protocol family. Failures are per-channel.
| Setting (file) | Effect |
|---|---|
network.witness_node |
Whether to serve witness data. If false: drop peers requesting witness |
node.allow_overlapped |
If false: drop peer that issues overlapping get_data while a previous is in flight |
protocol_block_out_106 : Process
state: backlog : Deque InventoryItem (bounded by max_inventory)
inputs:
bus chase::block(link) → emit inv to peer (filtered by was_announced)
peer get_blocks(locator) → emit inv (from get_blocks query)
peer get_data(items) → enqueue items; kick send loop if idle
send completion → pop one; SEND next; loop
outputs:
peer inv | block messages
state: superseded : Atomic Bool (one-way latch)
inputs (additional):
peer send_headers → superseded := true
gating:
handle_event(chase::block) returns false if superseded
- Anti-echo (BlockOut-Announce-1).
-
Backlog bounded (BlockOut-Backlog-1): never exceeds
max_inventory. Peer is dropped before overflow. - One in-flight send (BlockOut-Stream-1): no concurrent block sends per channel.
- Supersede monotone (BlockOut-Supersede-2): once superseded, always superseded.
- The send loop drains the backlog as long as the peer accepts data and the store yields block bodies.
- Stop is non-blocking; an in-flight send completes through the channel's stop machinery (libbitcoin-network) and the backlog is abandoned.
- The async-send loop is naturally tail-recursive:
send_blockcallsSEND(..., send_block, ...). In C++ this relies on the callback being posted on the strand. - The peer-drop conditions in
handle_receive_get_dataare straightforwardcond/switchcases. -
superseded_is a one-bit latch — a single boolean.
- The protocol is strand-confined except for the
superseded_atomic (and even that is realistically strand-only). Single-threaded reasoning suffices per channel. - The combined "announce via headers OR inv, never both" property
needs joint reasoning across
protocol_block_out_70012andprotocol_header_out_70012: they consume the samechase::blockevent, and exactly one (or zero) will issue an announcement per block per channel. This is enforced by the supersede flag in this protocol turning the header-out protocol into the sole announcer. - A peer's behaviour (deciding to send
sendheadersor not, when to issueget_data, etc.) is an oracle in the model.
-
05-chaser-confirm§7 — issueschase::blockconsumed here -
06-sessions-and-protocols§2.3 — attach tree (which version attaches when) -
07-header-protocols§5 — the header-out_70012 counterpart that takes over announcement when this one is superseded -
09-filter-out-70015(BIP157/158 client filters) -
10-tx-protocols(transaction in/out 106) -
11-protocol-block-in-106(legacy blocks-first)
Users | Developers | License | Copyright © 2011-2018 libbitcoin developers