[bug] fix duplicate transactions issue in handle_recover_transaction + refactor
This commit is contained in:
parent
30c3ec9673
commit
f03abf15b9
@ -372,9 +372,6 @@ fn handle_recover_transaction(
|
||||
tweak_data: PublicKey,
|
||||
fee_rate: u32,
|
||||
) -> 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 commitment = if op_return.is_none() {
|
||||
vec![]
|
||||
@ -382,24 +379,9 @@ fn handle_recover_transaction(
|
||||
op_return.unwrap().script_pubkey.as_bytes()[2..].to_vec()
|
||||
};
|
||||
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 destroyed outputs it means we either notified others, or ask confirmation, or confirm
|
||||
// We probably creates outputs too in this case because of change
|
||||
// If we only created outputs it means we are being notified
|
||||
// Basically a transaction that destroyed utxo is a transaction we sent.
|
||||
let utxo_destroyed: HashMap<&OutPoint, &OwnedOutput> = updated
|
||||
.iter()
|
||||
.filter(|(outpoint, output)| output.spend_status != OutputSpendStatus::Unspent)
|
||||
@ -409,89 +391,107 @@ fn handle_recover_transaction(
|
||||
.filter(|(outpoint, output)| output.spend_status == OutputSpendStatus::Unspent)
|
||||
.collect();
|
||||
|
||||
// 2) confirmation
|
||||
// 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());
|
||||
}
|
||||
let mut messages = lock_messages()?;
|
||||
|
||||
// sender needs to spent it back again to receiver
|
||||
let (outpoint, output) = utxo_created.iter().next().unwrap();
|
||||
|
||||
// If we are sender, then we must update the confirmed_by field
|
||||
message.confirmed_by = Some(**outpoint);
|
||||
|
||||
// Caller must interpret this message as "spend confirmed_by outpoint to receiver"
|
||||
return Ok(message.clone());
|
||||
// empty utxo_destroyed means we received this transaction
|
||||
if utxo_destroyed.is_empty() {
|
||||
// We first check for faucet transactions
|
||||
if let Some(pos) = messages.iter().position(|m| {
|
||||
if m.status == CachedMessageStatus::FaucetWaiting {
|
||||
m.commitment.as_ref() == Some(&commitment_str)
|
||||
} else {
|
||||
// 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);
|
||||
|
||||
let mut messages = lock_messages()?;
|
||||
let 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()
|
||||
});
|
||||
|
||||
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());
|
||||
}
|
||||
false
|
||||
}
|
||||
})
|
||||
{
|
||||
let message = messages.get_mut(pos).unwrap();
|
||||
match message.status {
|
||||
CachedMessageStatus::FaucetWaiting => {
|
||||
message.status = CachedMessageStatus::FaucetComplete;
|
||||
message.commited_in = utxo_created.into_iter().next().map(|(outpoint, _)| *outpoint);
|
||||
return Ok(message.clone());
|
||||
},
|
||||
CachedMessageStatus::FaucetComplete => return Ok(message.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 {
|
||||
// We are sender of a notification transaction
|
||||
// We are sender of a transaction
|
||||
// We only need to return the message
|
||||
if let Some(message) = lock_messages()?.iter()
|
||||
if let Some(message) = messages.iter()
|
||||
.find(|m| {
|
||||
m.commitment.as_ref() == Some(&commitment_str)
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user