simplify contract helpers

This commit is contained in:
wiesche
2026-09-12 18:55:04 +02:00
parent f1d11e38ab
commit 89dd555851
9 changed files with 115 additions and 149 deletions
+3 -8
View File
@@ -303,14 +303,9 @@ where
{
let mut client = w.w2n_client().clone();
let wd = match proof.witness_data.clone() {
Some(w) => w,
None => {
return Err(Error::PaymentProof(
"Cannot verify early payment proof with no witness data".to_string(),
))
}
};
let wd = proof.witness_data.as_ref().ok_or_else(|| {
Error::PaymentProof("Cannot verify early payment proof with no witness data".to_string())
})?;
let (retrieved_kernel, _) = match client.get_kernel(&wd.kernel_commitment, None, None) {
Err(e) => {
+33 -45
View File
@@ -571,51 +571,39 @@ where
.as_ref()
.and_then(|proof| proof.sender_public_nonce);
let (mut proof, sender_part_sig) = match tx.payment_proof {
Some(p) => {
if p.receiver_public_nonce.is_none() {
return Err(Error::PaymentProofRetrieval(
"Early payment proof requires stored receiver public nonce".into(),
));
};
if p.receiver_public_excess.is_none() {
return Err(Error::PaymentProofRetrieval(
"Early payment proof requires stored receiver public excess".into(),
));
};
if p.timestamp.is_none() {
return Err(Error::PaymentProofRetrieval(
"Early payment proof requires stored timestamp".into(),
));
};
if p.sender_part_sig.is_none() {
return Err(Error::PaymentProofRetrieval(
"Early payment proof requires stored sender partial signature".into(),
));
};
(
EarlyPaymentProof {
proof_type: PaymentProofType::try_from(
p.proof_type.unwrap_or(PaymentProofType::Invoice.as_u8()),
)?,
amount,
receiver_public_nonce: p.receiver_public_nonce.unwrap(),
receiver_public_excess: p.receiver_public_excess.unwrap(),
sender_address: p.sender_address,
timestamp: p.timestamp.unwrap().timestamp(),
memo: p.memo,
promise_signature: p.receiver_signature,
witness_data: None,
},
p.sender_part_sig.unwrap(),
)
}
None => {
return Err(Error::PaymentProofRetrieval(
"Transaction does not contain a payment proof".to_owned(),
));
}
let p = tx.payment_proof.ok_or_else(|| {
Error::PaymentProofRetrieval("Transaction does not contain a payment proof".to_owned())
})?;
let receiver_public_nonce = p.receiver_public_nonce.ok_or_else(|| {
Error::PaymentProofRetrieval(
"Early payment proof requires stored receiver public nonce".into(),
)
})?;
let receiver_public_excess = p.receiver_public_excess.ok_or_else(|| {
Error::PaymentProofRetrieval(
"Early payment proof requires stored receiver public excess".into(),
)
})?;
let timestamp = p.timestamp.ok_or_else(|| {
Error::PaymentProofRetrieval("Early payment proof requires stored timestamp".into())
})?;
let sender_part_sig = p.sender_part_sig.ok_or_else(|| {
Error::PaymentProofRetrieval(
"Early payment proof requires stored sender partial signature".into(),
)
})?;
let mut proof = EarlyPaymentProof {
proof_type: PaymentProofType::try_from(
p.proof_type.unwrap_or(PaymentProofType::Invoice.as_u8()),
)?,
amount,
receiver_public_nonce,
receiver_public_excess,
sender_address: p.sender_address,
timestamp: timestamp.timestamp(),
memo: p.memo,
promise_signature: p.receiver_signature,
witness_data: None,
};
// Now to kernel lookup, to fill in the witness data
+1 -1
View File
@@ -328,7 +328,7 @@ where
/// Get an (Optional) tx log entry by parent key id and log id.
pub fn get_tx_log_entry_by_id(
&self,
parent_id: Identifier,
parent_id: &Identifier,
log_id: u32,
) -> Result<Option<TxLogEntry>, Error> {
let tx_log_key = to_key_u64(parent_id.to_bytes(), log_id as u64);
+2 -2
View File
@@ -55,7 +55,7 @@ where
// cannot be built until the input is unlocked.
// FUTURE: we may want to boost fees if we notice the original tx in the mempool.
let tx_id = args.tx_id;
let parent_key_id = utils::parent_key_for(w, args.src_acct_name.as_ref())?;
let parent_key_id = utils::parent_key_for(w, args.src_acct_name.as_deref())?;
// Inputs we contributed to tx_id that are still recoverable. Locked => the original tx
// is still active; Unspent => a previous revoke cancelled it but the self-spend did not
@@ -75,7 +75,7 @@ where
// it is already a *Cancelled type (and the inputs are Unspent), so we skip straight to
// re-spending them.
let revoked = w
.get_tx_log_entry_by_id(parent_key_id.clone(), tx_id)?
.get_tx_log_entry_by_id(&parent_key_id, tx_id)?
.ok_or_else(|| Error::NotFoundErr(format!("Transaction {}", tx_id)))?;
let revoked_slate_id = revoked.tx_slate_id;
let needs_cancel = matches!(
+3 -5
View File
@@ -46,7 +46,7 @@ where
// Get data required for creating a context
let height = w.w2n_client().get_chain_tip()?.0;
let parent_key_id =
contract_utils::parent_key_for(w, setup_args.src_acct_name.as_ref())?;
contract_utils::parent_key_for(w, setup_args.src_acct_name.as_deref())?;
self::create(
w,
keychain_mask,
@@ -179,14 +179,14 @@ where
)));
}
// Add selected/created inputs/outputs to the context
add_inputs_to_ctx(context, &inputs)?;
add_inputs_to_ctx(context, &inputs);
add_outputs_to_ctx(w, keychain_mask, context, my_output_amounts)?;
Ok(())
}
/// Add inputs to Context
fn add_inputs_to_ctx(context: &mut Context, inputs: &Vec<OutputData>) -> Result<(), Error> {
fn add_inputs_to_ctx(context: &mut Context, inputs: &[OutputData]) {
debug!("contract::utils::add_inputs_to_ctx => adding inputs to context");
for input in inputs {
context.add_input(&input.key_id, &input.mmr_index, input.value);
@@ -195,8 +195,6 @@ fn add_inputs_to_ctx(context: &mut Context, inputs: &Vec<OutputData>) -> Result<
&input.key_id, input.value
);
}
Ok(())
}
/// Add outputs to Context
+33 -33
View File
@@ -71,10 +71,8 @@ where
pub fn compute(
setup_args: &ContractSetupArgsAPI,
committed_fee: Option<FeeFields>,
inputs: &mut Vec<OutputData>,
) -> Result<(Vec<OutputData>, Vec<u64>, FeeFields), Error>
where
{
inputs: &mut [OutputData],
) -> Result<(Vec<OutputData>, Vec<u64>, FeeFields), Error> {
let (inputs, fee) = select_inputs(setup_args, committed_fee, inputs)?;
let input_sum = inputs
.iter()
@@ -88,10 +86,8 @@ where
fn select_inputs(
setup_args: &ContractSetupArgsAPI,
committed_fee: Option<FeeFields>,
inputs: &mut Vec<OutputData>,
) -> Result<(Vec<OutputData>, FeeFields), Error>
where
{
inputs: &mut [OutputData],
) -> Result<(Vec<OutputData>, FeeFields), Error> {
// We use 'lhs' and 'rhs' to denote the amounts on the left/right-hand side of the equation.
// To simulate receive/payment value we:
// - add positive net_change to the 'lhs' for the receiver (to simulate sender's input)
@@ -142,15 +138,16 @@ where
}
// NOTE: that these are inputs that MUST be selected. We should lock the inputs if they're
// required to minimize any potential race conditions.
let must_use_list = required_inputs.unwrap_or(vec![]);
if must_use_list.len() > 0 {
let must_use_list = required_inputs.unwrap_or_default();
if !must_use_list.is_empty() {
// Sort the inputs first by the ones listed in the use_inputs and then by value
inputs.sort_by_key(|out| {
(
// We have to negate the boolean to prioritize truthy values because
// false is 0 and hence would be sorted before truthy entries
!(out.commit.is_some()
&& must_use_list.contains(&&out.commit.as_ref().unwrap()[..])),
!out.commit
.as_deref()
.is_some_and(|commit| must_use_list.contains(&commit)),
out.value,
)
});
@@ -171,7 +168,7 @@ where
// We want to count how many inputs we've picked _so far_. This is used to prevent picking
// all 0*H +r*G outputs when we call with min_input_amount=0 and want just a payjoin.
let mut n_inputs = 0;
let mut must_use_list_cnt: u32 = 0;
let mut must_use_list_cnt: usize = 0;
// If we have already committed to a fee (context.fee) then set this as our "minimum" fee. The reason we have to
// do this is to avoid solving the equation for less than the committed fee. We have to guarantee the inputs we take
// are enough to cover the committed fee. At the end of selection, we check that the fees for the selection were not
@@ -193,8 +190,10 @@ where
let mut selected_inputs = Vec::new();
for out in inputs.iter() {
// Take the commitment if it is listed as one of those we MUST take
let must_take =
out.commit.is_some() && must_use_list.contains(&&out.commit.as_ref().unwrap()[..]);
let must_take = out
.commit
.as_deref()
.is_some_and(|commit| must_use_list.contains(&commit));
// Compute the fee without this input
let fee_without = fee_contribution(
n_inputs,
@@ -251,13 +250,15 @@ where
}
// Return an error if the fee computed is larger than the committed fee
if committed_fee.is_some() && my_fee.fee() > committed_fee.unwrap().fee() {
let msg = format!(
"Fee computed ({}) is larger than the committed fee ({}); cancel the transaction and retry",
my_fee.fee(),
committed_fee.unwrap().fee()
);
return Err(Error::Fee(msg).into());
if let Some(committed_fee) = committed_fee {
if my_fee.fee() > committed_fee.fee() {
let msg = format!(
"Fee computed ({}) is larger than the committed fee ({}); cancel the transaction and retry",
my_fee.fee(),
committed_fee.fee()
);
return Err(Error::Fee(msg));
}
}
// Check that the inputs we picked are enough to cover all our output amounts and fees
@@ -276,19 +277,18 @@ where
available_disp: amount_to_hr_string(total, false),
needed,
needed_disp: amount_to_hr_string(needed, false),
}
.into());
// return Err(ErrorKind::GenericError(msg.into()).into());
});
}
// Assert that all the use_inputs have been selected
if must_use_list.len() != must_use_list_cnt as usize {
let msg = format!(
"We have not found all the inputs that have been requested. {}, found only: {}",
setup_args.selection_args.use_inputs.as_ref().unwrap(),
must_use_list_cnt
);
return Err(Error::GenericError(msg.into()).into());
if let Some(use_inputs) = &setup_args.selection_args.use_inputs {
if must_use_list.len() != must_use_list_cnt {
let msg = format!(
"We have not found all the inputs that have been requested. {}, found only: {}",
use_inputs, must_use_list_cnt
);
return Err(Error::GenericError(msg));
}
}
debug!(
@@ -307,7 +307,7 @@ fn build_output_amount_list(
let expected_net_change = setup_args.net_change.ok_or_else(|| {
Error::GenericError("Contract requires a net change (--send or --receive)".to_string())
})?;
let mut my_output_amounts = setup_args.selection_args.output_amounts()?;
let mut my_output_amounts = setup_args.selection_args.output_amounts();
let custom_outputs_sum = my_output_amounts
.iter()
.try_fold(0u64, |acc, v| acc.checked_add(*v))
+5 -12
View File
@@ -49,21 +49,14 @@ impl OutputSelectionArgs {
}
/// Return a list of commitments we must use
pub fn required_inputs(&self) -> Option<Vec<&str>> {
if self.use_inputs.is_some() {
Some(
self.use_inputs.as_ref().unwrap()[..]
.split(",")
.filter(|x| *x != "any")
.collect(),
)
} else {
None
}
self.use_inputs
.as_deref()
.map(|inputs| inputs.split(',').filter(|x| *x != "any").collect())
}
/// Returns the output amounts (nanogrin) we have to create. Amounts arrive already
/// parsed by the caller (e.g. the CLI), so this is just an accessor.
pub fn output_amounts(&self) -> Result<Vec<u64>, Error> {
Ok(self.make_outputs.clone().unwrap_or_default())
pub fn output_amounts(&self) -> Vec<u64> {
self.make_outputs.clone().unwrap_or_default()
}
/// Returns the sum of our output amounts
pub fn sum_output_amounts(&self) -> Result<u64, Error> {
+26 -33
View File
@@ -33,7 +33,7 @@ pub fn create_tx_log_entry(
net_change: i64,
parent_key_id: Identifier,
log_id: u32,
) -> Result<TxLogEntry, Error> {
) -> TxLogEntry {
let log_type = if slate.num_participants == 1 {
TxLogEntryType::TxSelfSpend
} else {
@@ -43,7 +43,7 @@ pub fn create_tx_log_entry(
TxLogEntryType::TxSent
}
};
let mut t = TxLogEntry::new(parent_key_id.clone(), log_type, log_id);
let mut t = TxLogEntry::new(parent_key_id, log_type, log_id);
// stored_tx is set in save_step, once we have signed and the transaction is written.
t.tx_slate_id = Some(slate.id);
@@ -57,7 +57,7 @@ pub fn create_tx_log_entry(
n => Some(n),
};
Ok(t)
t
}
/// Update TxLogEntry with data from the sign step
@@ -158,16 +158,13 @@ pub fn get_net_change(
// We have a context so we must have agreed on a certain net_change value in Context.net_change.
// If we have both Context.net_change and setup_args.net_change, then they must be equal.
let ctx_net_change = context.get_net_change()?;
match expected_net_change {
Some(args_net_change) => {
if ctx_net_change != args_net_change {
return Err(Error::GenericError(format!(
"Expected net change mismatch! Context.net_change: {}, setup_args.net_change: {}",
ctx_net_change, args_net_change
)));
}
if let Some(args_net_change) = expected_net_change {
if ctx_net_change != args_net_change {
return Err(Error::GenericError(format!(
"Expected net change mismatch! Context.net_change: {}, setup_args.net_change: {}",
ctx_net_change, args_net_change
)));
}
None => (),
}
expected_net_change = Some(ctx_net_change);
} else {
@@ -176,18 +173,15 @@ pub fn get_net_change(
// Fail if net_change was not passed to setup_args and was also not present in the context.
// This means it has not been explicitly agreed on and we require the user to pass it.
if expected_net_change.is_none() {
return Err(Error::GenericError(
"You did not agree on the expected net difference.".into(),
)
.into());
}
let expected_net_change = expected_net_change.ok_or_else(|| {
Error::GenericError("You did not agree on the expected net difference.".into())
})?;
debug!(
"contract::utils::get_net_change => expected_net_change: {}",
expected_net_change.unwrap()
expected_net_change
);
Ok(expected_net_change.unwrap())
Ok(expected_net_change)
}
/// Lock inputs and store the Context, TxLogEntry and OutputData atomically
@@ -221,15 +215,14 @@ where
// We are at step2 if we don't have context.log_id and we have signed the slate
let is_step2 = context.log_id.is_none() && is_signed;
let mut tx_log_entry = {
if context.log_id.is_none() {
let mut tx_log_entry = match context.log_id {
None => {
// We create a new entry with log_id=0 and but replace it with the real id before committing
create_tx_log_entry(slate, context.get_net_change()?, parent_key_id.clone(), 0)?
} else {
let log_id = context.log_id.unwrap();
w.get_tx_log_entry_by_id(parent_key_id.clone(), log_id)?
.ok_or_else(|| Error::NotFoundErr(format!("Transaction log entry {}", log_id)))?
create_tx_log_entry(slate, context.get_net_change()?, parent_key_id.clone(), 0)
}
Some(log_id) => w
.get_tx_log_entry_by_id(parent_key_id, log_id)?
.ok_or_else(|| Error::NotFoundErr(format!("Transaction log entry {}", log_id)))?,
};
// Update TxLogEntry if we have signed the contract (we have data about the kernel)
@@ -280,11 +273,11 @@ where
// Update TxLogEntry
if context.log_id.is_none() {
// If we just created the TxLogEntry, we have to assign it an id
let log_id = batch.next_tx_log_id(&parent_key_id)?;
let log_id = batch.next_tx_log_id(parent_key_id)?;
tx_log_entry.id = log_id;
context.log_id = Some(log_id);
}
batch.save_tx_log_entry(tx_log_entry.clone(), &parent_key_id)?;
batch.save_tx_log_entry(tx_log_entry, parent_key_id)?;
// Create OutputData entries and lock inputs if we added outputs at this step
if step_added_outputs {
// Create an OutputData entry for every created output
@@ -493,16 +486,16 @@ pub fn verify_setup_args_consistency(
/// active account.
pub fn parent_key_for<C, K>(
w: &mut WalletBackend<C, K>,
src_acct_name: Option<&String>,
src_acct_name: Option<&str>,
) -> Result<Identifier, Error>
where
C: NodeClient,
K: Keychain,
{
let parent_key_id = match src_acct_name {
Some(d) => match w.get_acct_path(d.clone())? {
Some(d) => match w.get_acct_path(d.to_owned())? {
Some(p) => p.path,
None => return Err(Error::UnknownAccountLabel(d.clone())),
None => return Err(Error::UnknownAccountLabel(d.to_owned())),
},
None => w.parent_key_id(),
};
@@ -519,7 +512,7 @@ mod tests {
for (net_change, credited, debited) in
[(1, 1, 0), (0, 0, 0), (-1, 0, 1), (i64::MIN, 0, 1u64 << 63)]
{
let entry = create_tx_log_entry(&slate, net_change, Identifier::zero(), 0).unwrap();
let entry = create_tx_log_entry(&slate, net_change, Identifier::zero(), 0);
assert_eq!(entry.amount_credited, credited);
assert_eq!(entry.amount_debited, debited);
}
+9 -10
View File
@@ -328,11 +328,10 @@ impl EarlyPaymentProof {
recipient_address: &DalekPublicKey,
) -> Result<(), Error> {
check_proof_type(&self.proof_type)?;
if self.promise_signature.is_none() {
return Err(Error::PaymentProofValidation(
"Missing promise signature".into(),
));
}
let promise_signature = self
.promise_signature
.as_ref()
.ok_or_else(|| Error::PaymentProofValidation("Missing promise signature".into()))?;
// Rebuild message
let mut sig_data_bin = Vec::new();
@@ -341,7 +340,7 @@ impl EarlyPaymentProof {
})?;
if recipient_address
.verify(&sig_data_bin, self.promise_signature.as_ref().unwrap())
.verify(&sig_data_bin, promise_signature)
.is_err()
{
return Err(Error::PaymentProof(
@@ -359,13 +358,13 @@ impl EarlyPaymentProof {
excess_sig: &Signature,
msg: &Message,
) -> Result<(), Error> {
if self.witness_data.is_none() {
return Err(Error::PaymentProofValidation("Missing witness data".into()));
}
let wd = self
.witness_data
.as_ref()
.ok_or_else(|| Error::PaymentProofValidation("Missing witness data".into()))?;
self.verify_promise_signature(recipient_address)?;
let wd = self.witness_data.as_ref().unwrap().clone();
{
let static_secp = static_secp_instance();
let static_secp = static_secp.lock();