Skip to content

Commit f68a6dd

Browse files
committed
Use fixed-size Vec allocations for BOLT12 messages
Instead of using elaborate calculations to determine the exact amount of bytes need for a BOLT12 message are allocated, use a fixed size amount. This reduces the code complexity and potentially reduces heap fragmentation in the normal case.
1 parent 569f906 commit f68a6dd

File tree

6 files changed

+25
-105
lines changed

6 files changed

+25
-105
lines changed

lightning/src/offers/invoice.rs

Lines changed: 7 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ use crate::offers::invoice_macros::{invoice_accessors_common, invoice_builder_me
122122
#[cfg(test)]
123123
use crate::offers::invoice_macros::invoice_builder_methods_test_common;
124124
use crate::offers::invoice_request::{EXPERIMENTAL_INVOICE_REQUEST_TYPES, ExperimentalInvoiceRequestTlvStream, ExperimentalInvoiceRequestTlvStreamRef, INVOICE_REQUEST_PAYER_ID_TYPE, INVOICE_REQUEST_TYPES, IV_BYTES as INVOICE_REQUEST_IV_BYTES, InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
125-
use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, self, SIGNATURE_TLV_RECORD_SIZE};
125+
use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, self};
126126
use crate::offers::nonce::Nonce;
127127
use crate::offers::offer::{Amount, EXPERIMENTAL_OFFER_TYPES, ExperimentalOfferTlvStream, ExperimentalOfferTlvStreamRef, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef, Quantity};
128128
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
@@ -520,19 +520,8 @@ impl UnsignedBolt12Invoice {
520520
let (_, _, _, invoice_tlv_stream, _, _, experimental_invoice_tlv_stream) =
521521
contents.as_tlv_stream();
522522

523-
// Allocate enough space for the invoice, which will include:
524-
// - all TLV records from `invreq_bytes` except signatures,
525-
// - all invoice-specific TLV records, and
526-
// - a signature TLV record once the invoice is signed.
527-
//
528-
// This assumes both the invoice request and the invoice will each only have one signature
529-
// using SIGNATURE_TYPES.start as the TLV record. Thus, it is accounted for by invreq_bytes.
530-
let mut bytes = Vec::with_capacity(
531-
invreq_bytes.len()
532-
+ invoice_tlv_stream.serialized_length()
533-
+ if contents.is_for_offer() { 0 } else { SIGNATURE_TLV_RECORD_SIZE }
534-
+ experimental_invoice_tlv_stream.serialized_length(),
535-
);
523+
const INVOICE_ALLOCATION_SIZE: usize = 2048;
524+
let mut bytes = Vec::with_capacity(INVOICE_ALLOCATION_SIZE);
536525

537526
// Use the invoice_request bytes instead of the invoice_request TLV stream as the latter may
538527
// have contained unknown TLV records, which are not stored in `InvoiceRequestContents` or
@@ -545,23 +534,16 @@ impl UnsignedBolt12Invoice {
545534

546535
invoice_tlv_stream.write(&mut bytes).unwrap();
547536

548-
let mut experimental_tlv_stream = TlvStream::new(remaining_bytes)
549-
.range(EXPERIMENTAL_TYPES)
550-
.peekable();
551-
let mut experimental_bytes = Vec::with_capacity(
552-
remaining_bytes.len()
553-
- experimental_tlv_stream
554-
.peek()
555-
.map_or(remaining_bytes.len(), |first_record| first_record.start)
556-
+ experimental_invoice_tlv_stream.serialized_length(),
557-
);
537+
const EXPERIMENTAL_TLV_ALLOCATION_SIZE: usize = 512;
538+
let mut experimental_bytes = Vec::with_capacity(EXPERIMENTAL_TLV_ALLOCATION_SIZE);
558539

540+
let experimental_tlv_stream = TlvStream::new(remaining_bytes)
541+
.range(EXPERIMENTAL_TYPES);
559542
for record in experimental_tlv_stream {
560543
record.write(&mut experimental_bytes).unwrap();
561544
}
562545

563546
experimental_invoice_tlv_stream.write(&mut experimental_bytes).unwrap();
564-
debug_assert_eq!(experimental_bytes.len(), experimental_bytes.capacity());
565547

566548
let tlv_stream = TlvStream::new(&bytes).chain(TlvStream::new(&experimental_bytes));
567549
let tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
@@ -592,14 +574,6 @@ macro_rules! unsigned_invoice_sign_method { ($self: ident, $self_type: ty $(, $s
592574
signature_tlv_stream.write(&mut $self.bytes).unwrap();
593575

594576
// Append the experimental bytes after the signature.
595-
debug_assert_eq!(
596-
// The two-byte overallocation results from SIGNATURE_TLV_RECORD_SIZE accommodating TLV
597-
// records with types >= 253.
598-
$self.bytes.len()
599-
+ $self.experimental_bytes.len()
600-
+ if $self.contents.is_for_offer() { 0 } else { 2 },
601-
$self.bytes.capacity(),
602-
);
603577
$self.bytes.extend_from_slice(&$self.experimental_bytes);
604578

605579
Ok(Bolt12Invoice {
@@ -965,13 +939,6 @@ impl Hash for Bolt12Invoice {
965939
}
966940

967941
impl InvoiceContents {
968-
fn is_for_offer(&self) -> bool {
969-
match self {
970-
InvoiceContents::ForOffer { .. } => true,
971-
InvoiceContents::ForRefund { .. } => false,
972-
}
973-
}
974-
975942
/// Whether the original offer or refund has expired.
976943
#[cfg(feature = "std")]
977944
fn is_offer_or_refund_expired(&self) -> bool {

lightning/src/offers/invoice_request.rs

Lines changed: 7 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ use crate::ln::channelmanager::PaymentId;
7777
use crate::types::features::InvoiceRequestFeatures;
7878
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN};
7979
use crate::ln::msgs::DecodeError;
80-
use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, self, SIGNATURE_TLV_RECORD_SIZE};
80+
use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, self};
8181
use crate::offers::nonce::Nonce;
8282
use crate::offers::offer::{Amount, EXPERIMENTAL_OFFER_TYPES, ExperimentalOfferTlvStream, ExperimentalOfferTlvStreamRef, OFFER_TYPES, Offer, OfferContents, OfferId, OfferTlvStream, OfferTlvStreamRef};
8383
use crate::offers::parse::{Bolt12ParseError, ParsedMessage, Bolt12SemanticError};
@@ -473,17 +473,8 @@ impl UnsignedInvoiceRequest {
473473
_experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
474474
) = contents.as_tlv_stream();
475475

476-
// Allocate enough space for the invoice_request, which will include:
477-
// - all TLV records from `offer.bytes`,
478-
// - all invoice_request-specific TLV records, and
479-
// - a signature TLV record once the invoice_request is signed.
480-
let mut bytes = Vec::with_capacity(
481-
offer.bytes.len()
482-
+ payer_tlv_stream.serialized_length()
483-
+ invoice_request_tlv_stream.serialized_length()
484-
+ SIGNATURE_TLV_RECORD_SIZE
485-
+ experimental_invoice_request_tlv_stream.serialized_length(),
486-
);
476+
const INVOICE_REQUEST_ALLOCATION_SIZE: usize = 1024;
477+
let mut bytes = Vec::with_capacity(INVOICE_REQUEST_ALLOCATION_SIZE);
487478

488479
payer_tlv_stream.write(&mut bytes).unwrap();
489480

@@ -495,23 +486,16 @@ impl UnsignedInvoiceRequest {
495486

496487
invoice_request_tlv_stream.write(&mut bytes).unwrap();
497488

498-
let mut experimental_tlv_stream = TlvStream::new(remaining_bytes)
499-
.range(EXPERIMENTAL_OFFER_TYPES)
500-
.peekable();
501-
let mut experimental_bytes = Vec::with_capacity(
502-
remaining_bytes.len()
503-
- experimental_tlv_stream
504-
.peek()
505-
.map_or(remaining_bytes.len(), |first_record| first_record.start)
506-
+ experimental_invoice_request_tlv_stream.serialized_length(),
507-
);
489+
const EXPERIMENTAL_TLV_ALLOCATION_SIZE: usize = 512;
490+
let mut experimental_bytes = Vec::with_capacity(EXPERIMENTAL_TLV_ALLOCATION_SIZE);
508491

492+
let experimental_tlv_stream = TlvStream::new(remaining_bytes)
493+
.range(EXPERIMENTAL_OFFER_TYPES);
509494
for record in experimental_tlv_stream {
510495
record.write(&mut experimental_bytes).unwrap();
511496
}
512497

513498
experimental_invoice_request_tlv_stream.write(&mut experimental_bytes).unwrap();
514-
debug_assert_eq!(experimental_bytes.len(), experimental_bytes.capacity());
515499

516500
let tlv_stream = TlvStream::new(&bytes).chain(TlvStream::new(&experimental_bytes));
517501
let tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
@@ -544,12 +528,6 @@ macro_rules! unsigned_invoice_request_sign_method { (
544528
signature_tlv_stream.write(&mut $self.bytes).unwrap();
545529

546530
// Append the experimental bytes after the signature.
547-
debug_assert_eq!(
548-
// The two-byte overallocation results from SIGNATURE_TLV_RECORD_SIZE accommodating TLV
549-
// records with types >= 253.
550-
$self.bytes.len() + $self.experimental_bytes.len() + 2,
551-
$self.bytes.capacity(),
552-
);
553531
$self.bytes.extend_from_slice(&$self.experimental_bytes);
554532

555533
Ok(InvoiceRequest {

lightning/src/offers/merkle.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
1212
use bitcoin::hashes::{Hash, HashEngine, sha256};
1313
use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, self};
14-
use bitcoin::secp256k1::constants::SCHNORR_SIGNATURE_SIZE;
1514
use bitcoin::secp256k1::schnorr::Signature;
1615
use crate::io;
1716
use crate::util::ser::{BigSize, Readable, Writeable, Writer};
@@ -26,10 +25,6 @@ tlv_stream!(SignatureTlvStream, SignatureTlvStreamRef<'a>, SIGNATURE_TYPES, {
2625
(240, signature: Signature),
2726
});
2827

29-
/// Size of a TLV record in `SIGNATURE_TYPES` when the type is 1000. TLV types are encoded using
30-
/// BigSize, so a TLV record with type 240 will use two less bytes.
31-
pub(super) const SIGNATURE_TLV_RECORD_SIZE: usize = 3 + 1 + SCHNORR_SIGNATURE_SIZE;
32-
3328
/// A hash for use in a specific context by tweaking with a context-dependent tag as per [BIP 340]
3429
/// and computed over the merkle root of a TLV stream to sign as defined in [BOLT 12].
3530
///
@@ -253,7 +248,6 @@ pub(super) struct TlvRecord<'a> {
253248
type_bytes: &'a [u8],
254249
// The entire TLV record.
255250
pub(super) record_bytes: &'a [u8],
256-
pub(super) start: usize,
257251
pub(super) end: usize,
258252
}
259253

@@ -278,7 +272,7 @@ impl<'a> Iterator for TlvStream<'a> {
278272
self.data.set_position(end);
279273

280274
Some(TlvRecord {
281-
r#type, type_bytes, record_bytes, start: start as usize, end: end as usize,
275+
r#type, type_bytes, record_bytes, end: end as usize,
282276
})
283277
} else {
284278
None

lightning/src/offers/offer.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,8 @@ macro_rules! offer_builder_methods { (
438438
}
439439
}
440440

441-
let mut bytes = Vec::new();
441+
const OFFER_ALLOCATION_SIZE: usize = 1024;
442+
let mut bytes = Vec::with_capacity(OFFER_ALLOCATION_SIZE);
442443
$self.offer.write(&mut bytes).unwrap();
443444

444445
let id = OfferId::from_valid_offer_tlv_stream(&bytes);

lightning/src/offers/refund.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,8 @@ macro_rules! refund_builder_methods { (
338338
$self.refund.payer.0 = metadata;
339339
}
340340

341-
let mut bytes = Vec::new();
341+
const REFUND_ALLOCATION_SIZE: usize = 1024;
342+
let mut bytes = Vec::with_capacity(REFUND_ALLOCATION_SIZE);
342343
$self.refund.write(&mut bytes).unwrap();
343344

344345
Ok(Refund {

lightning/src/offers/static_invoice.rs

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ use crate::offers::invoice_macros::{invoice_accessors_common, invoice_builder_me
2525
use crate::offers::invoice_request::InvoiceRequest;
2626
use crate::offers::merkle::{
2727
self, SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream,
28-
SIGNATURE_TLV_RECORD_SIZE,
2928
};
3029
use crate::offers::nonce::Nonce;
3130
use crate::offers::offer::{
@@ -288,16 +287,8 @@ impl UnsignedStaticInvoice {
288287
fn new(offer_bytes: &Vec<u8>, contents: InvoiceContents) -> Self {
289288
let (_, invoice_tlv_stream, _, experimental_invoice_tlv_stream) = contents.as_tlv_stream();
290289

291-
// Allocate enough space for the invoice, which will include:
292-
// - all TLV records from `offer_bytes`,
293-
// - all invoice-specific TLV records, and
294-
// - a signature TLV record once the invoice is signed.
295-
let mut bytes = Vec::with_capacity(
296-
offer_bytes.len()
297-
+ invoice_tlv_stream.serialized_length()
298-
+ SIGNATURE_TLV_RECORD_SIZE
299-
+ experimental_invoice_tlv_stream.serialized_length(),
300-
);
290+
const INVOICE_ALLOCATION_SIZE: usize = 2048;
291+
let mut bytes = Vec::with_capacity(INVOICE_ALLOCATION_SIZE);
301292

302293
// Use the offer bytes instead of the offer TLV stream as the latter may have contained
303294
// unknown TLV records, which are not stored in `InvoiceContents`.
@@ -309,22 +300,16 @@ impl UnsignedStaticInvoice {
309300

310301
invoice_tlv_stream.write(&mut bytes).unwrap();
311302

312-
let mut experimental_tlv_stream =
313-
TlvStream::new(remaining_bytes).range(EXPERIMENTAL_OFFER_TYPES).peekable();
314-
let mut experimental_bytes = Vec::with_capacity(
315-
remaining_bytes.len()
316-
- experimental_tlv_stream
317-
.peek()
318-
.map_or(remaining_bytes.len(), |first_record| first_record.start)
319-
+ experimental_invoice_tlv_stream.serialized_length(),
320-
);
303+
const EXPERIMENTAL_TLV_ALLOCATION_SIZE: usize = 512;
304+
let mut experimental_bytes = Vec::with_capacity(EXPERIMENTAL_TLV_ALLOCATION_SIZE);
321305

306+
let experimental_tlv_stream =
307+
TlvStream::new(remaining_bytes).range(EXPERIMENTAL_OFFER_TYPES);
322308
for record in experimental_tlv_stream {
323309
record.write(&mut experimental_bytes).unwrap();
324310
}
325311

326312
experimental_invoice_tlv_stream.write(&mut experimental_bytes).unwrap();
327-
debug_assert_eq!(experimental_bytes.len(), experimental_bytes.capacity());
328313

329314
let tlv_stream = TlvStream::new(&bytes).chain(TlvStream::new(&experimental_bytes));
330315
let tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
@@ -344,12 +329,6 @@ impl UnsignedStaticInvoice {
344329
signature_tlv_stream.write(&mut self.bytes).unwrap();
345330

346331
// Append the experimental bytes after the signature.
347-
debug_assert_eq!(
348-
// The two-byte overallocation results from SIGNATURE_TLV_RECORD_SIZE accommodating TLV
349-
// records with types >= 253.
350-
self.bytes.len() + self.experimental_bytes.len() + 2,
351-
self.bytes.capacity(),
352-
);
353332
self.bytes.extend_from_slice(&self.experimental_bytes);
354333

355334
Ok(StaticInvoice { bytes: self.bytes, contents: self.contents, signature })

0 commit comments

Comments
 (0)