[bug fix] Working faucet + small fixes
This commit is contained in:
parent
0f7bc644c8
commit
a93aed0ede
@ -34,7 +34,7 @@ use wasm_bindgen::convert::FromWasmAbi;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use sdk_common::network::{
|
||||
self, AnkFlag, AnkNetworkMsg, FaucetMessage, NewTxMessage, UnknownMessage,
|
||||
self, AnkFlag, AnkNetworkMsg, CachedMessage, CachedMessageStatus, FaucetMessage, NewTxMessage, UnknownMessage
|
||||
};
|
||||
use sdk_common::silentpayments::{
|
||||
create_transaction, create_transaction_for_address_with_shared_secret,
|
||||
@ -371,10 +371,10 @@ fn handle_recover_transaction(
|
||||
sp_wallet: &mut SpWallet,
|
||||
tweak_data: PublicKey,
|
||||
fee_rate: u32,
|
||||
) -> anyhow::Result<NetworkMessage> {
|
||||
) -> anyhow::Result<CachedMessage> {
|
||||
// We need to look for different case:
|
||||
// 1) faucet
|
||||
// This one is the simplest, we only care about finding the commitment
|
||||
// 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,14 +382,18 @@ fn handle_recover_transaction(
|
||||
op_return.unwrap().script_pubkey.as_bytes()[2..].to_vec()
|
||||
};
|
||||
let commitment_str = commitment.to_lower_hex_string();
|
||||
let pos = lock_messages()?
|
||||
{
|
||||
let mut messages = lock_messages()?;
|
||||
let pos = messages
|
||||
.iter()
|
||||
.position(|m| m.commitment.as_ref() == Some(&commitment_str));
|
||||
|
||||
if pos.is_some() {
|
||||
let messages = lock_messages()?;
|
||||
let message = messages.get(pos.unwrap());
|
||||
return Ok(message.cloned().unwrap());
|
||||
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
|
||||
@ -423,7 +427,7 @@ fn handle_recover_transaction(
|
||||
// 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 = NetworkMessageStatus::Complete;
|
||||
message.status = CachedMessageStatus::Complete;
|
||||
return Ok(message.clone());
|
||||
}
|
||||
|
||||
@ -443,11 +447,11 @@ fn handle_recover_transaction(
|
||||
|
||||
let mut messages = lock_messages()?;
|
||||
let cipher_pos = messages.iter().position(|m| {
|
||||
if m.status != NetworkMessageStatus::CipherWaitingTx {
|
||||
if m.status != CachedMessageStatus::CipherWaitingTx {
|
||||
return false;
|
||||
}
|
||||
m.try_decrypt_with_shared_secret(shared_secret.to_byte_array())
|
||||
.is_some()
|
||||
.is_ok()
|
||||
});
|
||||
|
||||
if cipher_pos.is_some() {
|
||||
@ -468,15 +472,17 @@ fn handle_recover_transaction(
|
||||
return Ok(message.clone())
|
||||
} else {
|
||||
// store it and wait for the message
|
||||
let mut new_msg = NetworkMessage::default();
|
||||
let (outpoint, output) = utxo_created.iter().next().unwrap();
|
||||
new_msg.commited_in = Some(**outpoint);
|
||||
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 = NetworkMessageStatus::TxWaitingCipher;
|
||||
new_msg.status = CachedMessageStatus::TxWaitingCipher;
|
||||
lock_messages()?.push(new_msg.clone());
|
||||
debug!("returning {:?}", new_msg);
|
||||
return Ok(new_msg.clone());
|
||||
}
|
||||
}
|
||||
@ -497,14 +503,14 @@ fn handle_recover_transaction(
|
||||
}
|
||||
}
|
||||
|
||||
/// If the transaction has anything to do with us, we create/update the relevant `NetworkMessage`
|
||||
/// If the transaction has anything to do with us, we create/update the relevant `CachedMessage`
|
||||
/// and return it to caller for persistent storage
|
||||
fn process_transaction(
|
||||
tx_hex: String,
|
||||
blockheight: u32,
|
||||
tweak_data_hex: String,
|
||||
fee_rate: u32,
|
||||
) -> anyhow::Result<NetworkMessage> {
|
||||
) -> anyhow::Result<CachedMessage> {
|
||||
let tx = deserialize::<Transaction>(&Vec::from_hex(&tx_hex)?)?;
|
||||
|
||||
// check that we don't already have scanned the tx
|
||||
@ -545,16 +551,8 @@ fn process_transaction(
|
||||
Err(anyhow::Error::msg("No output found"))
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct parseNetworkMsgReturn {
|
||||
topic: String,
|
||||
message: NetworkMessage,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn parse_network_msg(raw: String, fee_rate: u32) -> ApiResult<parseNetworkMsgReturn> {
|
||||
pub fn parse_network_msg(raw: String, fee_rate: u32) -> ApiResult<CachedMessage> {
|
||||
if let Ok(ank_msg) = serde_json::from_str::<AnkNetworkMsg>(&raw) {
|
||||
match ank_msg.flag {
|
||||
AnkFlag::NewTx => {
|
||||
@ -570,29 +568,23 @@ pub fn parse_network_msg(raw: String, fee_rate: u32) -> ApiResult<parseNetworkMs
|
||||
tx_message.tweak_data.unwrap(),
|
||||
fee_rate,
|
||||
)?;
|
||||
debug!("{:?}", network_msg);
|
||||
return Ok(parseNetworkMsgReturn {
|
||||
topic: ank_msg.flag.as_str().to_owned(),
|
||||
message: network_msg,
|
||||
});
|
||||
return Ok(network_msg);
|
||||
}
|
||||
AnkFlag::Faucet => unimplemented!(),
|
||||
AnkFlag::Error => {
|
||||
let error_msg = NetworkMessage::new_error(ank_msg.content);
|
||||
return Ok(parseNetworkMsgReturn {
|
||||
topic: AnkFlag::Error.as_str().to_owned(),
|
||||
message: error_msg,
|
||||
})
|
||||
let error_msg = CachedMessage::new_error(ank_msg.content);
|
||||
return Ok(error_msg)
|
||||
}
|
||||
AnkFlag::Unknown => {
|
||||
// let's try to decrypt with keys we found in transactions but haven't used yet
|
||||
let mut messages = lock_messages()?;
|
||||
let cipher = Vec::from_hex(&ank_msg.content)?;
|
||||
let cipher = Vec::from_hex(&ank_msg.content.trim_matches('\"'))?;
|
||||
let cipher_pos = messages.iter().position(|m| {
|
||||
if m.status != NetworkMessageStatus::TxWaitingCipher {
|
||||
debug!("Trying message: {:?}", m);
|
||||
if m.status != CachedMessageStatus::TxWaitingCipher {
|
||||
return false;
|
||||
}
|
||||
m.try_decrypt_cipher(cipher.clone()).is_some()
|
||||
m.try_decrypt_cipher(cipher.clone()).is_ok()
|
||||
});
|
||||
if cipher_pos.is_some() {
|
||||
let mut message = messages.get_mut(cipher_pos.unwrap()).unwrap();
|
||||
@ -601,14 +593,11 @@ pub fn parse_network_msg(raw: String, fee_rate: u32) -> ApiResult<parseNetworkMs
|
||||
message.plaintext = Some(unknown_msg.message);
|
||||
message.sender = Some(unknown_msg.sender);
|
||||
message.ciphertext = Some(ank_msg.content);
|
||||
return Ok(parseNetworkMsgReturn {
|
||||
topic: AnkFlag::Unknown.as_str().to_owned(),
|
||||
message: message.clone(),
|
||||
});
|
||||
return Ok(message.clone());
|
||||
} else {
|
||||
// let's keep it in case we receive the transaction later
|
||||
let mut new_msg = NetworkMessage::default();
|
||||
new_msg.status = NetworkMessageStatus::CipherWaitingTx;
|
||||
let mut new_msg = CachedMessage::default();
|
||||
new_msg.status = CachedMessageStatus::CipherWaitingTx;
|
||||
new_msg.ciphertext = Some(ank_msg.content);
|
||||
messages.push(new_msg);
|
||||
return Err(ApiError {
|
||||
@ -677,18 +666,61 @@ pub fn is_tx_owned_by_user(pre_id: String, tx: String) -> ApiResult<bool> {
|
||||
#[derive(Tsify, Serialize, Deserialize)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct createNotificationTransactionReturn {
|
||||
pub struct createTransactionReturn {
|
||||
pub txid: String,
|
||||
pub transaction: String,
|
||||
pub new_network_msg: NetworkMessage,
|
||||
pub new_network_msg: CachedMessage,
|
||||
}
|
||||
|
||||
/// This is what we call to answer a confirmation as a sender
|
||||
#[wasm_bindgen]
|
||||
pub fn answer_confirmation_transaction(
|
||||
message: CachedMessage,
|
||||
fee_rate: u32,
|
||||
) -> ApiResult<createTransactionReturn> {
|
||||
if message.recipient.is_none() || message.confirmed_by.is_none() {
|
||||
return Err(ApiError { message: "Invalid network message".to_owned() });
|
||||
}
|
||||
|
||||
let sp_address: SilentPaymentAddress = message.recipient.as_ref().unwrap().as_str().try_into()?;
|
||||
|
||||
let connected_user = lock_connected_user()?;
|
||||
|
||||
let sp_wallet: &SpWallet;
|
||||
if sp_address.is_testnet() {
|
||||
sp_wallet = connected_user.try_get_recover()?;
|
||||
} else {
|
||||
sp_wallet = connected_user.try_get_main()?;
|
||||
}
|
||||
|
||||
let recipient = Recipient {
|
||||
address: sp_address.into(),
|
||||
amount: Amount::from_sat(0), // we'll set amount to what's available in the confirmed_by output we don't want change
|
||||
nb_outputs: 1,
|
||||
};
|
||||
|
||||
let signed_psbt = create_transaction_spend_outpoint(
|
||||
&message.confirmed_by.unwrap(),
|
||||
sp_wallet,
|
||||
recipient,
|
||||
Amount::from_sat(fee_rate.into())
|
||||
)?;
|
||||
|
||||
let final_tx = signed_psbt.extract_tx()?;
|
||||
|
||||
Ok(createTransactionReturn {
|
||||
txid: final_tx.txid().to_string(),
|
||||
transaction: serialize(&final_tx).to_lower_hex_string(),
|
||||
new_network_msg: message
|
||||
})
|
||||
}
|
||||
|
||||
/// This is what we call to confirm as a receiver
|
||||
#[wasm_bindgen]
|
||||
pub fn create_confirmation_transaction(
|
||||
message: NetworkMessage,
|
||||
message: CachedMessage,
|
||||
fee_rate: u32,
|
||||
) -> ApiResult<createNotificationTransactionReturn> {
|
||||
) -> ApiResult<createTransactionReturn> {
|
||||
if message.sender.is_none() || message.confirmed_by.is_none() {
|
||||
return Err(ApiError { message: "Invalid network message".to_owned() });
|
||||
}
|
||||
@ -706,7 +738,7 @@ pub fn create_confirmation_transaction(
|
||||
|
||||
let recipient = Recipient {
|
||||
address: sp_address.into(),
|
||||
amount: Amount::from_sat(1200),
|
||||
amount: Amount::from_sat(0),
|
||||
nb_outputs: 1,
|
||||
};
|
||||
|
||||
@ -719,7 +751,7 @@ pub fn create_confirmation_transaction(
|
||||
|
||||
let final_tx = signed_psbt.extract_tx()?;
|
||||
|
||||
Ok(createNotificationTransactionReturn {
|
||||
Ok(createTransactionReturn {
|
||||
txid: final_tx.txid().to_string(),
|
||||
transaction: serialize(&final_tx).to_lower_hex_string(),
|
||||
new_network_msg: message
|
||||
@ -731,7 +763,7 @@ pub fn create_notification_transaction(
|
||||
address: String,
|
||||
commitment: Option<String>,
|
||||
fee_rate: u32,
|
||||
) -> ApiResult<createNotificationTransactionReturn> {
|
||||
) -> ApiResult<createTransactionReturn> {
|
||||
let sp_address: SilentPaymentAddress = address.as_str().try_into()?;
|
||||
|
||||
let connected_user = lock_connected_user()?;
|
||||
@ -782,16 +814,17 @@ pub fn create_notification_transaction(
|
||||
let recipients_vouts = sp_address2vouts.get::<String>(&address).expect("recipients didn't change").as_slice();
|
||||
// for now let's just take the smallest vout that belongs to the recipient
|
||||
let final_tx = psbt.extract_tx()?;
|
||||
let mut new_msg = NetworkMessage::default();
|
||||
let mut new_msg = CachedMessage::default();
|
||||
new_msg.commitment = commitment;
|
||||
new_msg.commited_in = Some(OutPoint { txid: final_tx.txid(), vout: recipients_vouts[0] as u32 });
|
||||
new_msg.shared_secret = Some(shared_secret.to_byte_array().to_lower_hex_string());
|
||||
new_msg.recipient = Some(address);
|
||||
new_msg.sender = Some(sp_wallet.get_client().get_receiving_address());
|
||||
new_msg.status = CachedMessageStatus::SentWaitingConfirmation;
|
||||
// plaintext and ciphertext to be added later when sending the encrypted message
|
||||
lock_messages()?.push(new_msg.clone());
|
||||
|
||||
Ok(createNotificationTransactionReturn {
|
||||
Ok(createTransactionReturn {
|
||||
txid: final_tx.txid().to_string(),
|
||||
transaction: serialize(&final_tx).to_lower_hex_string(),
|
||||
new_network_msg: new_msg,
|
||||
@ -872,8 +905,9 @@ pub fn create_faucet_msg() -> ApiResult<FaucetMessage> {
|
||||
let sp_address = user.try_get_recover()?.get_client().get_receiving_address();
|
||||
let faucet_msg = FaucetMessage::new(sp_address);
|
||||
// we write the commitment in a networkmessage so that we can keep track
|
||||
let mut network_msg = NetworkMessage::default();
|
||||
let mut network_msg = CachedMessage::new();
|
||||
network_msg.commitment = Some(faucet_msg.commitment.clone());
|
||||
network_msg.status = CachedMessageStatus::FaucetWaiting;
|
||||
lock_messages()?.push(network_msg);
|
||||
Ok(faucet_msg)
|
||||
}
|
||||
@ -885,110 +919,3 @@ pub fn create_commitment(payload_to_hash: String) -> String {
|
||||
let hash = sha256::Hash::from_engine(engine);
|
||||
hash.to_byte_array().to_lower_hex_string()
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Tsify, Clone)]
|
||||
pub enum NetworkMessageStatus {
|
||||
NoStatus, // Default
|
||||
CipherWaitingTx,
|
||||
TxWaitingCipher,
|
||||
SentWaitingConfirmation,
|
||||
MustSpentConfirmation,
|
||||
Complete,
|
||||
}
|
||||
|
||||
impl Default for NetworkMessageStatus {
|
||||
fn default() -> Self {
|
||||
Self::NoStatus
|
||||
}
|
||||
}
|
||||
|
||||
/// Unique struct for both 4nk messages and notification/key exchange, both rust and ts
|
||||
/// 1. Faucet: commited_in with nothing else, status is NoStatus
|
||||
/// 2. notification:
|
||||
/// 1. sender: ciphertext, plaintext, commited_in, sender, recipient, shared_secret, key
|
||||
/// 2. receiver (without tx): ciphertext
|
||||
/// 3. receiver (tx without msg): commited_in, commitment, recipient, shared_secret
|
||||
/// 4. receiver (receive tx after msg): plaintext, key, sender, commited_in, commitment, recipient, shared_secret
|
||||
/// 5. receiver (msg after tx): ciphertext, key, plaintext, sender
|
||||
/// 3. confirmation:
|
||||
/// 1. receiver (spend the smallest vout that pays him in the first tx): confirmed_by
|
||||
/// 2. sender (detect a transaction that pays him and spend commited_by): confirmed_by
|
||||
/// 3. sender toggle status to complete when it spent confirmed_by, receiver when it detects the confirmed_by is spent
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Tsify, Clone)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct NetworkMessage {
|
||||
pub id: u32,
|
||||
pub status: NetworkMessageStatus,
|
||||
pub ciphertext: Option<String>, // When we receive message we can't decrypt we only have this and commited_in_tx
|
||||
pub plaintext: Option<String>, // Never None when message sent
|
||||
pub commited_in: Option<OutPoint>,
|
||||
pub commitment: Option<String>, // content of the op_return
|
||||
pub sender: Option<String>, // Never None when message sent
|
||||
pub recipient: Option<String>, // Never None when message sent
|
||||
pub shared_secret: Option<String>, // Never None when message sent
|
||||
pub key: Option<String>, // Never None when message sent
|
||||
pub confirmed_by: Option<OutPoint>, // If this None, Sender keeps sending
|
||||
pub timestamp: u64,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
impl NetworkMessage {
|
||||
pub fn new() -> Self {
|
||||
let mut new = NetworkMessage::default();
|
||||
let mut buf = [0u8;4];
|
||||
thread_rng().fill_bytes(&mut buf);
|
||||
new.id = u32::from_be_bytes(buf);
|
||||
new
|
||||
}
|
||||
|
||||
pub fn new_error(error: String) -> Self {
|
||||
let mut new = NetworkMessage::default();
|
||||
new.error = Some(error);
|
||||
new
|
||||
}
|
||||
|
||||
pub fn try_decrypt_cipher(&self, cipher: Vec<u8>) -> Option<Vec<u8>> {
|
||||
if self.ciphertext.is_some() || self.shared_secret.is_none() {
|
||||
log::error!(
|
||||
"Can't try decrypt this message, there's already a ciphertext or no shared secret"
|
||||
);
|
||||
return None;
|
||||
}
|
||||
let mut shared_secret = [0u8; 32];
|
||||
shared_secret
|
||||
.copy_from_slice(&Vec::from_hex(self.shared_secret.as_ref().unwrap()).unwrap());
|
||||
let aes_decrypt = Aes256Decryption::new(Purpose::Arbitrary, cipher, shared_secret);
|
||||
|
||||
if aes_decrypt.is_err() {
|
||||
log::error!("Failed to create decrypt object");
|
||||
return None;
|
||||
}
|
||||
|
||||
aes_decrypt.unwrap().decrypt_with_key().ok()
|
||||
}
|
||||
|
||||
pub fn try_decrypt_with_shared_secret(&self, shared_secret: [u8; 32]) -> Option<Vec<u8>> {
|
||||
if self.ciphertext.is_none() || self.shared_secret.is_some() {
|
||||
log::error!(
|
||||
"Can't try decrypt this message, ciphertext is none or shared_secret already found"
|
||||
);
|
||||
return None;
|
||||
}
|
||||
let cipher_bin = Vec::from_hex(self.ciphertext.as_ref().unwrap());
|
||||
if cipher_bin.is_err() {
|
||||
let error = cipher_bin.unwrap_err();
|
||||
log::error!("Invalid hex in ciphertext: {}", error.to_string());
|
||||
return None;
|
||||
}
|
||||
let aes_decrypt =
|
||||
Aes256Decryption::new(Purpose::Arbitrary, cipher_bin.unwrap(), shared_secret);
|
||||
|
||||
if aes_decrypt.is_err() {
|
||||
log::error!("Failed to create decrypt object");
|
||||
return None;
|
||||
}
|
||||
|
||||
aes_decrypt.unwrap().decrypt_with_key().ok()
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
#![allow(warnings)]
|
||||
use anyhow::Error;
|
||||
use api::NetworkMessage;
|
||||
use sdk_common::crypto::AnkSharedSecret;
|
||||
use sdk_common::network::CachedMessage;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sp_client::bitcoin::{OutPoint, Txid};
|
||||
use sp_client::silentpayments::sending::SilentPaymentAddress;
|
||||
@ -17,10 +17,10 @@ mod peers;
|
||||
mod process;
|
||||
mod user;
|
||||
|
||||
pub static NETWORKMESSAGES: OnceLock<Mutex<Vec<NetworkMessage>>> = OnceLock::new();
|
||||
pub static CACHEDMESSAGES: OnceLock<Mutex<Vec<CachedMessage>>> = OnceLock::new();
|
||||
|
||||
pub fn lock_messages() -> Result<MutexGuard<'static, Vec<NetworkMessage>>, Error> {
|
||||
NETWORKMESSAGES
|
||||
pub fn lock_messages() -> Result<MutexGuard<'static, Vec<CachedMessage>>, Error> {
|
||||
CACHEDMESSAGES
|
||||
.get_or_init(|| Mutex::new(vec![]))
|
||||
.lock_anyhow()
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user