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
Expose valid (min, max) difficulty transition thresholds #1820
Conversation
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.
Thanks for the contribution! Sorry for rugging you :)
My review is just for code correctness, I have no knowledge on whether or not Target
should expose these, the use case sounds sane to me though.
ACK 669f248
Isn't If not, I would still prefer having a method to do the verification rather than ad-hoc shr/shl because presumably more people will want to do this kind of verification, not just LDK. Also this shouldn't block you because you can implement it manually: // this is shl, shr is similar
let bytes = target.to_be_bytes();
let high = u128::from_be_bytes(bytes[..16].try_into().unwrap());
let low = u128::from_be_bytes(bytes[16..].try_into().unwrap());
let transferred_bits = low >> (128 - bits_to_shift);
let high = high << bits_to_shift | transferred_bits;
let low = low << bits_to_shift;
let mut shifted_bytes = [0u8; 64];
shifted_bytes[..16].copy_from_slice(&high.to_be_bytes());
shifted_bytes[16..].copy_from_slice(&high.to_le_bytes());
Target::from_le_bytes(shifted_bytes) I know it's not nice but at least it's not impossible and we can clean it up soon. |
@Kixunil I think the shift operators make sense here, since they shift "how many bits of difficulty" the target is. I'm leaning toward ACKing this because the operators do make conceptual sense, and they don't make sense for |
But yes, the specific "determine difficulty rails after an adjustment" algorithm that @wpaulino is describing is something we should have first-class support for! Note that the difficulty adjustment is bounded by "divide by 4" below and "multiply by 4" above. So this is a nearly trivial operation even if your only operators are shifts. It would be a shame to turn 2 lines into 20. Edit It's actually a little more subtle, I remembered, since there's a hard upper bound at difficulty 1. We probably should provide such a method (and maybe one for |
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 669f248
Isn't it a bit awkward to have only shift exposed and not other operations? Also can't find it now but IIRC division was also needed to adjust difficulty. One thing that bothers me about this is that lowering target by an amount that can not be expressed as shift is still valid operation and does increase difficulty but we don't allow it. |
Fair enough. I figured since it was something already supported by the API previously, and it's required for a valid use-case, it would make sense to expose it again.
Yeah, I thought so too. I guess it depends on what plans you all have for |
I'd also strongly prefer if rust-bitcoin had a method for this, but the context-less validate_pow isn't what we need - we want to verify that a difficulty adjustment is sane. This is a really critical operation to prevent all kinds of DoS attacks on clients which download headers, and while it would be great if we could call out to rust-bitcoin and have it tell us if a difficulty transition given a pile of headers is correct, just checking the 4x bounds at least addresses the DoS issues for the most part. |
I think we should expose both functions (compute min/max bounds, and compute exact adjustment). And we shouldn't expose the shifts. @wpaulino could you adjust this PR to expose a min/max function instead? (You could also implement a full difficulty calculation if you're feeling generous, but that'd be 10x the work and it sounds like you'd be satisfied with just the min/max for now.) |
669f248
to
3357db8
Compare
@apoelstra only exposed the thresholds for now, but I may come back around to implement the exact adjustment computation in the future. |
Needs |
3357db8
to
6187983
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.
ACK 6187983
Thanks man! |
@wpaulino the |
Whoops, thanks. Also bounded the |
6187983
to
f95a3e9
Compare
bitcoin/src/pow.rs
Outdated
if min > Self::MAX { | ||
Self::MAX | ||
} else { | ||
min | ||
} |
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 fan of this, it reads funnily. If we are not upholding the invariant that Self
is less than or equal to Self::MAX
then we should fix that and not band aid over it here.
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 raised an issue: #1828
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.
Agree.
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 this something we want to block this PR on, or can it come after this lands?
I also realized that MAX
here is network dependent. The current value is valid for mainnet and testnet, but regtest and signet have higher values, invalidating the current MAX
constant. This makes me think that until we have network dependent max values, we shouldn't do any bounds checking, unless we only plan to support mainnet for such operations.
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.
o.O you can have difficulty < 1 for regtest and signet? What is the corresponding target value?
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.
Or do you mean that the conversion between difficulty and target is different on these networks....which would require we modify our conversion 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.
The conversion is the same, but the highest target/PoW limit varies by network. IIUC, the difficulty is just the ratio between the highest target and the current target, so a difficulty of 1 just represents the highest target for that given network. With that said, it seems Target::difficulty
currently only works for mainnet and testnet values, since they both share the same max target.
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.
:( so the conversion is different for different networks.
That is really obscure and does not seem necessary. But ok, we'll find a way to deal with 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.
How would you like to proceed then? Since we're not doing any bounds checking at all, and it depends on the active network, we could defer that to future work and not hold back the changes proposed here?
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.
Agree let's not hold this up.
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 the overflow should be fixed unless I'm missing something.
bitcoin/src/pow.rs
Outdated
if min > Self::MAX { | ||
Self::MAX | ||
} else { | ||
min | ||
} |
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.
Agree.
Once `U256` was made private, we lost the ability to check whether a valid difficulty transition was made in the chain, since `Target` no longer exposes any arithmetic operations.
f95a3e9
to
8e6f953
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.
ACK 8e6f953
/// | ||
/// The difficulty can only decrease or increase by a factor of 4 max on each difficulty | ||
/// adjustment period. | ||
pub fn max_difficulty_transition_threshold(&self) -> Self { Self(self.0 << 2) } |
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.
This will debug-panic on large values, is this OK/correct?
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.
What makes you say that @Kixunil ? We use wrapping_shl
to implement <<
so I thought this couldn't panic, what am I missing?
@wpaulino why did the check against MAX
get removed here? In the previous iteration I only meant to comment that I didn't like the check in the min_difficulty_transition_threshold
method, in the max one I thought it was correct.
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.
why did the check against MAX get removed here? In the previous iteration I only meant to comment that I didn't like the check in the min_difficulty_transition_threshold method, in the max one I thought it was correct.
Because MAX
currently is only applicable to mainnet and testnet. I guess that's fine since this is also the case with Target::difficulty
, but I figured since we want this to be network agnostic at some point, it doesn't make sense to bound by MAX
until we have the network specific max constants.
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.
Oh yes, sorry, my bad for not remembering all the context when reviewing. I did read that before.
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.
Oh, sorry I got confused, it'd only panic if the argument was too large, 2 is not too large.
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 constantly have this same confusion about left-shift. It's a little weird, this notion of "overflow" is different from that for the arithmetic opcodes.
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 8e6f953.
Retracting my ACK. I might need some more time to check this completely. bitcoin core does these operations of in64_t. and then converts to uint256_t. Need to make sure if the way we are doing it is correct
@sanket1729 could you point to what you were looking at? There should only be a conversion from the compact representation ( |
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 8e6f953 . Sorry, was confused by some details.
Implementors of difficulty transition algorithms should take care to only operate on Some of these conversions are lossy, and could lead to subtle bugs. |
I'm happy to merge this as-is. We don't expose enough functionality yet for somebody to compute actual difficulty targets so I'm not too worried about somebody using the incorrect-on-extreme-values logic to cause consensus faults (also, even if you did, the first 10000 or so Bitcoin blocks hit the min-difficulty clamp so this would be immediately visible). And as written, this function serves well as a simple anti-DoS sanity check on a provided chain. Interesting that |
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 8e6f953
Have I gone mad, where are these changes gone? They are not on master anymore.
cc @apoelstra |
ugh, I am a total wombat, I had an old tag checked out from yesterday. Face palm. |
We just backported PR rust-bitcoin#1820, add a changelog entry and bump the crate version.
ba99aa4 Bump version to 0.30.2 (Tobin C. Harding) 3c9bbca Expose valid (min, max) difficulty transition thresholds (Wilmer Paulino) Pull request description: Patch one backports #1820, I believe this is all that is needed for LDK to be able to upgrade to `rust-bitcoin v0.30`. Context: - #1820 - lightningdevkit/rust-lightning#2124 Patch 2 adds a changelog entry and bumps the minor version. ACKs for top commit: Kixunil: ACK ba99aa4 Tree-SHA512: 93adb52bbc8e61ece63e5653dea7cf6d2d25028f25cb8bbda14606d9ebf74becaee0a0c4f4f3d7fde56c0e2b8a57f8c9be263e04833161a1da0baaf8fcca8764
Once
U256
was made private, we lost the ability to check whether a valid difficulty transition was made in the chain, sinceTarget
doesn't expose any operations. We only choose to exposeShl<u32>
andShr<u32>
such that we can compute the min and max target thresholds allowed for a difficulty transition.This is something we realized was missing after bumping to
rust-bitcoin v0.30.0
inrust-lightning
, specifically for ourlightning-block-sync
crate. It may also be worth having a helper inrust-bitcoin
that checks a header properly builds upon the previous, but that can be left for future work.