p2p: Add `sendtxrcncl` to network messages
What changed, and why it matters
This commit adds support for a new Bitcoin peer-to-peer message type called `sendtxrcncl` (transaction reconciliation, part of the Erlay protocol). It is a routine protocol feature addition: it wires up encoding and decoding for the new message so the library can send and receive it. There is no indication in the commit of a security bug, fix, or vulnerability.
No security action required. Review the corresponding `message_erlay::SendTxRcnCl` implementation for correct parsing limits as part of normal code review, but this commit itself does not introduce or fix a security issue.
Security signals we found
No security-relevant signals in commit message or diff.
Change is additive protocol support, not a patch for a vulnerability.
No mention of CVE, security advisory, researcher credit, or bug class.
Evidence from the diff
The change extends p2p/src/message.rs to include message_erlay::SendTxRcnCl in the NetworkMessage enum, its command string mapping, the v1 encoder/decoder dispatch, and the v2 decoder dispatch. It also adds the type to fuzzing roundtrip targets and to an exclusion list for an encodable-coverage check. The commit message explicitly notes sendtxrcncl has not been assigned a v2 shortID, so it falls back to the generic v1-style command handling. No bounds-checking, resource-limit, or parsing logic changes that would indicate a security fix are present.
Changed components
rust-bitcoin p2p message encoding/decodingNetworkMessage enum and encoders/decodersfuzz test generation scriptencodable coverage check scriptInspect captured patch +20 / −2
diff --git a/contrib/check-encodable-coverage.sh b/contrib/check-encodable-coverage.sh
index ee46963f..8375bf53 100755
--- a/contrib/check-encodable-coverage.sh
+++ b/contrib/check-encodable-coverage.sh
@@ -18,7 +18,8 @@ TRAIT_IMPL_JS="$REPO_DIR/target/doc/trait.impl/bitcoin_consensus_encoding/encode
# - HeadersMessage has no type in 0.32 bitcoin. Vec<(Header, u8)> is not Decodable.
# - InventoryPayload has no type in 0.32 bitcoin. Vec<Inventory> fails special case consideration.
# - FeeFilter is a FeeRate newtype. FeeRate has no old Encodable/Decodable and is just a u64 le encoding in FeeFilter.
-EXCLUSIONS="CommandString HeadersMessage InventoryPayload FeeFilter NetworkMessage Script Validation V2NetworkMessage V1MessageHeader"
+# - SendTxRcnCl is a new type that does not have a comparison.
+EXCLUSIONS="CommandString HeadersMessage InventoryPayload FeeFilter NetworkMessage Script Validation V2NetworkMessage V1MessageHeader SendTxRcnCl"
main() {
check_required_commands
diff --git a/fuzz/generate-encoding-roundtrip.sh b/fuzz/generate-encoding-roundtrip.sh
index 14d7190c..8251aadb 100755
--- a/fuzz/generate-encoding-roundtrip.sh
+++ b/fuzz/generate-encoding-roundtrip.sh
@@ -79,6 +79,7 @@ ROUNDTRIP_TYPES=(
"p2p::message_network::RejectReason"
"p2p::message_network::UserAgent"
"p2p::message_network::VersionMessage"
+ "p2p::message_erlay::SendTxRcnCl"
)
# Types tested with check_script_roundtrip (Buf types that Deref to their Encodable target).
diff --git a/p2p/src/message.rs b/p2p/src/message.rs
index 53431339..544e6b0e 100644
--- a/p2p/src/message.rs
+++ b/p2p/src/message.rs
@@ -25,6 +25,7 @@ use units::{Amount, FeeRate};
use self::error::V1NetworkMessageDecoderErrorInner;
use crate::address::{AddrV1Message, AddrV2Message};
use crate::merkle_tree::MerkleBlock;
+use crate::message_erlay::{SendTxRcnCl, SendTxRcnClDecoder};
use crate::{
bip152, message_blockdata, message_bloom, message_compact_blocks, message_filter,
message_network, Magic,
@@ -748,7 +749,8 @@ pub enum NetworkMessage {
AddrV2(AddrV2Payload),
/// `sendaddrv2`
SendAddrV2,
-
+ /// `sendtxrcncl`
+ SendTxRcnCl(SendTxRcnCl),
/// Any other message.
Unknown {
/// The command of this message.
@@ -802,6 +804,7 @@ impl NetworkMessage {
Self::WtxidRelay => "wtxidrelay",
Self::AddrV2(_) => "addrv2",
Self::SendAddrV2 => "sendaddrv2",
+ Self::SendTxRcnCl(_) => "sendtxrcncl",
Self::Unknown { .. } => "unknown",
}
}
@@ -940,6 +943,8 @@ pub enum NetworkMessageEncoder<'e> {
FeeFilter(<FeeFilter as encoding::Encode>::Encoder<'e>),
/// Encodes [`NetworkMessage::AddrV2`]
AddrV2(<AddrV2Payload as encoding::Encode>::Encoder<'e>),
+ /// Encodes [`NetworkMessage::SendTxRcnCl`].
+ SendTxRcnCl(<SendTxRcnCl as encoding::Encode>::Encoder<'e>),
/// Encodes zero-payload messages: verack, mempool, sendheaders, getaddr, wtxidrelay,
/// filterclear, sendaddrv2.
Empty,
@@ -980,6 +985,7 @@ impl<'e> NetworkMessageEncoder<'e> {
NetworkMessage::Reject(dat) => Self::Reject(dat.encoder()),
NetworkMessage::FeeFilter(dat) => Self::FeeFilter(dat.encoder()),
NetworkMessage::AddrV2(dat) => Self::AddrV2(dat.encoder()),
+ NetworkMessage::SendTxRcnCl(dat) => Self::SendTxRcnCl(dat.encoder()),
NetworkMessage::Verack
| NetworkMessage::SendHeaders
| NetworkMessage::MemPool
@@ -1024,6 +1030,7 @@ impl encoding::Encoder for NetworkMessageEncoder<'_> {
Self::Reject(e) => e.current_chunk(),
Self::FeeFilter(e) => e.current_chunk(),
Self::AddrV2(e) => e.current_chunk(),
+ Self::SendTxRcnCl(e) => e.current_chunk(),
Self::Empty => &[],
Self::Unknown(e) => e.current_chunk(),
}
@@ -1058,6 +1065,7 @@ impl encoding::Encoder for NetworkMessageEncoder<'_> {
Self::Reject(e) => e.advance(),
Self::FeeFilter(e) => e.advance(),
Self::AddrV2(e) => e.advance(),
+ Self::SendTxRcnCl(e) => e.advance(),
Self::Empty => false,
Self::Unknown(e) => e.advance(),
}
@@ -1127,6 +1135,7 @@ enum NetworkMessageDecoderInner {
Reject(message_network::RejectDecoder),
FeeFilter(FeeFilterDecoder),
AddrV2(AddrV2PayloadDecoder),
+ SendTxRcnCl(SendTxRcnClDecoder),
/// Zero-payload messages: verack, mempool, sendheaders, getaddr, wtxidrelay,
/// filterclear, sendaddrv2.
Empty(CommandString),
@@ -1172,6 +1181,7 @@ impl NetworkMessageDecoderInner {
"alert" => Self::Alert(message_network::Alert::decoder()),
"reject" => Self::Reject(message_network::Reject::decoder()),
"feefilter" => Self::FeeFilter(FeeFilter::decoder()),
+ "sendtxrcncl" => Self::SendTxRcnCl(SendTxRcnCl::decoder()),
"addrv2" => Self::AddrV2(AddrV2Payload::decoder()),
_ => Self::Unknown {
command,
@@ -1218,6 +1228,7 @@ impl encoding::Decoder for NetworkMessageDecoderInner {
Self::Reject(d) => d.push_bytes(bytes).map_err(|_| err),
Self::FeeFilter(d) => d.push_bytes(bytes).map_err(|_| err),
Self::AddrV2(d) => d.push_bytes(bytes).map_err(|_| err),
+ Self::SendTxRcnCl(d) => d.push_bytes(bytes).map_err(|_| err),
Self::Empty(_) => Ok(false),
Self::Unknown { remaining, buffer, .. } => {
let copy_len = bytes.len().min(*remaining);
@@ -1263,6 +1274,7 @@ impl encoding::Decoder for NetworkMessageDecoderInner {
Self::Reject(d) => Ok(NetworkMessage::Reject(d.end().map_err(|_| err)?)),
Self::FeeFilter(d) => Ok(NetworkMessage::FeeFilter(d.end().map_err(|_| err)?)),
Self::AddrV2(d) => Ok(NetworkMessage::AddrV2(d.end().map_err(|_| err)?)),
+ Self::SendTxRcnCl(d) => Ok(NetworkMessage::SendTxRcnCl(d.end().map_err(|_| err)?)),
Self::Empty(cmd) => match cmd.as_ref() {
"verack" => Ok(NetworkMessage::Verack),
"mempool" => Ok(NetworkMessage::MemPool),
@@ -1312,6 +1324,7 @@ impl encoding::Decoder for NetworkMessageDecoderInner {
Self::Reject(d) => d.read_limit(),
Self::FeeFilter(d) => d.read_limit(),
Self::AddrV2(d) => d.read_limit(),
+ Self::SendTxRcnCl(d) => d.read_limit(),
Self::Empty(_) => 0,
Self::Unknown { remaining, .. } => *remaining,
}
@@ -1584,6 +1597,7 @@ fn v2_command_byte(payload: &NetworkMessage) -> (u8, Option<CommandString>) {
| NetworkMessage::SendAddrV2
| NetworkMessage::Alert(_)
| NetworkMessage::Reject(_)
+ | NetworkMessage::SendTxRcnCl(_)
| NetworkMessage::Unknown { .. } => (0u8, Some(payload.command())),
}
}
@@ -1800,6 +1814,7 @@ impl V2NetworkMessageDecoder {
"verack" | "sendheaders" | "getaddr" | "wtxidrelay" | "sendaddrv2" => E::Empty(command),
"alert" => E::Alert(message_network::Alert::decoder()),
"reject" => E::Reject(message_network::Reject::decoder()),
+ "sendtxrcncl" => E::SendTxRcnCl(SendTxRcnCl::decoder()),
_ => E::Unknown {
command,
remaining: 0, // no payload length, buffer all bytes until end().
@@ -2559,6 +2574,7 @@ mod test {
)),
NetworkMessage::BlockTxn(blocktxn),
NetworkMessage::SendCmpct(SendCmpct { send_compact: true, version: 8333 }),
+ NetworkMessage::SendTxRcnCl(SendTxRcnCl::from_salt(224)),
];
for msg in &msgs {
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.