Support weight prediction in `const` context
Some smart contracts or simplified wallets statically know the sizes of transactions or inputs. The possible approaches to handling them so far were re-computing the values (and hoping the optimizer will const fold them) or using a simple constant which may be harder to understand and get right. It's much nicer to just use a `const` but our code didn't support it until now. This change adds methods that can compute the prediction in `const` context for Rust versions >= 1.46.0 which allow use of loops (and conditions but those could be workaround anyway). As a side effect of this, the change also adds `const` to `VarInt::len` in Rust 1.46+. While this one could be made unconditional using array trick it's probably not worth it because of the planned MSRV bump. Note: this commit is intentionally unformatted to make diff easier to understand. Formatting will be done in future commit.
This commit is contained in:
parent
2982681d59
commit
d56d202aeb
|
@ -1247,6 +1247,11 @@ pub fn predict_weight<I, O>(inputs: I, output_script_lens: O) -> Weight
|
|||
let script_size = script_len + VarInt(script_len as u64).len();
|
||||
(output_count + 1, total_scripts_size + script_size)
|
||||
});
|
||||
predict_weight_internal(input_count, partial_input_weight, inputs_with_witnesses, output_count, output_scripts_size)
|
||||
}
|
||||
|
||||
crate::internal_macros::maybe_const_fn! {
|
||||
fn predict_weight_internal(input_count: usize, partial_input_weight: usize, inputs_with_witnesses: usize, output_count: usize, output_scripts_size: usize) -> Weight {
|
||||
let input_weight = partial_input_weight + input_count * 4 * (32 + 4 + 4);
|
||||
let output_size = 8 * output_count + output_scripts_size;
|
||||
let non_input_size =
|
||||
|
@ -1265,6 +1270,42 @@ pub fn predict_weight<I, O>(inputs: I, output_script_lens: O) -> Weight
|
|||
};
|
||||
Weight::from_wu(weight as u64)
|
||||
}
|
||||
}
|
||||
|
||||
/// Predicts the weight of a to-be-constructed transaction in const context.
|
||||
///
|
||||
/// *Important: only available in Rust 1.46+*
|
||||
///
|
||||
/// This is a `const` version of [`predict_weight`] which only allows slices due to current Rust
|
||||
/// limitations around `const fn`. Because of these limitations it may be less efficient than
|
||||
/// `predict_weight` and thus is intended to be only used in `const` context.
|
||||
///
|
||||
/// Please see the documentation of `predict_weight` to learn more about this function.
|
||||
#[cfg(rust_v_1_46)]
|
||||
pub const fn predict_weight_from_slices(inputs: &[InputWeightPrediction], output_script_lens: &[usize]) -> Weight {
|
||||
let mut partial_input_weight = 0;
|
||||
let mut inputs_with_witnesses = 0;
|
||||
|
||||
// for loops not supported in const fn
|
||||
let mut i = 0;
|
||||
while i < inputs.len() {
|
||||
let prediction = inputs[i];
|
||||
partial_input_weight += prediction.script_size * 4 + prediction.witness_size;
|
||||
inputs_with_witnesses += (prediction.witness_size > 0) as usize;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
let mut output_scripts_size = 0;
|
||||
|
||||
i = 0;
|
||||
while i < output_script_lens.len() {
|
||||
let script_len = output_script_lens[i];
|
||||
output_scripts_size += script_len + VarInt(script_len as u64).len();
|
||||
i += 1;
|
||||
}
|
||||
|
||||
predict_weight_internal(inputs.len(), partial_input_weight, inputs_with_witnesses, output_script_lens.len(), output_scripts_size)
|
||||
}
|
||||
|
||||
/// Weight prediction of an individual input.
|
||||
///
|
||||
|
@ -1351,6 +1392,37 @@ impl InputWeightPrediction {
|
|||
witness_size,
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the prediction for a single input in `const` context.
|
||||
///
|
||||
/// *Important: only available in Rust 1.46+*
|
||||
///
|
||||
/// This is a `const` version of [`new`](Self::new) which only allows slices due to current Rust
|
||||
/// limitations around `const fn`. Because of these limitations it may be less efficient than
|
||||
/// `new` and thus is intended to be only used in `const` context.
|
||||
#[cfg(rust_v_1_46)]
|
||||
pub const fn from_slice(input_script_len: usize, witness_element_lengths: &[usize]) -> Self {
|
||||
let mut i = 0;
|
||||
let mut total_size = 0;
|
||||
// for loops not supported in const fn
|
||||
while i < witness_element_lengths.len() {
|
||||
let elem_len = witness_element_lengths[i];
|
||||
let elem_size = elem_len + VarInt(elem_len as u64).len();
|
||||
total_size += elem_size;
|
||||
i += 1;
|
||||
}
|
||||
let witness_size = if !witness_element_lengths.is_empty() {
|
||||
total_size + VarInt(witness_element_lengths.len() as u64).len()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let script_size = input_script_len + VarInt(input_script_len as u64).len();
|
||||
|
||||
InputWeightPrediction {
|
||||
script_size,
|
||||
witness_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -372,8 +372,11 @@ impl_int_encodable!(i64, read_i64, emit_i64);
|
|||
|
||||
#[allow(clippy::len_without_is_empty)] // VarInt has on concept of 'is_empty'.
|
||||
impl VarInt {
|
||||
crate::internal_macros::maybe_const_fn! {
|
||||
/// Gets the length of this VarInt when encoded.
|
||||
///
|
||||
/// *Important: this method is only `const` in Rust 1.46 or higher!*
|
||||
///
|
||||
/// Returns 1 for 0..=0xFC, 3 for 0xFD..=(2^16-1), 5 for 0x10000..=(2^32-1),
|
||||
/// and 9 otherwise.
|
||||
#[inline]
|
||||
|
@ -386,6 +389,7 @@ impl VarInt {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encodable for VarInt {
|
||||
#[inline]
|
||||
|
|
|
@ -45,6 +45,20 @@ macro_rules! impl_consensus_encoding {
|
|||
);
|
||||
}
|
||||
pub(crate) use impl_consensus_encoding;
|
||||
|
||||
/// Marks the function const in Rust 1.46.0
|
||||
macro_rules! maybe_const_fn {
|
||||
($(#[$attr:meta])* $vis:vis fn $name:ident($($args:tt)*) -> $ret:ty $body:block) => {
|
||||
#[cfg(rust_v_1_46)]
|
||||
$(#[$attr])*
|
||||
$vis const fn $name($($args)*) -> $ret $body
|
||||
|
||||
#[cfg(not(rust_v_1_46))]
|
||||
$(#[$attr])*
|
||||
$vis fn $name($($args)*) -> $ret $body
|
||||
}
|
||||
}
|
||||
pub(crate) use maybe_const_fn;
|
||||
// We use test_macros module to keep things organised, re-export everything for ease of use.
|
||||
#[cfg(test)]
|
||||
pub(crate) use test_macros::*;
|
||||
|
|
Loading…
Reference in New Issue