Skip to content

Commit 48efa42

Browse files
committed
Merge rust-bitcoin#5508: p2p: Implement encoding traits for BlockTransactionsRequest
b34d882 p2p: Implement `encoding` traits for `BlockTransactionsRequest` (rustaceanrob) 940602f bip152: Add an `Offset` wrapper type (rustaceanrob) Pull request description: First commit walks through a newtype introduced here. I took the liberty of hacking at this API in an attempt to improve it, but I am open to other approaches. ACKs for top commit: tcharding: ACK b34d882 apoelstra: ACK b34d882; successfully ran local tests; let me know if I should merge this or if you want to try to address my comment here Tree-SHA512: 16c680666ae8afb571db892c827fe0c6c59f8ff7e63334b4bc3220069d2b1959bb8e117282b1e7d07c0be86f5719e4f1e597432b9f954c7c9784d2126f02ea03
2 parents 6cfc2d2 + b34d882 commit 48efa42

2 files changed

Lines changed: 208 additions & 50 deletions

File tree

p2p/src/bip152.rs

Lines changed: 202 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ use std::error;
1414
use arbitrary::{Arbitrary, Unstructured};
1515
use bitcoin::consensus::encode::{self, Decodable, Encodable, ReadExt, WriteExt};
1616
use bitcoin::{block, Block, BlockChecked, BlockHash, Transaction};
17+
use encoding::{CompactSizeDecoder, CompactSizeEncoder, Decoder2, Encoder2, SliceEncoder, VecDecoder};
1718
use hashes::{sha256, siphash24};
1819
use internals::array::ArrayExt as _;
19-
use internals::ToU64 as _;
20+
use internals::write_err;
2021
use io::{BufRead, Write};
22+
use primitives::block::{BlockHashDecoder, BlockHashEncoder};
2123

2224
/// A BIP-0152 error
2325
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -314,27 +316,191 @@ impl HeaderAndShortIds {
314316
}
315317
}
316318

319+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
320+
struct Offset(usize);
321+
322+
impl encoding::Encodable for Offset {
323+
type Encoder<'e> = CompactSizeEncoder;
324+
325+
fn encoder(&self) -> Self::Encoder<'_> {
326+
CompactSizeEncoder::new(self.0)
327+
}
328+
}
329+
330+
struct OffsetDecoder(CompactSizeDecoder);
331+
332+
impl encoding::Decoder for OffsetDecoder {
333+
type Output = Offset;
334+
type Error = <CompactSizeDecoder as encoding::Decoder>::Error;
335+
336+
#[inline]
337+
fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
338+
self.0.push_bytes(bytes)
339+
}
340+
341+
#[inline]
342+
fn end(self) -> Result<Self::Output, Self::Error> {
343+
Ok(Offset(self.0.end()?))
344+
}
345+
346+
#[inline]
347+
fn read_limit(&self) -> usize { self.0.read_limit() }
348+
}
349+
350+
impl encoding::Decodable for Offset {
351+
type Decoder = OffsetDecoder;
352+
353+
fn decoder() -> Self::Decoder {
354+
OffsetDecoder(CompactSizeDecoder::new())
355+
}
356+
}
357+
317358
/// A [`BlockTransactionsRequest`] structure is used to list transaction indexes
318359
/// in a block being requested.
319360
#[derive(PartialEq, Eq, Clone, Debug, PartialOrd, Ord, Hash)]
320361
pub struct BlockTransactionsRequest {
321362
/// The blockhash of the block which the transactions being requested are in.
322363
pub block_hash: BlockHash,
323-
/// The indexes of the transactions being requested in the block.
364+
// The run-length between block indices in the request.
365+
offsets: Vec<Offset>,
366+
}
367+
368+
impl BlockTransactionsRequest {
369+
/// Build a request for a [`BlockHash`] and implicitly sort the indices on construction.
370+
pub fn from_unsorted_indices(block_hash: BlockHash, mut indexes: Vec<usize>) -> Self {
371+
indexes.sort_unstable();
372+
let mut offsets = Vec::with_capacity(indexes.len());
373+
let mut last_idx = 0;
374+
for idx in indexes {
375+
offsets.push(Offset(idx - last_idx));
376+
last_idx = idx + 1;
377+
}
378+
Self { block_hash, offsets }
379+
}
380+
381+
/// Build a request for a [`BlockHash`] assuming the indices are already sorted.
382+
///
383+
/// # Panics
384+
///
385+
/// If the list is not in ascending order.
386+
pub fn from_indices_unchecked(block_hash: BlockHash, indexes: Vec<usize>) -> Self {
387+
let mut offsets = Vec::with_capacity(indexes.len());
388+
let mut last_idx = 0;
389+
for idx in indexes {
390+
offsets.push(Offset(idx - last_idx));
391+
last_idx = idx + 1;
392+
}
393+
Self { block_hash, offsets }
394+
}
395+
396+
/// Get the list of indices in the block.
397+
///
398+
/// # Errors
324399
///
325-
/// Warning: Encoding panics with [`u64::MAX`] values. See [`BlockTransactionsRequest::consensus_encode()`]
326-
pub indexes: Vec<u64>,
400+
/// If the block index is out of range.
401+
pub fn indices(&self) -> Result<Vec<usize>, TxIndexOutOfRangeError> {
402+
let mut last_cs: usize = 0;
403+
let mut indexes = Vec::with_capacity(self.offsets.len());
404+
for offset in &self.offsets {
405+
last_cs = match last_cs.checked_add(offset.0) {
406+
Some(next) => {
407+
indexes.push(next);
408+
next
409+
},
410+
None => {
411+
return Err(TxIndexOutOfRangeError(last_cs as u64))
412+
}
413+
};
414+
last_cs = last_cs.checked_add(1).ok_or(TxIndexOutOfRangeError(last_cs as u64))?;
415+
}
416+
Ok(indexes)
417+
}
418+
}
419+
420+
encoding::encoder_newtype! {
421+
/// The encoder for [`BlockTransactionsRequest`].
422+
pub struct BlockTransactionsRequestEncoder<'e>(
423+
Encoder2<
424+
BlockHashEncoder,
425+
Encoder2<CompactSizeEncoder, SliceEncoder<'e, Offset>>
426+
>
427+
);
428+
}
429+
430+
impl encoding::Encodable for BlockTransactionsRequest {
431+
type Encoder<'e> = BlockTransactionsRequestEncoder<'e>;
432+
433+
fn encoder(&self) -> Self::Encoder<'_> {
434+
BlockTransactionsRequestEncoder(
435+
Encoder2::new(
436+
self.block_hash.encoder(),
437+
Encoder2::new(
438+
CompactSizeEncoder::new(self.offsets.len()),
439+
SliceEncoder::without_length_prefix(&self.offsets)
440+
)
441+
)
442+
)
443+
}
444+
}
445+
446+
type BlockTransactionsRequestInnerDecoder = Decoder2<BlockHashDecoder, VecDecoder<Offset>>;
447+
448+
/// The encoder type for a [`BlockTransactionsRequest`].
449+
pub struct BlockTransactionsRequestDecoder(BlockTransactionsRequestInnerDecoder);
450+
451+
impl encoding::Decoder for BlockTransactionsRequestDecoder {
452+
type Output = BlockTransactionsRequest;
453+
type Error = BlockTransactionsRequestDecoderError;
454+
455+
#[inline]
456+
fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
457+
self.0.push_bytes(bytes).map_err(BlockTransactionsRequestDecoderError)
458+
}
459+
460+
#[inline]
461+
fn end(self) -> Result<Self::Output, Self::Error> {
462+
let (block_hash, offsets) = self.0.end().map_err(BlockTransactionsRequestDecoderError)?;
463+
Ok(BlockTransactionsRequest { block_hash, offsets })
464+
}
465+
466+
#[inline]
467+
fn read_limit(&self) -> usize { self.0.read_limit() }
468+
}
469+
470+
impl encoding::Decodable for BlockTransactionsRequest {
471+
type Decoder = BlockTransactionsRequestDecoder;
472+
473+
fn decoder() -> Self::Decoder {
474+
BlockTransactionsRequestDecoder(Decoder2::new(BlockHashDecoder::new(), VecDecoder::new()))
475+
}
476+
}
477+
478+
/// An error decoding a [`BlockTransactionsRequest`] message.
479+
#[derive(Debug, Clone, PartialEq, Eq)]
480+
pub struct BlockTransactionsRequestDecoderError(<BlockTransactionsRequestInnerDecoder as encoding::Decoder>::Error);
481+
482+
impl From<Infallible> for BlockTransactionsRequestDecoderError {
483+
fn from(never: Infallible) -> Self { match never {} }
484+
}
485+
486+
impl fmt::Display for BlockTransactionsRequestDecoderError {
487+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
488+
write_err!(f, "blocktxnrequest error"; self.0)
489+
}
490+
}
491+
492+
#[cfg(feature = "std")]
493+
impl std::error::Error for BlockTransactionsRequestDecoderError {
494+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.0) }
327495
}
328496

329497
impl Encodable for BlockTransactionsRequest {
330498
fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
331499
let mut len = self.block_hash.consensus_encode(w)?;
332500
// Manually encode indexes because they are differentially encoded as CompactSize.
333-
len += w.emit_compact_size(self.indexes.len())?;
334-
let mut last_idx = 0;
335-
for idx in &self.indexes {
336-
len += w.emit_compact_size(*idx - last_idx)?;
337-
last_idx = *idx + 1; // can panic here
501+
len += w.emit_compact_size(self.offsets.len())?;
502+
for idx in &self.offsets {
503+
len += w.emit_compact_size(idx.0)?;
338504
}
339505
Ok(len)
340506
}
@@ -344,7 +510,7 @@ impl Decodable for BlockTransactionsRequest {
344510
fn consensus_decode<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
345511
Ok(Self {
346512
block_hash: BlockHash::consensus_decode(r)?,
347-
indexes: {
513+
offsets: {
348514
// Manually decode indexes because they are differentially encoded as CompactSize.
349515
let nb_indexes = r.read_compact_size()? as usize;
350516

@@ -361,28 +527,12 @@ impl Decodable for BlockTransactionsRequest {
361527
}
362528
.into());
363529
}
364-
365-
let mut indexes = Vec::with_capacity(nb_indexes);
366-
let mut last_index: u64 = 0;
530+
let mut offsets = Vec::with_capacity(nb_indexes);
367531
for _ in 0..nb_indexes {
368532
let differential = r.read_compact_size()?;
369-
last_index = match last_index.checked_add(differential) {
370-
Some(i) => i,
371-
None =>
372-
return Err(crate::consensus::parse_failed_error(
373-
"block index overflow",
374-
)),
375-
};
376-
indexes.push(last_index);
377-
last_index = match last_index.checked_add(1) {
378-
Some(i) => i,
379-
None =>
380-
return Err(crate::consensus::parse_failed_error(
381-
"block index overflow",
382-
)),
383-
};
533+
offsets.push(Offset(differential as usize));
384534
}
385-
indexes
535+
offsets
386536
},
387537
})
388538
}
@@ -435,12 +585,13 @@ impl BlockTransactions {
435585
Ok(Self {
436586
block_hash: request.block_hash,
437587
transactions: {
438-
let mut txs = Vec::with_capacity(request.indexes.len());
439-
for idx in &request.indexes {
440-
if *idx >= block.transactions().len().to_u64() {
441-
return Err(TxIndexOutOfRangeError(*idx));
588+
let mut txs = Vec::with_capacity(request.offsets.len());
589+
let indexes = request.indices()?;
590+
for idx in indexes {
591+
if idx >= block.transactions().len() {
592+
return Err(TxIndexOutOfRangeError(idx as u64));
442593
}
443-
txs.push(block.transactions()[*idx as usize].clone());
594+
txs.push(block.transactions()[idx].clone());
444595
}
445596
txs
446597
},
@@ -479,10 +630,17 @@ impl<'a> Arbitrary<'a> for BlockTransactions {
479630
}
480631
}
481632

633+
#[cfg(feature = "arbitrary")]
634+
impl<'a> Arbitrary<'a> for Offset {
635+
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
636+
Ok(Self(u.arbitrary()?))
637+
}
638+
}
639+
482640
#[cfg(feature = "arbitrary")]
483641
impl<'a> Arbitrary<'a> for BlockTransactionsRequest {
484642
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
485-
Ok(Self { block_hash: u.arbitrary()?, indexes: Vec::<u64>::arbitrary(u)? })
643+
Ok(Self { block_hash: u.arbitrary()?, offsets: Vec::<Offset>::arbitrary(u)? })
486644
}
487645
}
488646

@@ -584,14 +742,11 @@ mod test {
584742
let mut raw: Vec<u8> = vec![0u8; 32];
585743
raw.extend(testcase.0.clone());
586744
let btr: BlockTransactionsRequest = deserialize(&raw.clone()).unwrap();
587-
assert_eq!(testcase.1, btr.indexes);
745+
assert_eq!(testcase.1, btr.indices().unwrap());
588746
}
589747
{
590748
// test serialization
591-
let raw: Vec<u8> = serialize(&BlockTransactionsRequest {
592-
block_hash: BlockHash::from_byte_array([0; 32]),
593-
indexes: testcase.1,
594-
});
749+
let raw: Vec<u8> = serialize(&&BlockTransactionsRequest::from_indices_unchecked(BlockHash::from_byte_array([0; 32]), testcase.1));
595750
let mut expected_raw: Vec<u8> = [0u8; 32].to_vec();
596751
expected_raw.extend(testcase.0);
597752
assert_eq!(expected_raw, raw);
@@ -602,18 +757,19 @@ mod test {
602757
// test that we return Err() if deserialization fails (and don't panic)
603758
let mut raw: Vec<u8> = [0u8; 32].to_vec();
604759
raw.extend(errorcase);
605-
assert!(deserialize::<BlockTransactionsRequest>(&raw.clone()).is_err());
760+
let get_block_txn = deserialize::<BlockTransactionsRequest>(&raw.clone()).unwrap();
761+
assert!(get_block_txn.indices().is_err());
606762
}
607763
}
608764
}
609765

610766
#[test]
611-
#[cfg(debug_assertions)]
612-
#[should_panic(expected = "attempt to add with overflow")]
613767
fn getblocktx_panic_when_encoding_u64_max() {
614-
serialize(&BlockTransactionsRequest {
768+
assert!(BlockTransactionsRequest {
615769
block_hash: BlockHash::from_byte_array([0; 32]),
616-
indexes: vec![u64::MAX],
617-
});
770+
offsets: vec![Offset(usize::MAX)],
771+
}
772+
.indices()
773+
.is_err());
618774
}
619775
}

p2p/src/message.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1855,10 +1855,12 @@ mod test {
18551855
}])),
18561856
NetworkMessage::SendAddrV2,
18571857
NetworkMessage::CmpctBlock(cmptblock),
1858-
NetworkMessage::GetBlockTxn(BlockTransactionsRequest {
1859-
block_hash: BlockHash::from_byte_array(hash([11u8; 32]).to_byte_array()),
1860-
indexes: vec![0, 1, 2, 3, 10, 3002],
1861-
}),
1858+
NetworkMessage::GetBlockTxn(
1859+
BlockTransactionsRequest::from_indices_unchecked(
1860+
BlockHash::from_byte_array(hash([11u8; 32]).to_byte_array()),
1861+
vec![0, 1, 2, 3, 10, 3002]
1862+
),
1863+
),
18621864
NetworkMessage::BlockTxn(blocktxn),
18631865
NetworkMessage::SendCmpct(SendCmpct { send_compact: true, version: 8333 }),
18641866
];

0 commit comments

Comments
 (0)