Alloc-free (AKA zero-copy) control block#4248
Conversation
Pull Request Test Coverage Report for Build 13927232201Details
💛 - Coveralls |
1102a65 to
d164ea6
Compare
This comment was marked as outdated.
This comment was marked as outdated.
d164ea6 to
bf5de4a
Compare
This comment was marked as outdated.
This comment was marked as outdated.
bf5de4a to
ef8c9fb
Compare
|
🚨 API BREAKING CHANGE DETECTED To see the changes click details on "Check semver breaks / PR Semver - stable toolchain" job then expand "Run semver checker script" and scroll to the end of the section. |
|
I have a few questions/nits but they can be addressed as follow ups if needed.
|
| /// Makes sure only the allowed conversions are accessible to external code. | ||
| mod privacy_boundary { |
There was a problem hiding this comment.
I don't see the point of this module?
There was a problem hiding this comment.
It prevents direct access to the .0 field. Though the comment is somewhat misleading because any "disallowed" conversion would have to use unsafe anyway.
|
|
||
| /// Returns a reference to the mutable slice of hashes. | ||
| #[inline] | ||
| pub fn as_mut_slice(&mut self) -> &mut [TapNodeHash] { &mut self.0 } |
There was a problem hiding this comment.
How does having this in a submodule enforce anything?
There was a problem hiding this comment.
Only methods in submodule can access .0, the outer ones can't.
In this specific code it's mainly a lint against doing .0 since that makes future changes annoying. (I linted against myself. :))
| } | ||
| } | ||
|
|
||
| pub(super) fn from_mut_hashes_unchecked(hashes: &mut [TapNodeHash]) -> &mut Self { |
There was a problem hiding this comment.
How is this different from just having a private function outside of the module?
There was a problem hiding this comment.
Well, true, it doesn't need to be inside since it uses unsafe anyway. But I like to having it there anyway because it better shows that this function directly manipulates the internals of the type but also there was a proposal to allow as to avoid these unsafe blocks and if it ever gets into the language then we will definitely want to use it and have the function right there, so we may as well put it there right now.
(The proposed rule was that if the type is #[repr(transparent)] then whichever code sees all its fields as accessible can use as to cast the inner slice to the outer type. This is perfectly sensible and what we want, with the implications above.)
There was a problem hiding this comment.
Fair arguments but none of that convinces me that this is better than not having the module, its unnecessary complexity that is not at all apparent what its there for. IMO simple trumps complex unless there is a good reason for the complexity.
there was a proposal to allow as to avoid these unsafe blocks and if it ever gets into the language then we will definitely want to use it and have the function right there, so we may as well put it there right now.
I'm not a fan of just-in-case code at the best of times, definitely not for a proposal that is not even in the language yet.
There was a problem hiding this comment.
Are you talking about module or that specific function? I was talking about that specific function. It strictly doesn't need to be inside the module but it's better to have it there if we have the module anyway. And I want the module because the other function accesses .0 which is enough reason to have it. If this was the only function that belongs to the module then I would've agreed that adding the module is additional complexity.
The reason I went with
I didn't want to move too much code around at first, to not make the review too boring. We can do it later.
Frankly, I mostly focused on getting some reasonable things working. I'm pretty sure the new type can have richer API. But I also want to make some changes after #4200 merges (IIRC these PRs shouldn't conflict). However, I can already answer
Because of the above, I think it's more pragmatic to compare docs, not the code. |
Oh interesting, I had a play and this means that there is no reason to have any of the methods on #[cfg(test)]
mod tests {
use super::*;
use crate::hashes::sha256;
#[test]
fn deref_coercion() {
let h = sha256::hash(b"some arbitrary bytes");
let t = vec![TapNodeHash::from_byte_array(h.to_byte_array())];
let b = TaprootMerkleBranchBuf::from_collection(t).unwrap();
println!("len: {}", b.len())
}
} The exact same argument applies to |
|
@apoelstra you can make the call whether on not to have the |
|
Regarding methods, for Other than that, I think we should remove the other excess methods. I will do that in a followup PR. Your question about |
|
Oh I see the misunderstanding. The module comment makes no sense to me and I spent the whole time fixating on it.
Perhaps just use the same name we used in Or if you really like |
|
I couldn't remember what name we were using and forgot to look it up later. :D Do you want this in the same PR? (I'll need to leave in a minute for hopefully less than an hour if it helps with deciding.) |
|
Follow up PR is fine bro - thanks. |
|
Done reviewing ef8c9fb. Needs rebase because I merged the weekly format PR before reviewing this one (we skipped last weeks' format PR and at some point we just gotta do it). I agree with Tobin's comments that this should be more uniform with As for the And even if none of those things apply here, in general I'd like to have privacy boundaries like this and I'd prefer we be uniform about it. |
I think that already has non-zero value. :) My main reason to have this guideline is to allow changing the fields (adding |
This type actually contains a `Vec` but we would prefer to have an unsized type. Rename it first so that we can reuse the name later.
This moves the content of the module into `buf` submodule making future changes clearer.
`TaprootMerkleBranchBuf` already had `as_slice` method and `DerefMut` but was missing `as_slice_mut`, so this change adds it.
`TaprootMerkleBranchBuf` being a vec introduced intermediate allocation when creating or decoding `Witness`. However the representation on the wire is the same as in-memory (aside from `#[repr(transparent)]`) so this allocation wasn't really needed. This commit introduces `TaprootMerkleBranch` type which is unsized and can be used in place of `TaprootMerkleBranchBuf` within `ControlBlock`. Aside from removing the intermediate allocation, this improves the API a bit: the conversion from array to other type is no longer needed because it's performed by `ControlBlock` in its methods. Thus, consumers who have an array can simply set it as `merkle_branch` field and then encode the `ControlBlock` into witness. A convenience method is also provided to push the `ControlBlock` along with other parts at the end of the `Witness`.
`TaprootMerkleBranchBuf` previously derefed to a slice which lost the information about length being valid. This commit changes the type which, while API-breaking, is not disruptive because the type has API very similar to slice.
The new unsized type is more flexible and so are the references to it. Just like we pass around `&str` instead of `&String` we should be passing `&TaprootMerkleBranch` instead of `&TaprootMerkleBranchBuf`.
ef8c9fb to
9ea2e92
Compare
|
🚨 API BREAKING CHANGE DETECTED To see the changes click details on "Check semver breaks / PR Semver - stable toolchain" job then expand "Run semver checker script" and scroll to the end of the section. |
|
I was thinking of moving the trait impls in another commit given that I had to rebase anyway but I'll definitely want to clean up the macro for multiple sizes which isn't as trivial and I have more urgent things to do today. |
|
Looks good -- will run local CI on 9ea2e92.
If you need to rebase again though can you rename the module though to be called |
|
IIRC we have such modules elsewhere and they are called differently. I'll open an issue about it to decide on the module name. |
|
|
||
| /// Appends elements to proof. | ||
| pub(super) fn push(&mut self, h: TapNodeHash) -> Result<(), InvalidMerkleTreeDepthError> { | ||
| pub(in super::super) fn push(&mut self, h: TapNodeHash) -> Result<(), InvalidMerkleTreeDepthError> { |
This implements a bunch of changes needed to make
ControlBlockalloc-free. In particular, this allows constructingWitnesswithout the intermediate allocation. It is also a step towards havingP2TrSpendpublic.Closes #1614
This also intentionally does not address decoding of
ControlBlockfromWitnesssince I'm not sure about the API.Rationale for doing the
Bufrename: while doing it withScriptwas very painful it shouldn't be here since it's not used that often and also we can just backport the first commit with deprecated type alias. I was thinking of havingTaprootMerkleBrbut it'd be inconsistent and the name is silly.(Also if anyone is wondering why I did this: I was too exhausted to do more important stuff but felt like doing something nice and easy like this.)