[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,
|
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)
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user