Add relative lock time type#1196
Conversation
49ef7b6 to
ac14bd4
Compare
src/blockdata/transaction.rs
Outdated
There was a problem hiding this comment.
Docs are clear but naming might be a little confusing.
Just from the name I would assume that this returns the underlying u32 of Sequence, not just the low 16 bits.
Maybe timelock_bits()?
There was a problem hiding this comment.
I like low_u16, cheers.
ac14bd4 to
67c9d2e
Compare
|
Implemented suggestions above and rebased. |
In preparation for adding a relative lock time type move the `locktime` module to a new module called `absolute`. Use qualified path for locktime types (e.g. `absolute::PackedLockTime`) to improve readability.
Bitcoin lock times are easy to get confused, add absolute/relative in various places to help clarify things.
67c9d2e to
2e6a8ae
Compare
|
Rebased on top of 'disable fuzzer' patch, slightly improved docs. |
src/blockdata/locktime/relative.rs
Outdated
There was a problem hiding this comment.
In 2e6a8aefc048c28f71d8a4efb5f1d2766354c9f2:
I don't think it makes sense for a locktime to be satisfied by a sequence no, or vice-versa. Rather, I would expect a sequence number to be convertable to a relative::Locktime, and then this would be satisfied by a blockheight and/or timestamp.
IOW I would replace this method with one that takes a height and a time, and then just passes through to is_satisfied_by_height() && is_satisfied_by_time().
There was a problem hiding this comment.
Oh, if you keep the existing false returns (see below) then && should be replaced by ||.
There was a problem hiding this comment.
I'm still not fully understanding where the height and time values would come from to pass into this new function. Would a user of this function have access to the chain and be expected to be able to compute the current height and time? I've based this (and the absolute::LockTime work) off of the usage in miniscript, is it that miniscript is not a normal user? How would we implement check_older in miniscript if we cannot use a Sequence number to satisfy the relative lock time?
There was a problem hiding this comment.
Ah, I understand the question now. So, there are two pieces to locktime validation in Bitcoin:
- The
CHECKSEQUENCEVERIFY(andCHECKLOCKTIMEVERIFY) opcodes, which check the sequence/locktime values in a transaction against some "test" sequence/locktime value, and decides whether or not it passes. This is what rust-miniscript uses when reasoning aboutcheck_older/check_after. - Separately the Bitcoin blockchain checks whether the sequence/locktime values actually indicate a valid locktime, based on the state of the chain. This is what I think "normal users" are thinking of when they work with sequence/locktime values.
I would suggest that a "normal user" is more likely to be in situation (2), whereas situation (1) corresponds to Script interpretation. In this API we should somehow support both usecases somehow
There was a problem hiding this comment.
I think that my suggested API, in which is_satisfied_by takes both a height and time, would serve both cases.
In case (1), rust-miniscript will see check_older(N), decide whether to interpret N as a height or time, and then use is_satisfied_by_height/is_satisfied_by_time as appropriate. In case (2) the user has both the current height and current time, and can use is_satisfied_by.
Miniscript additionally needs a way to serialize a height/time as a "sequence number" to embed into Script, and to parse out such a value.
There was a problem hiding this comment.
Idea how to resolve having to come up with different names:
// maybe this doesn't have to be sealed, IDK
pub trait SatisfiesRelativeLockTime: sealed::SatisfiesRelativeLockTime {
fn satisfies_relative_lock_time(self, lock_time: relative::LockTime) -> bool;
}
impl SatisfiesRelativeLockTime for Sequence { /* ... */ }
impl SatisfiesRelativeLockTime for (Height, Time) { /* ... */ }
// impl SatisfiesRelativeLockTime for (Time, Height) { /* ... */ } // for convenience so people don't have to remember the order?
impl relative::LockTime {
pub fn is_satisfied_by<T: SatisfiesRelativeLockTime>(self, satisified_by: T) -> bool {
satisfied_by.satisfies_relative_lock_time(self)
}
}Note: not implementing SatisfiesRelativeLockTime for relative::LockTime is intentional because sequence may disable it entirely. Not implementing it prevents accidents.
There was a problem hiding this comment.
I like the trait idea. Implementing for Sequence goes against what @apoelstra says above
I don't think it makes sense for a locktime to be satisfied by a sequence no, or vice-versa. Rather, I would expect a sequence number to be convertable to a relative::Locktime, and then this would be satisfied by a blockheight and/or timestamp.
^ quote by Andrew from above
Note: not implementing SatisfiesRelativeLockTime for relative::LockTime is intentional because sequence may disable it entirely. Not implementing it prevents accidents.
If Sequence disables lock time it is not possible to construct a relative::LockTime from the sequence so this concern is addressed. I think we should implement for relative::LockTime, (Height, Time), and (Time, Height).
There was a problem hiding this comment.
I implemented for Height and Time as well as for the tuple of both and documented ... excessively.
There was a problem hiding this comment.
If
Sequencedisables lock time it is not possible to construct arelative::LockTimefrom the sequence so this concern is addressed.
I still didn't like the possibility of manual check but now I think maybe that's too much paranoia.
There was a problem hiding this comment.
Oh, crap, I now realized it should've been the other way around. It makes sense to check if sequence is satisfied by (Height, Time)|RelativeLockTime but not that much for relative lock time.
|
Done reviewing 2e6a8aefc048c28f71d8a4efb5f1d2766354c9f2. |
|
I've updated rust-bitcoin/rust-miniscript#455 as well to demo current state of this PR. |
2e6a8ae to
fb5abb4
Compare
|
Implemented the |
|
Note that now in miniscript we have the super sexy impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for Sequence {
fn check_older(&self, n: relative::LockTime) -> bool {
match self.to_relative_lock_time() {
None => false,
Some(lock_time) => n.is_satisfied_by(lock_time),
}
}
} |
|
Damn, now that I think about it, silently returning |
src/blockdata/transaction.rs
Outdated
There was a problem hiding this comment.
In fb5abb43711a9f39e62f0e7b25dda8f85b32e98b:
The masking seems unnecessary.
There was a problem hiding this comment.
I have a mild preference for removing it. I think the doccomment already justifies the loss of bits. The masking adds to the cognitive load.
|
Just a couple of nits. I see what @Kixunil is saying that silently returning I'm okay with the current API but my alternative would be for |
|
I added separate functions [back in] This results in a much cleaner API I found, as show by the examples in this version. |
e729cd5 to
6a489a3
Compare
|
Woops, I just squashed the |
6a489a3 to
b55fe3d
Compare
|
Rust question please: I've changed all the methods to take ownership of |
|
@tcharding |
The annoying part is calling pub trait SatisfiesRelativeLockTime: sealed::SatisfiesRelativeLockTime {
type Error;
fn satisfies_relative_lock_time(self, lock_time: relative::LockTime) -> Result<bool, Self::Error>;
}
// impl for `(Height, Time)`, `(Time, Height)` with `Error = Infallible`
// impl for `Height`, `Time, `Sequence` with `Error = UnitMismatchError`
impl relative::LockTime {
pub fn is_satisfied_by<T: SatisfiesRelativeLockTime<Error=Infallible>>(self, satisified_by: T) -> bool {
satisfied_by.satisfies_relative_lock_time(self).unwrap_or_else(|never| match never {})
}
pub fn try_is_satisfied_by<T: SatisfiesRelativeLockTime>(self, satisified_by: T) -> Result<bool, T::Error> {
satisfied_by.satisfies_relative_lock_time(self)
}
} |
apoelstra
left a comment
There was a problem hiding this comment.
ACK b55fe3d89abb6b510c08aafed777af91a55111a9
|
This looks good to me, though I'm not convinced that it's simpler than the equivalent code where we drop the traits and just (repetitively) inline all the impls, returning |
b55fe3d to
d9e3d0e
Compare
|
Ha, after adding the result returning ones yesterday that thought did cross my mind. Glad you mentioned it. I removed the traits, simple code is goal. It was an educational exercise however. |
We recently added the `Sequence` type and we explicitly did not include relative lock time logic. Add a new module `relative` and new type `LockTime` to represent relative lock times as defined by BIP 68.
d9e3d0e to
da95c3a
Compare
|
I think this looks great! Maybe @Kixunil will disagree since we moved away from his suggested trait-based style. |
sanket1729
left a comment
There was a problem hiding this comment.
ACK da95c3a. Left a minor nit
| /// | ||
| /// Will return an error if the input cannot be encoded in 16 bits. | ||
| #[inline] | ||
| pub fn from_seconds_ceil(seconds: u32) -> Result<Self, Error> { |
There was a problem hiding this comment.
nit: Can this be an Option<T> instead? There is only one variant that can happen here. Most arithmetic operations in rust return an Option when the error is only possible before as overflow.
I would have suggested a dedicated error type, but it would be some work and a lot of code.
This would be annoying to unwrap because we not know all error variants
There was a problem hiding this comment.
hmmm, a few considerations
- There is an equivalent method on
Sequenceso we should do the same for both - It was a dedicated error type until I added the other variants
- Option has less information because the error returns the input value, is that useful? I don't know really (@Kixunil is the resident "useful error message" bloke, what do you think man?)
I can do either
- split the error into two different types
- Use an
Optionas suggested
I tend to agree that 2 is the best
There was a problem hiding this comment.
Either way, I'll do it as a follow up please - this one has been running for long enough now.
There was a problem hiding this comment.
I personally prefer custom error types to Option even at the cost of added code. It provides more information when people blindly ? it. (and .ok() is possible if they want to ignore)
A special type would be better indeed.
There was a problem hiding this comment.
To expand on this a bit, in my experience sprinkling .ok_or{_else} or map_err everywhere is quite annoying and not doing it will bite you when function deep in the call stack returns error (None) and you don't even know which one it was. For instance this is very common when people ? std::io::Error.
There was a problem hiding this comment.
@sanket1729 could you raise an issue about this, if you feel strongly enough? I am going to merge this PR.
There was a problem hiding this comment.
Furthering this discussion, I'm interested to learn if our method of having a single enum error type in each module (typically) is the correct way to go about things, alternatives (off the top of my head) are:
- An individual error struct for every error
- An enum for related errors
- An enum error per "some other semantic block" e.g. all of the networking code
There was a problem hiding this comment.
My initial thoughts: for types convertible from byte arrays and such, parsing errors should be separate from other conversion errors - different layers-different errors
Functions that are very often called together can share error.
Everything else should have its own.
Splitting into crates will force us to at least split errors from independent parts.
There was a problem hiding this comment.
Don't feel so strongly about it now. My workflow for detecting it is to try using it on a type that I know it should succeed on and match on error types before returning. I will raise issue/PR later about it
|
I don't mind avoiding the trait, it was just an idea. |
Patches 1 and 2 are preparation. Patch 3 adds a new
relative::LockTimetype.Please see rust-bitcoin/rust-miniscript#455 for demo of use of the new API in miniscript.
Some links to help during review