New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add new type for sequence #1093
Conversation
I also thought about having a type for a relative time locks which a sequence can be created with, but thought it would be better to add it after #994. pub enum RelativeTimeLock {
BlockBased(u16),
TimeBased(u16)
}
impl Sequence {
// ...
pub fn from_timelock(timelock: RelativeTimeLock) -> Self {
//...
}
pub fn timelock(&self) -> Option<RelativeTimeLock> {
//...
}
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work, I just left a few comments from what I have learned recently. I'll leave the finer API design review to others more knowledgeable.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] | ||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] | ||
pub struct Sequence(pub u32); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this type is to abstract over nSequence do we want the inner int to be private, wait for others to comment though, don't go just off my review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also thought the value doesn't need to be public but made it public following #1082 (comment).
However, I do see the benefits of having the value public where users might want to do arithmetic with the value for any reason.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sequence has no invariants so I think public is fine.
src/blockdata/transaction.rs
Outdated
} | ||
} | ||
|
||
impl From<u32> for Sequence { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think all the discussion on #994 about the naming of to_consensus_u32
and from_consensus
applies to sequence as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a big fan of the naming but I've replaced From<u32>
with the above two functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tcharding I actually think it doesn't. :) Sequence is a number, it's also publicly represented as a number, has no different imaginable encodings... The entire purpose of this newtype is to avoid mixing it with other integers. There's also implicit constructor Sequence
just because the field is pub
.
That being said, I dislike From<u32> for Sequence
, From<Sequence> for u32
is OK though. Also if people prefer consistency I'm fine with having to_consensus
/from_consensus
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll leave this one open for discussion. I personally think it's clearer to be using the Sequence constructor + From<Sequence> for u32
since there is no special encoding for sequence numbers.
Sweet mate, cheers. We can probably learn a thing or two by using this patch in miniscript like I do in rust-bitcoin/rust-miniscript#408, I can have a go but I'll wait for you first so as not to tread on your toes. |
Sounds like a good way to figure out what APIs are useful and not but I'm not familiar with mini-script so happy for you to have a go at it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Light review, looks nice so far!
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] | ||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] | ||
pub struct Sequence(pub u32); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sequence has no invariants so I think public is fine.
Also maybe |
2c56750
to
97b5afa
Compare
Thanks all, looking a lot cleaner now. I've made |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple of observations.
Could you add |
9083777
to
478d513
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost ACK, would like to see public method naming resolved and also Default
reconsidered.
|
||
/// Returns `true` if the sequence has a relative locktime. | ||
#[inline] | ||
pub fn is_relative_lock_time(&self) -> bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is
vs has
(in doc), not sure what is better - native speaker check?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think @nlanson is a native English speaker but I'll chime in with 'is'. Also s/locktime/lock time/
since we use undersore separation in is_relative_lock_time
src/blockdata/transaction.rs
Outdated
|
||
/// Create a relative lock-time using time units where each unit is equivalent to 512 seconds. | ||
#[inline] | ||
pub fn from_timeunits(units: u16) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uff, this will be confusing. Maybe something like from_512_seconds_multiples
?
Maybe consider adding from_seconds_rounded
, from_seconds_floor
, from_seconds_ceiling
each dividing by 512 and applying appropriate operation.
Explaining in the docs that finer granularity is not supported by Bitcoin would be also helpful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've renamed from_timeunits()
to from_512_second_intervals()
and added from_seconds_floor
and from_seconds_ceiling
but have them returning Result<Self, RelativeLockTimeError>
due to the max possible seconds being larger that u16 and conversion from u32 to u16 being fallible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, if I am to add the RelativeTimeLock
enum in this PR, these methods would be implemented on the enum instead of Sequence
and Sequence
will instead have pub fn from_lock_time(lock_time: RelativeLockTime) -> Sequence
You've got a bunch of trailing whitespace issues in this PR @nlanson, we have a git pre-commit hook to help catch that (it does make committing slower though). Check out the |
We are currently working on two new timelock APIs - `LockTime`: rust-bitcoin/rust-bitcoin#994 - `Sequence`: rust-bitcoin/rust-bitcoin#1093 This PR demonstrates how we would use those APIs here in miniscript. Includes improvements that are not yet in 1093. To test this out locally you can grab the `WIP-sequence-and-lock-time` branch from my tree. You'll have to have `rust-bitcoincore-rpc` locally and patch it to use local `rust-bitcoin`. This PR is not intended as a merge candidate.
Hi @nlanson, I've hacked around in miniscript on top of this PR and the The diff --git a/src/blockdata/transaction.rs b/src/blockdata/transaction.rs
index b990744a..dace0958 100644
--- a/src/blockdata/transaction.rs
+++ b/src/blockdata/transaction.rs
@@ -243,10 +243,13 @@ impl Sequence {
pub const RBF_SIGNAL: Self = Sequence(0xFFFFFFFE);
/// Zero value sequence
pub const ZERO: Self = Sequence(0);
+
/// BIP-68 relative time lock disable flag mask
const DISABLE_FLAG_MASK: u32 = 0x80000000;
/// BIP-68 relative time lock type flag mask
const LOCK_TYPE_MASK: u32 = 0x00400000;
+ /// Allows getting the least significant 16 bits representing the CSV value.
+ const VALUE_MASK: u32 = 0x0000FFFF;
/// Retuns `true` if the sequence number is 0xffffffff.
#[inline]
@@ -279,6 +282,19 @@ impl Sequence {
self.is_relative_lock_time() & (self.0 & Sequence::LOCK_TYPE_MASK > 0)
}
+ /// Returns true if both sequence numbers are timelocks (i.e. enabled) and both are the same
+ /// type i.e., both height based or both time based.
+ pub fn is_same_type_locked(&self, other: Sequence) -> bool {
+ if self.is_time_locked() && other.is_time_locked() {
+ return true;
+ }
+ if self.is_height_locked() && other.is_height_locked() {
+ return true;
+ }
+ // Either one is disabled or they are different lock types.
+ false
+ }
+
/// Create a relative lock-time using block height.
#[inline]
pub fn from_height(height: u16) -> Self {
@@ -297,6 +313,24 @@ impl Sequence {
!self.is_final()
}
+ /// Returns the sixteen least significant bits representing a timelock value.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if [`Sequence`] is disabled.
+ pub fn value(&self) -> Result<u16, SequenceError> {
+ if !self.is_relative_lock_time() {
+ return Err(SequenceError::Disabled);
+ }
+ Ok(self.value_unchecked())
+ }
+
+ /// Returns the sixteen least significant bits representing a timelock value. Caller is expected
+ /// to have checked that the values are comparable by first calling [`is_same_unit_timelock`].
+ pub fn value_unchecked(&self) -> u16 {
+ (self.0 & Sequence::VALUE_MASK) as u16
+ }
+
/// Create a sequence from a u32 value.
#[inline]
pub fn from_consensus(n: u32) -> Self {
@@ -322,6 +356,42 @@ impl From<Sequence> for u32 {
}
}
+impl fmt::Display for Sequence {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+/// Catchall type for errors that relate to sequences.
+#[derive(Debug, Clone, PartialEq, Eq)]
+#[non_exhaustive]
+pub enum SequenceError {
+ /// Attempted to use sequence but it is disabled.
+ Disabled,
+}
+
+impl fmt::Display for SequenceError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::SequenceError::*;
+
+ match *self {
+ Disabled => write!(f, "attempted to use sequence but it is disabled"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+impl std::error::Error for SequenceError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ use self::SequenceError::*;
+
+ match *self {
+ Disabled => None,
+ }
+ }
+}
+
/// Bitcoin transaction output.
///
/// Defines new coins to be created as a result of the transaction,
diff --git a/src/lib.rs b/src/lib.rs
index 2bb103fa..a67af7b0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -102,6 +102,7 @@ pub use crate::blockdata::block::Block;
pub use crate::blockdata::block::BlockHeader;
pub use crate::blockdata::locktime::{self, LockTime, PackedLockTime};
pub use crate::blockdata::script::Script;
+pub use crate::blockdata::transaction::Sequence;
pub use crate::blockdata::transaction::Transaction;
pub use crate::blockdata::transaction::TxIn;
pub use crate::blockdata::transaction::TxOut; (The error could be a struct instead of an enum?) |
accidentally closed on force push |
026bdef
to
7002299
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found some more things to polish, not critical though.
src/blockdata/transaction.rs
Outdated
/// Bitcoin transaction input sequencing number. | ||
/// | ||
/// The sequence field is used for: | ||
/// - Indicating whether [BIP-65] absolute lock-times are enabled. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just realized this is confusing because lock times are ancient and the behavior was implemented before BIPs existed. BIP-65 is somewhat related but not the entire specification.
I suggest removing the reference from that phrase and perhaps put below something like "Note that transactions spending an output with OP_CHECKLOCKTIMEVERIFY
MUST NOT use Sequence::MAX
for the corresponding input. (BIP-65)."
Also "absolute" is not common terminology, so maybe write "Indicating whether the absolute lock-time (specified in lock_time
field of Transaction
) is enabled." Singular because there's only one lock time for transaction.
5cecaec
to
7c1565d
Compare
RE: I'm finding the names misleading because I have got what is enabled/disabled written in both docs but still not sure if this is enough. |
What! Confusion surrounding timelock terminology - outrageous :) |
added absolute and relative prefixes in docs where they were missing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK cd5874c
The nit can be fixed later.
This is really great! I have been ignoring this PR because there's been a ton of active discussion. Now that I look at the result, it's really ambitious and (I think) correct. I had previously argued that we couldn't newtype |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK cd5874c
fixed nits. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK e34bc53
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK e34bc53
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK e34bc53
e34bc53 Add new type for sequence (Noah Lanson) Pull request description: #1082 Created a new type for txin sequence field with methods to create sequences with relative time locks from block height or time units. ACKs for top commit: Kixunil: ACK e34bc53 tcharding: ACK e34bc53 apoelstra: ACK e34bc53 Tree-SHA512: 6605349d0312cc36ef9a4632f954e59265b3ba5cfd437aa88a37672fe479688aa4a3eff474902f8cc55848efe55caf3f09f321b3a62417842bfc3ec365c40688
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice. Thanks for working on that, that'll be helpful for me downstream! :)
/// Returns `true` if the sequence has a relative lock-time. | ||
#[inline] | ||
pub fn is_relative_lock_time(&self) -> bool { | ||
self.0 & Sequence::LOCK_TIME_DISABLE_FLAG_MASK == 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason for ruling out 0
block (or time) relative timelocks?`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't rule them out. 0 & x == 0
holds for all x
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I misread the &
for a &&
🤦. That wouldn't even make sense as it would always be false. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That wouldn't even compile since they are not bools. :)
|
||
/// Create a relative lock-time using block height. | ||
#[inline] | ||
pub fn from_height(height: u16) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Neat.
#1082
Created a new type for txin sequence field with methods to create sequences with relative time locks from block height or time units.