diff --git a/crates/sp_client/src/api.rs b/crates/sp_client/src/api.rs index 1f54f16..a6f2a66 100644 --- a/crates/sp_client/src/api.rs +++ b/crates/sp_client/src/api.rs @@ -372,9 +372,6 @@ fn handle_recover_transaction( tweak_data: PublicKey, fee_rate: u32, ) -> anyhow::Result { - // 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) })