[bug] fix duplicate transactions issue in handle_recover_transaction + refactor

This commit is contained in:
Sosthene 2024-05-22 23:37:54 +02:00
parent 30c3ec9673
commit f03abf15b9

View File

@ -372,9 +372,6 @@ fn handle_recover_transaction(
tweak_data: PublicKey, tweak_data: PublicKey,
fee_rate: u32, fee_rate: u32,
) -> anyhow::Result<CachedMessage> { ) -> anyhow::Result<CachedMessage> {
// We need to look for different case:
// 1) faucet
// This one is the simplest, we only care about finding the commitment.clone()
let op_return = tx.output.iter().find(|o| o.script_pubkey.is_op_return()); let op_return = tx.output.iter().find(|o| o.script_pubkey.is_op_return());
let commitment = if op_return.is_none() { let commitment = if op_return.is_none() {
vec![] vec![]
@ -382,24 +379,9 @@ fn handle_recover_transaction(
op_return.unwrap().script_pubkey.as_bytes()[2..].to_vec() op_return.unwrap().script_pubkey.as_bytes()[2..].to_vec()
}; };
let commitment_str = commitment.to_lower_hex_string(); let commitment_str = commitment.to_lower_hex_string();
{
let mut messages = lock_messages()?;
let pos = messages
.iter()
.position(|m| m.commitment.as_ref() == Some(&commitment_str));
if pos.is_some() {
let mut message = messages.swap_remove(pos.unwrap());
message.commited_in = updated.into_iter().next().map(|(outpoint, _)| outpoint);
message.status = CachedMessageStatus::FaucetComplete;
return Ok(message);
}
}
// If we got updates from a transaction, it means that it creates an output to us, spend an output we owned, or both // If we got updates from a transaction, it means that it creates an output to us, spend an output we owned, or both
// If we destroyed outputs it means we either notified others, or ask confirmation, or confirm // Basically a transaction that destroyed utxo is a transaction we sent.
// We probably creates outputs too in this case because of change
// If we only created outputs it means we are being notified
let utxo_destroyed: HashMap<&OutPoint, &OwnedOutput> = updated let utxo_destroyed: HashMap<&OutPoint, &OwnedOutput> = updated
.iter() .iter()
.filter(|(outpoint, output)| output.spend_status != OutputSpendStatus::Unspent) .filter(|(outpoint, output)| output.spend_status != OutputSpendStatus::Unspent)
@ -409,89 +391,107 @@ fn handle_recover_transaction(
.filter(|(outpoint, output)| output.spend_status == OutputSpendStatus::Unspent) .filter(|(outpoint, output)| output.spend_status == OutputSpendStatus::Unspent)
.collect(); .collect();
// 2) confirmation let mut messages = lock_messages()?;
// If the transaction spends one outpoint in `commited_in`, it means we are receiving a confirmation for a notification
// if we are receiver, then we must look for `confirmed_by`
// if we owned at least one input or no outputs, we can skip the check
if utxo_destroyed.is_empty() && !utxo_created.is_empty() {
for input in tx.input.iter() {
// Check for each input if it match a known commitment we made as a sender
// OR a confirmation for the receiver
let pos = lock_messages()?.iter().position(|m| {
m.commited_in == Some(input.previous_output)
|| m.confirmed_by == Some(input.previous_output)
});
if pos.is_some() {
let mut messages = lock_messages()?;
let message = messages.get_mut(pos.unwrap()).unwrap();
// If we are receiver, that's pretty much it, just set status to complete
if message.recipient == Some(sp_wallet.get_client().get_receiving_address()) {
debug_assert!(message.confirmed_by == Some(input.previous_output));
message.status = CachedMessageStatus::Complete;
return Ok(message.clone());
}
// sender needs to spent it back again to receiver // empty utxo_destroyed means we received this transaction
let (outpoint, output) = utxo_created.iter().next().unwrap(); if utxo_destroyed.is_empty() {
// We first check for faucet transactions
// If we are sender, then we must update the confirmed_by field if let Some(pos) = messages.iter().position(|m| {
message.confirmed_by = Some(**outpoint); if m.status == CachedMessageStatus::FaucetWaiting {
m.commitment.as_ref() == Some(&commitment_str)
// Caller must interpret this message as "spend confirmed_by outpoint to receiver"
return Ok(message.clone());
} else { } else {
// we are being notified false
let shared_point = }
shared_secret_point(&tweak_data, &sp_wallet.get_client().get_scan_key()); })
let shared_secret = AnkSharedSecret::new(shared_point); {
let message = messages.get_mut(pos).unwrap();
let mut messages = lock_messages()?; match message.status {
let cipher_pos = messages.iter().position(|m| { CachedMessageStatus::FaucetWaiting => {
if m.status != CachedMessageStatus::CipherWaitingTx { message.status = CachedMessageStatus::FaucetComplete;
return false; message.commited_in = utxo_created.into_iter().next().map(|(outpoint, _)| *outpoint);
} return Ok(message.clone());
m.try_decrypt_with_shared_secret(shared_secret.to_byte_array()) },
.is_ok() CachedMessageStatus::FaucetComplete => return Ok(message.clone()),
}); _ => ()
if cipher_pos.is_some() {
let message = messages.get_mut(cipher_pos.unwrap()).unwrap();
let (outpoint, output) = utxo_created.iter().next().unwrap();
message.commited_in = Some(**outpoint);
message.shared_secret =
Some(shared_secret.to_byte_array().to_lower_hex_string());
message.commitment = Some(commitment.to_lower_hex_string());
let plaintext = message
.try_decrypt_with_shared_secret(shared_secret.to_byte_array())
.unwrap();
let unknown_msg: UnknownMessage = serde_json::from_slice(&plaintext)?;
message.plaintext = Some(unknown_msg.message);
message.sender = Some(unknown_msg.sender);
message.recipient = Some(sp_wallet.get_client().get_receiving_address());
return Ok(message.clone())
} else {
// store it and wait for the message
let mut new_msg = CachedMessage::new();
debug!("{:?}", utxo_created);
let (outpoint, output) = utxo_created.into_iter().next().expect("utxo_created shouldn't be empty");
new_msg.commited_in = Some(outpoint.clone());
new_msg.commitment = Some(commitment.to_lower_hex_string());
new_msg.recipient = Some(sp_wallet.get_client().get_receiving_address());
new_msg.shared_secret =
Some(shared_secret.to_byte_array().to_lower_hex_string());
new_msg.status = CachedMessageStatus::TxWaitingCipher;
lock_messages()?.push(new_msg.clone());
debug!("returning {:?}", new_msg);
return Ok(new_msg.clone());
}
} }
} }
unreachable!("Transaction with no inputs");
// we inspect inputs looking for links with previous tx
for input in tx.input.iter() {
if let Some(pos) = messages.iter().position(|m| {
m.confirmed_by == Some(input.previous_output)
})
{
let message = messages.get_mut(pos).unwrap();
// If we are receiver, that's pretty much it, just set status to complete
message.status = CachedMessageStatus::Complete;
return Ok(message.clone());
} else if let Some(pos) = messages.iter().position(|m| {
m.commited_in == Some(input.previous_output)
})
{
// sender needs to spent it back again to receiver
let (outpoint, output) = utxo_created.into_iter().next().unwrap();
let message = messages.get_mut(pos).unwrap();
message.confirmed_by = Some(outpoint.clone());
message.status = CachedMessageStatus::MustSpendConfirmation;
// Caller must interpret this message as "do spend confirmed_by outpoint to receiver"
return Ok(message.clone());
}
}
// if we've found nothing we are being notified
let shared_point =
shared_secret_point(&tweak_data, &sp_wallet.get_client().get_scan_key());
let shared_secret = AnkSharedSecret::new(shared_point);
if let Some(cipher_pos) = messages.iter().position(|m| {
if m.status != CachedMessageStatus::CipherWaitingTx {
return false;
}
m.try_decrypt_with_shared_secret(shared_secret.to_byte_array())
.is_ok()
})
{
let message = messages.get_mut(cipher_pos).unwrap();
let (outpoint, output) = utxo_created.into_iter().next().unwrap();
message.commited_in = Some(outpoint.clone());
message.shared_secret =
Some(shared_secret.to_byte_array().to_lower_hex_string());
message.commitment = Some(commitment_str);
let plaintext = message
.try_decrypt_with_shared_secret(shared_secret.to_byte_array())
.unwrap();
let unknown_msg: UnknownMessage = serde_json::from_slice(&plaintext)?;
message.plaintext = Some(unknown_msg.message);
message.sender = Some(unknown_msg.sender);
message.recipient = Some(sp_wallet.get_client().get_receiving_address());
message.status = CachedMessageStatus::ReceivedMustConfirm;
return Ok(message.clone())
} else {
// store it and wait for the message
let mut new_msg = CachedMessage::new();
let (outpoint, output) = utxo_created.into_iter().next().expect("utxo_created shouldn't be empty");
new_msg.commited_in = Some(outpoint.clone());
new_msg.commitment = Some(commitment.to_lower_hex_string());
new_msg.recipient = Some(sp_wallet.get_client().get_receiving_address());
new_msg.shared_secret =
Some(shared_secret.to_byte_array().to_lower_hex_string());
new_msg.status = CachedMessageStatus::TxWaitingCipher;
messages.push(new_msg.clone());
return Ok(new_msg.clone());
}
} else { } else {
// We are sender of a notification transaction // We are sender of a transaction
// We only need to return the message // We only need to return the message
if let Some(message) = lock_messages()?.iter() if let Some(message) = messages.iter()
.find(|m| { .find(|m| {
m.commitment.as_ref() == Some(&commitment_str) m.commitment.as_ref() == Some(&commitment_str)
}) })