Skip to content

Don't over-allocate invoice bytes - #3494

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2024-12-invoice-byte-allocation
Feb 7, 2025
Merged

Don't over-allocate invoice bytes#3494
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2024-12-invoice-byte-allocation

Conversation

@jkczyz

Copy link
Copy Markdown
Contributor

When allocating space for an invoice's bytes, it was assumed that the bytes used for the invoice request signature are the same length as those used for the invoice's signature. However, fuzz testing revealed that this isn't always the case since an invoice request could contain more than one signature TLV. Account for this when determining the number of bytes to allocate for the invoice. This comes at the expense of an additional traversal of all TLVs in the invoice request through the end of SIGNATURE_TYPES (i.e., every TLV except experimental ones).

@TheBlueMatt

TheBlueMatt commented Dec 18, 2024

Copy link
Copy Markdown
Collaborator

Can we just always allocate a fixed 512/1024/2048/4096 bytes and call it a day? Over-allocating for an object that's not gonna stick around long seems fine (and over-allocating by <2x is generally pretty fine as it could reduce memory fragmentation to offset the additional allocated size.

@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Sure, though we still need to calculate the size and round to the nearest power of 2, if I understand what you're proposing.

@jkczyz

Copy link
Copy Markdown
ContributorAuthor

FYI, I made the adjustment. The first two commits will need to be squashed if this looks good. Did the same for when building offers and refunds.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Oh, no, I was proposing we allocate enough space for any reasonable invreq and if we under-allocate cause someone is doing something nuts that's okay.

@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Oh, no, I was proposing we allocate enough space for any reasonable invreq and if we under-allocate cause someone is doing something nuts that's okay.

Gotcha. Note that the failing fuzzer was from creating an invoice from an invreq, though.

@TheBlueMatt

TheBlueMatt commented Dec 19, 2024

Copy link
Copy Markdown
Collaborator

Err, either way yea. If this code is gonna cause problems I vote we replace with a constant. Sadly for invoices over-allocating may bloat memory somewhat as some users are likely to keep them around for a while, but invreqs much less so. If we can get the over-allocation under, like, 1.5x in the vast majority of cases (seems doable?) then I'd prefer that...

Of course if you prefer to fix it and keep it, that's okay with me, you're the one writing the PR :)

@TheBlueMattTheBlueMatt added this to the 0.1.1 milestone Jan 12, 2025
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Would be good to backport this in 0.1.1 since its currently causing fuzzers to fail on the 0.1 branch, which I'm not a particular fan of :)

@vincenzopalazzovincenzopalazzo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, outside of the compilation failure but they looks trivial to fix

 --> /rustc/90b35a6239c3d8bdabc530a6a0816f7ff89a0aaf/library/alloc/src/vec/mod.rs:422:5
= help: items from traits can only be used if the trait is implemented and in scope
note: `WithRoundedCapacity` defines an item `with_rounded_capacity`, perhaps you need to implement it
--> lightning/src/offers/alloc.rs:11:1
|
11 | pub(super) trait WithRoundedCapacity {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: there is an associated function `with_capacity` with a similar name
|
502 | let mut experimental_bytes = Vec::with_capacity(
| ~~~~~~~~~~~~~
error[E0599]: no function or associated item named `with_rounded_capacity` found for struct `alloc::vec::Vec<_, _>` in the current scope
--> lightning/src/offers/refund.rs:342:24
|
342 | let mut bytes = Vec::with_rounded_capacity($self.refund.serialized_length());
| ^^^^^^^^^^^^^^^^^^^^^ function or associated item not found in `Vec<_, _>`
...
389 | refund_builder_methods!(self, Self, Self, self, T, mut);
| ------------------------------------------------------- in this macro invocation
|
note: if you're trying to build a new `alloc::vec::Vec<_, _>` consider using one of the following associated functions:

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Guess its not worth delaying 0.1.1 for this.

@TheBlueMattTheBlueMatt removed this from the 0.1.1 milestone Jan 28, 2025
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Guess its not worth delaying 0.1.1 for this.

Just let me know when you are looking to cut, and I can have it ready prior to then.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Probably today :). But, really, there's not huge need to get this done in a given release, it just needs to be on the branch so we can fuzz the branch itself.

@jkczyz
jkczyzforce-pushed the 2024-12-invoice-byte-allocation branch from b415372 to f68a6ddCompareJanuary 28, 2025 21:06
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Pushed the simpler solution of a fixed-size allocation. Didn't put too much thought into the numbers.

+ experimental_invoice_tlv_stream.serialized_length(),
);
const EXPERIMENTAL_TLV_ALLOCATION_SIZE: usize = 512;
let mut experimental_bytes = Vec::with_capacity(EXPERIMENTAL_TLV_ALLOCATION_SIZE);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we default to no allocation here instead? If we expect these to rarely be used that'd save us an allocation and if we only ever have one TLV then we always only do one allocation anyway, so it'd only cost us an allocation if we have multiple TLVs.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, we can do that. Though the number of allocations will likely be at least two since we write the type and length before writing the value. Depends on how large the value is and how much the initial Vec allocation size is. Rust playground shows 8 bytes.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure about that? TlvStream iterates over TlvRecords which implement Writeable with a single call to write_all.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, for experimental TLVs from the offer/invreq (i.e., in experimental_tlv_stream), that is true. For those that we set in the invoice (i.e., anything in experimental_invoice_tlv_stream), it would not be the case though.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm, hmmm, right. We could check if any of the supported experimental fields are set and allocate based on that? I can imagine someone having paid 100k BOLT12s, loading them in memory, and us wasting 48MB plus overhead for them...Basically this would just be a comment in ExperimentalInvoiceTlvStream that we should allocate if we add fields since we don't support any fields outside of test anyway.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Comment threadlightning/src/offers/offer.rs Outdated
}

let mut bytes = Vec::new();
const OFFER_ALLOCATION_SIZE: usize = 1024;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this many bytes? Most of the offers in tests fit within 256 bytes (and are generally even smaller).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I was mistakenly thinking in terms of bech32 encoding and also wanted to give a little extra room. Changed to 512 for offer, refund, and invreq and 1024 for invoices.

Note with a non-compact, two-hop blinded path we are over 256 bytes before bech32 encoding. My numbers might be slightly out-of-date, but it's close enough where I think 256 would be too small.

@jkczyz
jkczyzforce-pushed the 2024-12-invoice-byte-allocation branch from f68a6dd to 2b74373CompareJanuary 31, 2025 17:16
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.
@jkczyz
jkczyzforce-pushed the 2024-12-invoice-byte-allocation branch from 2b74373 to ffaccc0CompareJanuary 31, 2025 21:45
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

There's a number of tests that reserve bytes to pass the old assertions, I think we can remove those now, no?

Now that the previous commit removed assertions on Vec capacities for
BOLT12 messages, the use of reserve_exact in tests is no longer needed.
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

There's a number of tests that reserve bytes to pass the old assertions, I think we can remove those now, no?

Yup, removed now.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, gonna go ahead and land this after CI.

@TheBlueMatt
TheBlueMatt merged commit f045c0e into lightningdevkit:mainFeb 7, 2025
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Backported in #3613

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@jkczyz@TheBlueMatt@vincenzopalazzo
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Don't over-allocate invoice bytes by jkczyz · Pull Request #3494 · lightningdevkit/rust-lightning · GitHub
Skip to content

Don't over-allocate invoice bytes - #3494

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2024-12-invoice-byte-allocation
Feb 7, 2025
Merged

Don't over-allocate invoice bytes#3494
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2024-12-invoice-byte-allocation

Conversation

@jkczyz

Copy link
Copy Markdown
Contributor

When allocating space for an invoice's bytes, it was assumed that the bytes used for the invoice request signature are the same length as those used for the invoice's signature. However, fuzz testing revealed that this isn't always the case since an invoice request could contain more than one signature TLV. Account for this when determining the number of bytes to allocate for the invoice. This comes at the expense of an additional traversal of all TLVs in the invoice request through the end of SIGNATURE_TYPES (i.e., every TLV except experimental ones).

@TheBlueMatt

TheBlueMatt commented Dec 18, 2024

Copy link
Copy Markdown
Collaborator

Can we just always allocate a fixed 512/1024/2048/4096 bytes and call it a day? Over-allocating for an object that's not gonna stick around long seems fine (and over-allocating by <2x is generally pretty fine as it could reduce memory fragmentation to offset the additional allocated size.

@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Sure, though we still need to calculate the size and round to the nearest power of 2, if I understand what you're proposing.

@jkczyz

Copy link
Copy Markdown
ContributorAuthor

FYI, I made the adjustment. The first two commits will need to be squashed if this looks good. Did the same for when building offers and refunds.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Oh, no, I was proposing we allocate enough space for any reasonable invreq and if we under-allocate cause someone is doing something nuts that's okay.

@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Oh, no, I was proposing we allocate enough space for any reasonable invreq and if we under-allocate cause someone is doing something nuts that's okay.

Gotcha. Note that the failing fuzzer was from creating an invoice from an invreq, though.

@TheBlueMatt

TheBlueMatt commented Dec 19, 2024

Copy link
Copy Markdown
Collaborator

Err, either way yea. If this code is gonna cause problems I vote we replace with a constant. Sadly for invoices over-allocating may bloat memory somewhat as some users are likely to keep them around for a while, but invreqs much less so. If we can get the over-allocation under, like, 1.5x in the vast majority of cases (seems doable?) then I'd prefer that...

Of course if you prefer to fix it and keep it, that's okay with me, you're the one writing the PR :)

@TheBlueMattTheBlueMatt added this to the 0.1.1 milestone Jan 12, 2025
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Would be good to backport this in 0.1.1 since its currently causing fuzzers to fail on the 0.1 branch, which I'm not a particular fan of :)

@vincenzopalazzovincenzopalazzo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, outside of the compilation failure but they looks trivial to fix

 --> /rustc/90b35a6239c3d8bdabc530a6a0816f7ff89a0aaf/library/alloc/src/vec/mod.rs:422:5
= help: items from traits can only be used if the trait is implemented and in scope
note: `WithRoundedCapacity` defines an item `with_rounded_capacity`, perhaps you need to implement it
--> lightning/src/offers/alloc.rs:11:1
|
11 | pub(super) trait WithRoundedCapacity {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: there is an associated function `with_capacity` with a similar name
|
502 | let mut experimental_bytes = Vec::with_capacity(
| ~~~~~~~~~~~~~
error[E0599]: no function or associated item named `with_rounded_capacity` found for struct `alloc::vec::Vec<_, _>` in the current scope
--> lightning/src/offers/refund.rs:342:24
|
342 | let mut bytes = Vec::with_rounded_capacity($self.refund.serialized_length());
| ^^^^^^^^^^^^^^^^^^^^^ function or associated item not found in `Vec<_, _>`
...
389 | refund_builder_methods!(self, Self, Self, self, T, mut);
| ------------------------------------------------------- in this macro invocation
|
note: if you're trying to build a new `alloc::vec::Vec<_, _>` consider using one of the following associated functions:

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Guess its not worth delaying 0.1.1 for this.

@TheBlueMattTheBlueMatt removed this from the 0.1.1 milestone Jan 28, 2025
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Guess its not worth delaying 0.1.1 for this.

Just let me know when you are looking to cut, and I can have it ready prior to then.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Probably today :). But, really, there's not huge need to get this done in a given release, it just needs to be on the branch so we can fuzz the branch itself.

@jkczyz
jkczyzforce-pushed the 2024-12-invoice-byte-allocation branch from b415372 to f68a6ddCompareJanuary 28, 2025 21:06
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Pushed the simpler solution of a fixed-size allocation. Didn't put too much thought into the numbers.

+ experimental_invoice_tlv_stream.serialized_length(),
);
const EXPERIMENTAL_TLV_ALLOCATION_SIZE: usize = 512;
let mut experimental_bytes = Vec::with_capacity(EXPERIMENTAL_TLV_ALLOCATION_SIZE);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we default to no allocation here instead? If we expect these to rarely be used that'd save us an allocation and if we only ever have one TLV then we always only do one allocation anyway, so it'd only cost us an allocation if we have multiple TLVs.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, we can do that. Though the number of allocations will likely be at least two since we write the type and length before writing the value. Depends on how large the value is and how much the initial Vec allocation size is. Rust playground shows 8 bytes.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure about that? TlvStream iterates over TlvRecords which implement Writeable with a single call to write_all.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, for experimental TLVs from the offer/invreq (i.e., in experimental_tlv_stream), that is true. For those that we set in the invoice (i.e., anything in experimental_invoice_tlv_stream), it would not be the case though.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm, hmmm, right. We could check if any of the supported experimental fields are set and allocate based on that? I can imagine someone having paid 100k BOLT12s, loading them in memory, and us wasting 48MB plus overhead for them...Basically this would just be a comment in ExperimentalInvoiceTlvStream that we should allocate if we add fields since we don't support any fields outside of test anyway.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Comment threadlightning/src/offers/offer.rs Outdated
}

let mut bytes = Vec::new();
const OFFER_ALLOCATION_SIZE: usize = 1024;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this many bytes? Most of the offers in tests fit within 256 bytes (and are generally even smaller).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I was mistakenly thinking in terms of bech32 encoding and also wanted to give a little extra room. Changed to 512 for offer, refund, and invreq and 1024 for invoices.

Note with a non-compact, two-hop blinded path we are over 256 bytes before bech32 encoding. My numbers might be slightly out-of-date, but it's close enough where I think 256 would be too small.

@jkczyz
jkczyzforce-pushed the 2024-12-invoice-byte-allocation branch from f68a6dd to 2b74373CompareJanuary 31, 2025 17:16
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.
@jkczyz
jkczyzforce-pushed the 2024-12-invoice-byte-allocation branch from 2b74373 to ffaccc0CompareJanuary 31, 2025 21:45
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

There's a number of tests that reserve bytes to pass the old assertions, I think we can remove those now, no?

Now that the previous commit removed assertions on Vec capacities for
BOLT12 messages, the use of reserve_exact in tests is no longer needed.
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

There's a number of tests that reserve bytes to pass the old assertions, I think we can remove those now, no?

Yup, removed now.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, gonna go ahead and land this after CI.

@TheBlueMatt
TheBlueMatt merged commit f045c0e into lightningdevkit:mainFeb 7, 2025
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Backported in #3613

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@jkczyz@TheBlueMatt@vincenzopalazzo
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Don't over-allocate invoice bytes by jkczyz · Pull Request #3494 · lightningdevkit/rust-lightning · GitHub
Skip to content

Don't over-allocate invoice bytes - #3494

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2024-12-invoice-byte-allocation
Feb 7, 2025
Merged

Don't over-allocate invoice bytes#3494
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2024-12-invoice-byte-allocation

Conversation

@jkczyz

Copy link
Copy Markdown
Contributor

When allocating space for an invoice's bytes, it was assumed that the bytes used for the invoice request signature are the same length as those used for the invoice's signature. However, fuzz testing revealed that this isn't always the case since an invoice request could contain more than one signature TLV. Account for this when determining the number of bytes to allocate for the invoice. This comes at the expense of an additional traversal of all TLVs in the invoice request through the end of SIGNATURE_TYPES (i.e., every TLV except experimental ones).

@TheBlueMatt

TheBlueMatt commented Dec 18, 2024

Copy link
Copy Markdown
Collaborator

Can we just always allocate a fixed 512/1024/2048/4096 bytes and call it a day? Over-allocating for an object that's not gonna stick around long seems fine (and over-allocating by <2x is generally pretty fine as it could reduce memory fragmentation to offset the additional allocated size.

@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Sure, though we still need to calculate the size and round to the nearest power of 2, if I understand what you're proposing.

@jkczyz

Copy link
Copy Markdown
ContributorAuthor

FYI, I made the adjustment. The first two commits will need to be squashed if this looks good. Did the same for when building offers and refunds.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Oh, no, I was proposing we allocate enough space for any reasonable invreq and if we under-allocate cause someone is doing something nuts that's okay.

@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Oh, no, I was proposing we allocate enough space for any reasonable invreq and if we under-allocate cause someone is doing something nuts that's okay.

Gotcha. Note that the failing fuzzer was from creating an invoice from an invreq, though.

@TheBlueMatt

TheBlueMatt commented Dec 19, 2024

Copy link
Copy Markdown
Collaborator

Err, either way yea. If this code is gonna cause problems I vote we replace with a constant. Sadly for invoices over-allocating may bloat memory somewhat as some users are likely to keep them around for a while, but invreqs much less so. If we can get the over-allocation under, like, 1.5x in the vast majority of cases (seems doable?) then I'd prefer that...

Of course if you prefer to fix it and keep it, that's okay with me, you're the one writing the PR :)

@TheBlueMattTheBlueMatt added this to the 0.1.1 milestone Jan 12, 2025
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Would be good to backport this in 0.1.1 since its currently causing fuzzers to fail on the 0.1 branch, which I'm not a particular fan of :)

@vincenzopalazzovincenzopalazzo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, outside of the compilation failure but they looks trivial to fix

 --> /rustc/90b35a6239c3d8bdabc530a6a0816f7ff89a0aaf/library/alloc/src/vec/mod.rs:422:5
= help: items from traits can only be used if the trait is implemented and in scope
note: `WithRoundedCapacity` defines an item `with_rounded_capacity`, perhaps you need to implement it
--> lightning/src/offers/alloc.rs:11:1
|
11 | pub(super) trait WithRoundedCapacity {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: there is an associated function `with_capacity` with a similar name
|
502 | let mut experimental_bytes = Vec::with_capacity(
| ~~~~~~~~~~~~~
error[E0599]: no function or associated item named `with_rounded_capacity` found for struct `alloc::vec::Vec<_, _>` in the current scope
--> lightning/src/offers/refund.rs:342:24
|
342 | let mut bytes = Vec::with_rounded_capacity($self.refund.serialized_length());
| ^^^^^^^^^^^^^^^^^^^^^ function or associated item not found in `Vec<_, _>`
...
389 | refund_builder_methods!(self, Self, Self, self, T, mut);
| ------------------------------------------------------- in this macro invocation
|
note: if you're trying to build a new `alloc::vec::Vec<_, _>` consider using one of the following associated functions:

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Guess its not worth delaying 0.1.1 for this.

@TheBlueMattTheBlueMatt removed this from the 0.1.1 milestone Jan 28, 2025
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Guess its not worth delaying 0.1.1 for this.

Just let me know when you are looking to cut, and I can have it ready prior to then.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Probably today :). But, really, there's not huge need to get this done in a given release, it just needs to be on the branch so we can fuzz the branch itself.

@jkczyz
jkczyzforce-pushed the 2024-12-invoice-byte-allocation branch from b415372 to f68a6ddCompareJanuary 28, 2025 21:06
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Pushed the simpler solution of a fixed-size allocation. Didn't put too much thought into the numbers.

+ experimental_invoice_tlv_stream.serialized_length(),
);
const EXPERIMENTAL_TLV_ALLOCATION_SIZE: usize = 512;
let mut experimental_bytes = Vec::with_capacity(EXPERIMENTAL_TLV_ALLOCATION_SIZE);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we default to no allocation here instead? If we expect these to rarely be used that'd save us an allocation and if we only ever have one TLV then we always only do one allocation anyway, so it'd only cost us an allocation if we have multiple TLVs.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, we can do that. Though the number of allocations will likely be at least two since we write the type and length before writing the value. Depends on how large the value is and how much the initial Vec allocation size is. Rust playground shows 8 bytes.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure about that? TlvStream iterates over TlvRecords which implement Writeable with a single call to write_all.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, for experimental TLVs from the offer/invreq (i.e., in experimental_tlv_stream), that is true. For those that we set in the invoice (i.e., anything in experimental_invoice_tlv_stream), it would not be the case though.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm, hmmm, right. We could check if any of the supported experimental fields are set and allocate based on that? I can imagine someone having paid 100k BOLT12s, loading them in memory, and us wasting 48MB plus overhead for them...Basically this would just be a comment in ExperimentalInvoiceTlvStream that we should allocate if we add fields since we don't support any fields outside of test anyway.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Comment threadlightning/src/offers/offer.rs Outdated
}

let mut bytes = Vec::new();
const OFFER_ALLOCATION_SIZE: usize = 1024;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this many bytes? Most of the offers in tests fit within 256 bytes (and are generally even smaller).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I was mistakenly thinking in terms of bech32 encoding and also wanted to give a little extra room. Changed to 512 for offer, refund, and invreq and 1024 for invoices.

Note with a non-compact, two-hop blinded path we are over 256 bytes before bech32 encoding. My numbers might be slightly out-of-date, but it's close enough where I think 256 would be too small.

@jkczyz
jkczyzforce-pushed the 2024-12-invoice-byte-allocation branch from f68a6dd to 2b74373CompareJanuary 31, 2025 17:16
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.
@jkczyz
jkczyzforce-pushed the 2024-12-invoice-byte-allocation branch from 2b74373 to ffaccc0CompareJanuary 31, 2025 21:45
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

There's a number of tests that reserve bytes to pass the old assertions, I think we can remove those now, no?

Now that the previous commit removed assertions on Vec capacities for
BOLT12 messages, the use of reserve_exact in tests is no longer needed.
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

There's a number of tests that reserve bytes to pass the old assertions, I think we can remove those now, no?

Yup, removed now.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, gonna go ahead and land this after CI.

@TheBlueMatt
TheBlueMatt merged commit f045c0e into lightningdevkit:mainFeb 7, 2025
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Backported in #3613

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@jkczyz@TheBlueMatt@vincenzopalazzo
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Don't over-allocate invoice bytes by jkczyz · Pull Request #3494 · lightningdevkit/rust-lightning · GitHub
Skip to content

Don't over-allocate invoice bytes - #3494

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2024-12-invoice-byte-allocation
Feb 7, 2025
Merged

Don't over-allocate invoice bytes#3494
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2024-12-invoice-byte-allocation

Conversation

@jkczyz

Copy link
Copy Markdown
Contributor

When allocating space for an invoice's bytes, it was assumed that the bytes used for the invoice request signature are the same length as those used for the invoice's signature. However, fuzz testing revealed that this isn't always the case since an invoice request could contain more than one signature TLV. Account for this when determining the number of bytes to allocate for the invoice. This comes at the expense of an additional traversal of all TLVs in the invoice request through the end of SIGNATURE_TYPES (i.e., every TLV except experimental ones).

@TheBlueMatt

TheBlueMatt commented Dec 18, 2024

Copy link
Copy Markdown
Collaborator

Can we just always allocate a fixed 512/1024/2048/4096 bytes and call it a day? Over-allocating for an object that's not gonna stick around long seems fine (and over-allocating by <2x is generally pretty fine as it could reduce memory fragmentation to offset the additional allocated size.

@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Sure, though we still need to calculate the size and round to the nearest power of 2, if I understand what you're proposing.

@jkczyz

Copy link
Copy Markdown
ContributorAuthor

FYI, I made the adjustment. The first two commits will need to be squashed if this looks good. Did the same for when building offers and refunds.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Oh, no, I was proposing we allocate enough space for any reasonable invreq and if we under-allocate cause someone is doing something nuts that's okay.

@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Oh, no, I was proposing we allocate enough space for any reasonable invreq and if we under-allocate cause someone is doing something nuts that's okay.

Gotcha. Note that the failing fuzzer was from creating an invoice from an invreq, though.

@TheBlueMatt

TheBlueMatt commented Dec 19, 2024

Copy link
Copy Markdown
Collaborator

Err, either way yea. If this code is gonna cause problems I vote we replace with a constant. Sadly for invoices over-allocating may bloat memory somewhat as some users are likely to keep them around for a while, but invreqs much less so. If we can get the over-allocation under, like, 1.5x in the vast majority of cases (seems doable?) then I'd prefer that...

Of course if you prefer to fix it and keep it, that's okay with me, you're the one writing the PR :)

@TheBlueMattTheBlueMatt added this to the 0.1.1 milestone Jan 12, 2025
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Would be good to backport this in 0.1.1 since its currently causing fuzzers to fail on the 0.1 branch, which I'm not a particular fan of :)

@vincenzopalazzovincenzopalazzo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, outside of the compilation failure but they looks trivial to fix

 --> /rustc/90b35a6239c3d8bdabc530a6a0816f7ff89a0aaf/library/alloc/src/vec/mod.rs:422:5
= help: items from traits can only be used if the trait is implemented and in scope
note: `WithRoundedCapacity` defines an item `with_rounded_capacity`, perhaps you need to implement it
--> lightning/src/offers/alloc.rs:11:1
|
11 | pub(super) trait WithRoundedCapacity {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: there is an associated function `with_capacity` with a similar name
|
502 | let mut experimental_bytes = Vec::with_capacity(
| ~~~~~~~~~~~~~
error[E0599]: no function or associated item named `with_rounded_capacity` found for struct `alloc::vec::Vec<_, _>` in the current scope
--> lightning/src/offers/refund.rs:342:24
|
342 | let mut bytes = Vec::with_rounded_capacity($self.refund.serialized_length());
| ^^^^^^^^^^^^^^^^^^^^^ function or associated item not found in `Vec<_, _>`
...
389 | refund_builder_methods!(self, Self, Self, self, T, mut);
| ------------------------------------------------------- in this macro invocation
|
note: if you're trying to build a new `alloc::vec::Vec<_, _>` consider using one of the following associated functions:

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Guess its not worth delaying 0.1.1 for this.

@TheBlueMattTheBlueMatt removed this from the 0.1.1 milestone Jan 28, 2025
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Guess its not worth delaying 0.1.1 for this.

Just let me know when you are looking to cut, and I can have it ready prior to then.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Probably today :). But, really, there's not huge need to get this done in a given release, it just needs to be on the branch so we can fuzz the branch itself.

@jkczyz
jkczyzforce-pushed the 2024-12-invoice-byte-allocation branch from b415372 to f68a6ddCompareJanuary 28, 2025 21:06
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Pushed the simpler solution of a fixed-size allocation. Didn't put too much thought into the numbers.

+ experimental_invoice_tlv_stream.serialized_length(),
);
const EXPERIMENTAL_TLV_ALLOCATION_SIZE: usize = 512;
let mut experimental_bytes = Vec::with_capacity(EXPERIMENTAL_TLV_ALLOCATION_SIZE);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we default to no allocation here instead? If we expect these to rarely be used that'd save us an allocation and if we only ever have one TLV then we always only do one allocation anyway, so it'd only cost us an allocation if we have multiple TLVs.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, we can do that. Though the number of allocations will likely be at least two since we write the type and length before writing the value. Depends on how large the value is and how much the initial Vec allocation size is. Rust playground shows 8 bytes.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure about that? TlvStream iterates over TlvRecords which implement Writeable with a single call to write_all.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, for experimental TLVs from the offer/invreq (i.e., in experimental_tlv_stream), that is true. For those that we set in the invoice (i.e., anything in experimental_invoice_tlv_stream), it would not be the case though.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm, hmmm, right. We could check if any of the supported experimental fields are set and allocate based on that? I can imagine someone having paid 100k BOLT12s, loading them in memory, and us wasting 48MB plus overhead for them...Basically this would just be a comment in ExperimentalInvoiceTlvStream that we should allocate if we add fields since we don't support any fields outside of test anyway.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Comment threadlightning/src/offers/offer.rs Outdated
}

let mut bytes = Vec::new();
const OFFER_ALLOCATION_SIZE: usize = 1024;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this many bytes? Most of the offers in tests fit within 256 bytes (and are generally even smaller).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I was mistakenly thinking in terms of bech32 encoding and also wanted to give a little extra room. Changed to 512 for offer, refund, and invreq and 1024 for invoices.

Note with a non-compact, two-hop blinded path we are over 256 bytes before bech32 encoding. My numbers might be slightly out-of-date, but it's close enough where I think 256 would be too small.

@jkczyz
jkczyzforce-pushed the 2024-12-invoice-byte-allocation branch from f68a6dd to 2b74373CompareJanuary 31, 2025 17:16
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.
@jkczyz
jkczyzforce-pushed the 2024-12-invoice-byte-allocation branch from 2b74373 to ffaccc0CompareJanuary 31, 2025 21:45
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

There's a number of tests that reserve bytes to pass the old assertions, I think we can remove those now, no?

Now that the previous commit removed assertions on Vec capacities for
BOLT12 messages, the use of reserve_exact in tests is no longer needed.
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

There's a number of tests that reserve bytes to pass the old assertions, I think we can remove those now, no?

Yup, removed now.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, gonna go ahead and land this after CI.

@TheBlueMatt
TheBlueMatt merged commit f045c0e into lightningdevkit:mainFeb 7, 2025
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Backported in #3613

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@jkczyz@TheBlueMatt@vincenzopalazzo
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Don't over-allocate invoice bytes by jkczyz · Pull Request #3494 · lightningdevkit/rust-lightning · GitHub
Skip to content

Don't over-allocate invoice bytes - #3494

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2024-12-invoice-byte-allocation
Feb 7, 2025
Merged

Don't over-allocate invoice bytes#3494
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2024-12-invoice-byte-allocation

Conversation

@jkczyz

Copy link
Copy Markdown
Contributor

When allocating space for an invoice's bytes, it was assumed that the bytes used for the invoice request signature are the same length as those used for the invoice's signature. However, fuzz testing revealed that this isn't always the case since an invoice request could contain more than one signature TLV. Account for this when determining the number of bytes to allocate for the invoice. This comes at the expense of an additional traversal of all TLVs in the invoice request through the end of SIGNATURE_TYPES (i.e., every TLV except experimental ones).

@TheBlueMatt

TheBlueMatt commented Dec 18, 2024

Copy link
Copy Markdown
Collaborator

Can we just always allocate a fixed 512/1024/2048/4096 bytes and call it a day? Over-allocating for an object that's not gonna stick around long seems fine (and over-allocating by <2x is generally pretty fine as it could reduce memory fragmentation to offset the additional allocated size.

@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Sure, though we still need to calculate the size and round to the nearest power of 2, if I understand what you're proposing.

@jkczyz

Copy link
Copy Markdown
ContributorAuthor

FYI, I made the adjustment. The first two commits will need to be squashed if this looks good. Did the same for when building offers and refunds.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Oh, no, I was proposing we allocate enough space for any reasonable invreq and if we under-allocate cause someone is doing something nuts that's okay.

@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Oh, no, I was proposing we allocate enough space for any reasonable invreq and if we under-allocate cause someone is doing something nuts that's okay.

Gotcha. Note that the failing fuzzer was from creating an invoice from an invreq, though.

@TheBlueMatt

TheBlueMatt commented Dec 19, 2024

Copy link
Copy Markdown
Collaborator

Err, either way yea. If this code is gonna cause problems I vote we replace with a constant. Sadly for invoices over-allocating may bloat memory somewhat as some users are likely to keep them around for a while, but invreqs much less so. If we can get the over-allocation under, like, 1.5x in the vast majority of cases (seems doable?) then I'd prefer that...

Of course if you prefer to fix it and keep it, that's okay with me, you're the one writing the PR :)

@TheBlueMattTheBlueMatt added this to the 0.1.1 milestone Jan 12, 2025
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Would be good to backport this in 0.1.1 since its currently causing fuzzers to fail on the 0.1 branch, which I'm not a particular fan of :)

@vincenzopalazzovincenzopalazzo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, outside of the compilation failure but they looks trivial to fix

 --> /rustc/90b35a6239c3d8bdabc530a6a0816f7ff89a0aaf/library/alloc/src/vec/mod.rs:422:5
= help: items from traits can only be used if the trait is implemented and in scope
note: `WithRoundedCapacity` defines an item `with_rounded_capacity`, perhaps you need to implement it
--> lightning/src/offers/alloc.rs:11:1
|
11 | pub(super) trait WithRoundedCapacity {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: there is an associated function `with_capacity` with a similar name
|
502 | let mut experimental_bytes = Vec::with_capacity(
| ~~~~~~~~~~~~~
error[E0599]: no function or associated item named `with_rounded_capacity` found for struct `alloc::vec::Vec<_, _>` in the current scope
--> lightning/src/offers/refund.rs:342:24
|
342 | let mut bytes = Vec::with_rounded_capacity($self.refund.serialized_length());
| ^^^^^^^^^^^^^^^^^^^^^ function or associated item not found in `Vec<_, _>`
...
389 | refund_builder_methods!(self, Self, Self, self, T, mut);
| ------------------------------------------------------- in this macro invocation
|
note: if you're trying to build a new `alloc::vec::Vec<_, _>` consider using one of the following associated functions:

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Guess its not worth delaying 0.1.1 for this.

@TheBlueMattTheBlueMatt removed this from the 0.1.1 milestone Jan 28, 2025
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Guess its not worth delaying 0.1.1 for this.

Just let me know when you are looking to cut, and I can have it ready prior to then.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Probably today :). But, really, there's not huge need to get this done in a given release, it just needs to be on the branch so we can fuzz the branch itself.

@jkczyz
jkczyzforce-pushed the 2024-12-invoice-byte-allocation branch from b415372 to f68a6ddCompareJanuary 28, 2025 21:06
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Pushed the simpler solution of a fixed-size allocation. Didn't put too much thought into the numbers.

+ experimental_invoice_tlv_stream.serialized_length(),
);
const EXPERIMENTAL_TLV_ALLOCATION_SIZE: usize = 512;
let mut experimental_bytes = Vec::with_capacity(EXPERIMENTAL_TLV_ALLOCATION_SIZE);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we default to no allocation here instead? If we expect these to rarely be used that'd save us an allocation and if we only ever have one TLV then we always only do one allocation anyway, so it'd only cost us an allocation if we have multiple TLVs.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, we can do that. Though the number of allocations will likely be at least two since we write the type and length before writing the value. Depends on how large the value is and how much the initial Vec allocation size is. Rust playground shows 8 bytes.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure about that? TlvStream iterates over TlvRecords which implement Writeable with a single call to write_all.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, for experimental TLVs from the offer/invreq (i.e., in experimental_tlv_stream), that is true. For those that we set in the invoice (i.e., anything in experimental_invoice_tlv_stream), it would not be the case though.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm, hmmm, right. We could check if any of the supported experimental fields are set and allocate based on that? I can imagine someone having paid 100k BOLT12s, loading them in memory, and us wasting 48MB plus overhead for them...Basically this would just be a comment in ExperimentalInvoiceTlvStream that we should allocate if we add fields since we don't support any fields outside of test anyway.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Comment threadlightning/src/offers/offer.rs Outdated
}

let mut bytes = Vec::new();
const OFFER_ALLOCATION_SIZE: usize = 1024;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this many bytes? Most of the offers in tests fit within 256 bytes (and are generally even smaller).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I was mistakenly thinking in terms of bech32 encoding and also wanted to give a little extra room. Changed to 512 for offer, refund, and invreq and 1024 for invoices.

Note with a non-compact, two-hop blinded path we are over 256 bytes before bech32 encoding. My numbers might be slightly out-of-date, but it's close enough where I think 256 would be too small.

@jkczyz
jkczyzforce-pushed the 2024-12-invoice-byte-allocation branch from f68a6dd to 2b74373CompareJanuary 31, 2025 17:16
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.
@jkczyz
jkczyzforce-pushed the 2024-12-invoice-byte-allocation branch from 2b74373 to ffaccc0CompareJanuary 31, 2025 21:45
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

There's a number of tests that reserve bytes to pass the old assertions, I think we can remove those now, no?

Now that the previous commit removed assertions on Vec capacities for
BOLT12 messages, the use of reserve_exact in tests is no longer needed.
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

There's a number of tests that reserve bytes to pass the old assertions, I think we can remove those now, no?

Yup, removed now.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, gonna go ahead and land this after CI.

@TheBlueMatt
TheBlueMatt merged commit f045c0e into lightningdevkit:mainFeb 7, 2025
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Backported in #3613

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@jkczyz@TheBlueMatt@vincenzopalazzo
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Don't over-allocate invoice bytes by jkczyz · Pull Request #3494 · lightningdevkit/rust-lightning · GitHub
Skip to content

Don't over-allocate invoice bytes - #3494

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2024-12-invoice-byte-allocation
Feb 7, 2025
Merged

Don't over-allocate invoice bytes#3494
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2024-12-invoice-byte-allocation

Conversation

@jkczyz

Copy link
Copy Markdown
Contributor

When allocating space for an invoice's bytes, it was assumed that the bytes used for the invoice request signature are the same length as those used for the invoice's signature. However, fuzz testing revealed that this isn't always the case since an invoice request could contain more than one signature TLV. Account for this when determining the number of bytes to allocate for the invoice. This comes at the expense of an additional traversal of all TLVs in the invoice request through the end of SIGNATURE_TYPES (i.e., every TLV except experimental ones).

@TheBlueMatt

TheBlueMatt commented Dec 18, 2024

Copy link
Copy Markdown
Collaborator

Can we just always allocate a fixed 512/1024/2048/4096 bytes and call it a day? Over-allocating for an object that's not gonna stick around long seems fine (and over-allocating by <2x is generally pretty fine as it could reduce memory fragmentation to offset the additional allocated size.

@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Sure, though we still need to calculate the size and round to the nearest power of 2, if I understand what you're proposing.

@jkczyz

Copy link
Copy Markdown
ContributorAuthor

FYI, I made the adjustment. The first two commits will need to be squashed if this looks good. Did the same for when building offers and refunds.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Oh, no, I was proposing we allocate enough space for any reasonable invreq and if we under-allocate cause someone is doing something nuts that's okay.

@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Oh, no, I was proposing we allocate enough space for any reasonable invreq and if we under-allocate cause someone is doing something nuts that's okay.

Gotcha. Note that the failing fuzzer was from creating an invoice from an invreq, though.

@TheBlueMatt

TheBlueMatt commented Dec 19, 2024

Copy link
Copy Markdown
Collaborator

Err, either way yea. If this code is gonna cause problems I vote we replace with a constant. Sadly for invoices over-allocating may bloat memory somewhat as some users are likely to keep them around for a while, but invreqs much less so. If we can get the over-allocation under, like, 1.5x in the vast majority of cases (seems doable?) then I'd prefer that...

Of course if you prefer to fix it and keep it, that's okay with me, you're the one writing the PR :)

@TheBlueMattTheBlueMatt added this to the 0.1.1 milestone Jan 12, 2025
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Would be good to backport this in 0.1.1 since its currently causing fuzzers to fail on the 0.1 branch, which I'm not a particular fan of :)

@vincenzopalazzovincenzopalazzo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, outside of the compilation failure but they looks trivial to fix

 --> /rustc/90b35a6239c3d8bdabc530a6a0816f7ff89a0aaf/library/alloc/src/vec/mod.rs:422:5
= help: items from traits can only be used if the trait is implemented and in scope
note: `WithRoundedCapacity` defines an item `with_rounded_capacity`, perhaps you need to implement it
--> lightning/src/offers/alloc.rs:11:1
|
11 | pub(super) trait WithRoundedCapacity {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: there is an associated function `with_capacity` with a similar name
|
502 | let mut experimental_bytes = Vec::with_capacity(
| ~~~~~~~~~~~~~
error[E0599]: no function or associated item named `with_rounded_capacity` found for struct `alloc::vec::Vec<_, _>` in the current scope
--> lightning/src/offers/refund.rs:342:24
|
342 | let mut bytes = Vec::with_rounded_capacity($self.refund.serialized_length());
| ^^^^^^^^^^^^^^^^^^^^^ function or associated item not found in `Vec<_, _>`
...
389 | refund_builder_methods!(self, Self, Self, self, T, mut);
| ------------------------------------------------------- in this macro invocation
|
note: if you're trying to build a new `alloc::vec::Vec<_, _>` consider using one of the following associated functions:

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Guess its not worth delaying 0.1.1 for this.

@TheBlueMattTheBlueMatt removed this from the 0.1.1 milestone Jan 28, 2025
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Guess its not worth delaying 0.1.1 for this.

Just let me know when you are looking to cut, and I can have it ready prior to then.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Probably today :). But, really, there's not huge need to get this done in a given release, it just needs to be on the branch so we can fuzz the branch itself.

@jkczyz
jkczyzforce-pushed the 2024-12-invoice-byte-allocation branch from b415372 to f68a6ddCompareJanuary 28, 2025 21:06
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Pushed the simpler solution of a fixed-size allocation. Didn't put too much thought into the numbers.

+ experimental_invoice_tlv_stream.serialized_length(),
);
const EXPERIMENTAL_TLV_ALLOCATION_SIZE: usize = 512;
let mut experimental_bytes = Vec::with_capacity(EXPERIMENTAL_TLV_ALLOCATION_SIZE);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we default to no allocation here instead? If we expect these to rarely be used that'd save us an allocation and if we only ever have one TLV then we always only do one allocation anyway, so it'd only cost us an allocation if we have multiple TLVs.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, we can do that. Though the number of allocations will likely be at least two since we write the type and length before writing the value. Depends on how large the value is and how much the initial Vec allocation size is. Rust playground shows 8 bytes.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure about that? TlvStream iterates over TlvRecords which implement Writeable with a single call to write_all.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, for experimental TLVs from the offer/invreq (i.e., in experimental_tlv_stream), that is true. For those that we set in the invoice (i.e., anything in experimental_invoice_tlv_stream), it would not be the case though.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm, hmmm, right. We could check if any of the supported experimental fields are set and allocate based on that? I can imagine someone having paid 100k BOLT12s, loading them in memory, and us wasting 48MB plus overhead for them...Basically this would just be a comment in ExperimentalInvoiceTlvStream that we should allocate if we add fields since we don't support any fields outside of test anyway.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Comment threadlightning/src/offers/offer.rs Outdated
}

let mut bytes = Vec::new();
const OFFER_ALLOCATION_SIZE: usize = 1024;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this many bytes? Most of the offers in tests fit within 256 bytes (and are generally even smaller).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I was mistakenly thinking in terms of bech32 encoding and also wanted to give a little extra room. Changed to 512 for offer, refund, and invreq and 1024 for invoices.

Note with a non-compact, two-hop blinded path we are over 256 bytes before bech32 encoding. My numbers might be slightly out-of-date, but it's close enough where I think 256 would be too small.

@jkczyz
jkczyzforce-pushed the 2024-12-invoice-byte-allocation branch from f68a6dd to 2b74373CompareJanuary 31, 2025 17:16
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.
@jkczyz
jkczyzforce-pushed the 2024-12-invoice-byte-allocation branch from 2b74373 to ffaccc0CompareJanuary 31, 2025 21:45
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

There's a number of tests that reserve bytes to pass the old assertions, I think we can remove those now, no?

Now that the previous commit removed assertions on Vec capacities for
BOLT12 messages, the use of reserve_exact in tests is no longer needed.
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

There's a number of tests that reserve bytes to pass the old assertions, I think we can remove those now, no?

Yup, removed now.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, gonna go ahead and land this after CI.

@TheBlueMatt
TheBlueMatt merged commit f045c0e into lightningdevkit:mainFeb 7, 2025
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Backported in #3613

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@jkczyz@TheBlueMatt@vincenzopalazzo
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Don't over-allocate invoice bytes by jkczyz · Pull Request #3494 · lightningdevkit/rust-lightning · GitHub
Skip to content

Don't over-allocate invoice bytes - #3494

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2024-12-invoice-byte-allocation
Feb 7, 2025
Merged

Don't over-allocate invoice bytes#3494
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2024-12-invoice-byte-allocation

Conversation

@jkczyz

Copy link
Copy Markdown
Contributor

When allocating space for an invoice's bytes, it was assumed that the bytes used for the invoice request signature are the same length as those used for the invoice's signature. However, fuzz testing revealed that this isn't always the case since an invoice request could contain more than one signature TLV. Account for this when determining the number of bytes to allocate for the invoice. This comes at the expense of an additional traversal of all TLVs in the invoice request through the end of SIGNATURE_TYPES (i.e., every TLV except experimental ones).

@TheBlueMatt

TheBlueMatt commented Dec 18, 2024

Copy link
Copy Markdown
Collaborator

Can we just always allocate a fixed 512/1024/2048/4096 bytes and call it a day? Over-allocating for an object that's not gonna stick around long seems fine (and over-allocating by <2x is generally pretty fine as it could reduce memory fragmentation to offset the additional allocated size.

@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Sure, though we still need to calculate the size and round to the nearest power of 2, if I understand what you're proposing.

@jkczyz

Copy link
Copy Markdown
ContributorAuthor

FYI, I made the adjustment. The first two commits will need to be squashed if this looks good. Did the same for when building offers and refunds.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Oh, no, I was proposing we allocate enough space for any reasonable invreq and if we under-allocate cause someone is doing something nuts that's okay.

@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Oh, no, I was proposing we allocate enough space for any reasonable invreq and if we under-allocate cause someone is doing something nuts that's okay.

Gotcha. Note that the failing fuzzer was from creating an invoice from an invreq, though.

@TheBlueMatt

TheBlueMatt commented Dec 19, 2024

Copy link
Copy Markdown
Collaborator

Err, either way yea. If this code is gonna cause problems I vote we replace with a constant. Sadly for invoices over-allocating may bloat memory somewhat as some users are likely to keep them around for a while, but invreqs much less so. If we can get the over-allocation under, like, 1.5x in the vast majority of cases (seems doable?) then I'd prefer that...

Of course if you prefer to fix it and keep it, that's okay with me, you're the one writing the PR :)

@TheBlueMattTheBlueMatt added this to the 0.1.1 milestone Jan 12, 2025
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Would be good to backport this in 0.1.1 since its currently causing fuzzers to fail on the 0.1 branch, which I'm not a particular fan of :)

@vincenzopalazzovincenzopalazzo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, outside of the compilation failure but they looks trivial to fix

 --> /rustc/90b35a6239c3d8bdabc530a6a0816f7ff89a0aaf/library/alloc/src/vec/mod.rs:422:5
= help: items from traits can only be used if the trait is implemented and in scope
note: `WithRoundedCapacity` defines an item `with_rounded_capacity`, perhaps you need to implement it
--> lightning/src/offers/alloc.rs:11:1
|
11 | pub(super) trait WithRoundedCapacity {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: there is an associated function `with_capacity` with a similar name
|
502 | let mut experimental_bytes = Vec::with_capacity(
| ~~~~~~~~~~~~~
error[E0599]: no function or associated item named `with_rounded_capacity` found for struct `alloc::vec::Vec<_, _>` in the current scope
--> lightning/src/offers/refund.rs:342:24
|
342 | let mut bytes = Vec::with_rounded_capacity($self.refund.serialized_length());
| ^^^^^^^^^^^^^^^^^^^^^ function or associated item not found in `Vec<_, _>`
...
389 | refund_builder_methods!(self, Self, Self, self, T, mut);
| ------------------------------------------------------- in this macro invocation
|
note: if you're trying to build a new `alloc::vec::Vec<_, _>` consider using one of the following associated functions:

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Guess its not worth delaying 0.1.1 for this.

@TheBlueMattTheBlueMatt removed this from the 0.1.1 milestone Jan 28, 2025
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Guess its not worth delaying 0.1.1 for this.

Just let me know when you are looking to cut, and I can have it ready prior to then.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Probably today :). But, really, there's not huge need to get this done in a given release, it just needs to be on the branch so we can fuzz the branch itself.

@jkczyz
jkczyzforce-pushed the 2024-12-invoice-byte-allocation branch from b415372 to f68a6ddCompareJanuary 28, 2025 21:06
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Pushed the simpler solution of a fixed-size allocation. Didn't put too much thought into the numbers.

+ experimental_invoice_tlv_stream.serialized_length(),
);
const EXPERIMENTAL_TLV_ALLOCATION_SIZE: usize = 512;
let mut experimental_bytes = Vec::with_capacity(EXPERIMENTAL_TLV_ALLOCATION_SIZE);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we default to no allocation here instead? If we expect these to rarely be used that'd save us an allocation and if we only ever have one TLV then we always only do one allocation anyway, so it'd only cost us an allocation if we have multiple TLVs.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, we can do that. Though the number of allocations will likely be at least two since we write the type and length before writing the value. Depends on how large the value is and how much the initial Vec allocation size is. Rust playground shows 8 bytes.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure about that? TlvStream iterates over TlvRecords which implement Writeable with a single call to write_all.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, for experimental TLVs from the offer/invreq (i.e., in experimental_tlv_stream), that is true. For those that we set in the invoice (i.e., anything in experimental_invoice_tlv_stream), it would not be the case though.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm, hmmm, right. We could check if any of the supported experimental fields are set and allocate based on that? I can imagine someone having paid 100k BOLT12s, loading them in memory, and us wasting 48MB plus overhead for them...Basically this would just be a comment in ExperimentalInvoiceTlvStream that we should allocate if we add fields since we don't support any fields outside of test anyway.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Comment threadlightning/src/offers/offer.rs Outdated
}

let mut bytes = Vec::new();
const OFFER_ALLOCATION_SIZE: usize = 1024;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this many bytes? Most of the offers in tests fit within 256 bytes (and are generally even smaller).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I was mistakenly thinking in terms of bech32 encoding and also wanted to give a little extra room. Changed to 512 for offer, refund, and invreq and 1024 for invoices.

Note with a non-compact, two-hop blinded path we are over 256 bytes before bech32 encoding. My numbers might be slightly out-of-date, but it's close enough where I think 256 would be too small.

@jkczyz
jkczyzforce-pushed the 2024-12-invoice-byte-allocation branch from f68a6dd to 2b74373CompareJanuary 31, 2025 17:16
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.
@jkczyz
jkczyzforce-pushed the 2024-12-invoice-byte-allocation branch from 2b74373 to ffaccc0CompareJanuary 31, 2025 21:45
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

There's a number of tests that reserve bytes to pass the old assertions, I think we can remove those now, no?

Now that the previous commit removed assertions on Vec capacities for
BOLT12 messages, the use of reserve_exact in tests is no longer needed.
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

There's a number of tests that reserve bytes to pass the old assertions, I think we can remove those now, no?

Yup, removed now.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, gonna go ahead and land this after CI.

@TheBlueMatt
TheBlueMatt merged commit f045c0e into lightningdevkit:mainFeb 7, 2025
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Backported in #3613

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@jkczyz@TheBlueMatt@vincenzopalazzo