mirror of
https://github.com/Qortal/piratewallet-light-cli.git
synced 2025-07-29 11:21:26 +00:00
Added "redeemp2sh" command - a very early attempt to redeem a P2SH to a z address
Warning: this is unsafe - do not use with real funds unless you have read the code in detail and know exactly what it is doing. Many safety checks and features (such as paying change) are disabled. This is a work in progress, and very experimental.
This commit is contained in:
@@ -48,33 +48,33 @@ reqwest = { version = "0.10.8", features = ["blocking", "json"] }
|
||||
|
||||
[dependencies.bellman]
|
||||
git = "https://github.com/CalDescent1/librustzcash.git"
|
||||
rev = "3bc31b9cceaef79865f3a4c85e31f76b9755e15c"
|
||||
rev = "5a4fd01f351259694c31ca161842dfd7078ef2f1"
|
||||
default-features = false
|
||||
features = ["groth16", "multicore"]
|
||||
|
||||
[dependencies.pairing]
|
||||
git = "https://github.com/CalDescent1/librustzcash.git"
|
||||
rev = "3bc31b9cceaef79865f3a4c85e31f76b9755e15c"
|
||||
rev = "5a4fd01f351259694c31ca161842dfd7078ef2f1"
|
||||
|
||||
[dependencies.zcash_client_backend]
|
||||
git = "https://github.com/CalDescent1/librustzcash.git"
|
||||
rev = "3bc31b9cceaef79865f3a4c85e31f76b9755e15c"
|
||||
rev = "5a4fd01f351259694c31ca161842dfd7078ef2f1"
|
||||
default-features = false
|
||||
|
||||
[dependencies.zcash_primitives]
|
||||
git = "https://github.com/CalDescent1/librustzcash.git"
|
||||
rev = "3bc31b9cceaef79865f3a4c85e31f76b9755e15c"
|
||||
rev = "5a4fd01f351259694c31ca161842dfd7078ef2f1"
|
||||
default-features = false
|
||||
features = ["transparent-inputs"]
|
||||
|
||||
[dependencies.zcash_proofs]
|
||||
git = "https://github.com/CalDescent1/librustzcash.git"
|
||||
rev = "3bc31b9cceaef79865f3a4c85e31f76b9755e15c"
|
||||
rev = "5a4fd01f351259694c31ca161842dfd7078ef2f1"
|
||||
default-features = false
|
||||
|
||||
[dependencies.ff]
|
||||
git = "https://github.com/CalDescent1/librustzcash.git"
|
||||
rev = "3bc31b9cceaef79865f3a4c85e31f76b9755e15c"
|
||||
rev = "5a4fd01f351259694c31ca161842dfd7078ef2f1"
|
||||
features = ["ff_derive"]
|
||||
|
||||
[build-dependencies]
|
||||
|
@@ -668,6 +668,163 @@ impl Command for SendP2shCommand {
|
||||
}
|
||||
}
|
||||
|
||||
struct RedeemP2shCommand {}
|
||||
impl Command for RedeemP2shCommand {
|
||||
fn help(&self) -> String {
|
||||
let mut h = vec![];
|
||||
h.push("Redeem ARRR from an HTLC");
|
||||
h.push("Usage:");
|
||||
h.push("send '{'input': <address>, 'output': [{'address': <address>, 'amount': <amount in zatoshis>, 'memo': <optional memo>, 'script': <redeem script>, 'txid': <funding txid>, 'secret': <secret>, 'privkey': <private key>}, ...]}");
|
||||
h.push("");
|
||||
h.push("NOTE: The fee required to send this transaction (currently ZEC 0.0001) is additionally detected from your balance.");
|
||||
h.push("Example:");
|
||||
h.push("send '{\"input\":\"ztestsapling1x65nq4dgp0qfywgxcwk9n0fvm4fysmapgr2q00p85ju252h6l7mmxu2jg9cqqhtvzd69jwhgv8d\", \"output\": [{ \"address\": \"ztestsapling1x65nq4dgp0qfywgxcwk9n0fvm4fysmapgr2q00p85ju252h6l7mmxu2jg9cqqhtvzd69jwhgv8d\", \"amount\": 200000, \"memo\": \"Hello from the command line\", \"script\": \"acbdef\", \"secret\": \"acbdef\", \"privkey\": \"acbdef\"}]}'");
|
||||
h.push("");
|
||||
h.join("\n")
|
||||
}
|
||||
|
||||
fn short_help(&self) -> String {
|
||||
"Redeem ARRR from a P2SH address, using redeem script, secret, and private key".to_string()
|
||||
}
|
||||
|
||||
fn exec(&self, args: &[&str], lightclient: &LightClient) -> String {
|
||||
// Parse the args.
|
||||
// A single argument in the form of a JSON string that is "{input: address, output: [{address: address, value: value, memo: memo},...], fee: fee}"
|
||||
|
||||
// 1 - Destination address. T or Z address
|
||||
if args.len() != 1 {
|
||||
return self.help();
|
||||
}
|
||||
|
||||
use std::convert::TryInto;
|
||||
use zcash_primitives::transaction::components::amount::DEFAULT_FEE;
|
||||
|
||||
// Check for a single argument that can be parsed as JSON
|
||||
let arg_list = args[0];
|
||||
|
||||
let json_args = match json::parse(&arg_list) {
|
||||
Ok(j) => j,
|
||||
Err(e) => {
|
||||
let es = format!("Couldn't understand JSON: {}", e);
|
||||
return format!("{}\n{}", es, self.help());
|
||||
}
|
||||
};
|
||||
|
||||
//Check for a fee key and convert to u64
|
||||
let fee: u64 = if json_args.has_key("fee") {
|
||||
match json_args["fee"].as_u64() {
|
||||
Some(f) => f.clone(),
|
||||
None => DEFAULT_FEE.try_into().unwrap()
|
||||
}
|
||||
} else {
|
||||
DEFAULT_FEE.try_into().unwrap()
|
||||
};
|
||||
|
||||
//Check for a input key and convert to str
|
||||
let from = if json_args.has_key("input") {
|
||||
json_args["input"].as_str().unwrap().clone()
|
||||
} else {
|
||||
return format!("Error: {}\n{}", "Need input address", self.help());
|
||||
};
|
||||
|
||||
//Check for output key
|
||||
let json_tos = if json_args.has_key("output") {
|
||||
&json_args["output"]
|
||||
} else {
|
||||
return format!("Error: {}\n{}", "Need output address", self.help());
|
||||
};
|
||||
|
||||
//Check output is in the form of an array
|
||||
if !json_tos.is_array() {
|
||||
return format!("Couldn't parse argument as array\n{}", self.help());
|
||||
}
|
||||
|
||||
|
||||
//Check for output script and convert to a string
|
||||
let script58 = if json_args.has_key("script") {
|
||||
json_args["script"].as_str().unwrap().to_string().clone()
|
||||
} else {
|
||||
return format!("Error: {}\n{}", "Need script", self.help());
|
||||
};
|
||||
|
||||
// Decode base58 encoded string
|
||||
let script_vec = script58.from_base58().unwrap();
|
||||
let script_bytes = &script_vec[..];
|
||||
|
||||
|
||||
//Check for funding txid and convert to a string
|
||||
let txid58 = if json_args.has_key("txid") {
|
||||
json_args["txid"].as_str().unwrap().to_string().clone()
|
||||
} else {
|
||||
return format!("Error: {}\n{}", "Need funding txid", self.help());
|
||||
};
|
||||
|
||||
// Decode base58 encoded string
|
||||
let txid_vec = txid58.from_base58().unwrap();
|
||||
let txid_bytes = &txid_vec[..];
|
||||
|
||||
|
||||
//Check for secret and convert to a string
|
||||
let secret58 = if json_args.has_key("secret") {
|
||||
json_args["secret"].as_str().unwrap().to_string().clone()
|
||||
} else {
|
||||
return format!("Error: {}\n{}", "Need secret", self.help());
|
||||
};
|
||||
|
||||
// Decode base58 encoded string
|
||||
let secret_vec = secret58.from_base58().unwrap();
|
||||
let secret_bytes = &secret_vec[..];
|
||||
|
||||
|
||||
//Check for privkey and convert to a string
|
||||
let privkey58 = if json_args.has_key("privkey") {
|
||||
json_args["privkey"].as_str().unwrap().to_string().clone()
|
||||
} else {
|
||||
return format!("Error: {}\n{}", "Need privkey", self.help());
|
||||
};
|
||||
|
||||
// Decode base58 encoded string
|
||||
let privkey_vec = privkey58.from_base58().unwrap();
|
||||
let privkey_bytes = &privkey_vec[..];
|
||||
|
||||
|
||||
//Check array for manadantory address and amount keys
|
||||
let maybe_send_args = json_tos.members().map( |j| {
|
||||
if !j.has_key("address") || !j.has_key("amount") {
|
||||
Err(format!("Need 'address' and 'amount'\n"))
|
||||
} else {
|
||||
let amount = match j["amount"].as_str() {
|
||||
Some("entire-verified-zbalance") => lightclient.wallet.read().unwrap().verified_zbalance(None).checked_sub(fee),
|
||||
_ => Some(j["amount"].as_u64().unwrap())
|
||||
};
|
||||
|
||||
match amount {
|
||||
Some(amt) => Ok((j["address"].as_str().unwrap().to_string().clone(), amt, j["memo"].as_str().map(|s| s.to_string().clone()))),
|
||||
None => Err(format!("Not enough in wallet to pay transaction fee"))
|
||||
}
|
||||
}
|
||||
}).collect::<Result<Vec<(String, u64, Option<String>)>, String>>();
|
||||
|
||||
let send_args = match maybe_send_args {
|
||||
Ok(a) => a.clone(),
|
||||
Err(s) => { return format!("Error: {}\n{}", s, self.help()); }
|
||||
};
|
||||
|
||||
|
||||
match lightclient.do_sync(true) {
|
||||
Ok(_) => {
|
||||
// Convert to the right format. String -> &str.
|
||||
let tos = send_args.iter().map(|(a, v, m)| (a.as_str(), *v, m.clone()) ).collect::<Vec<_>>();
|
||||
match lightclient.do_redeem_p2sh(from, tos, &fee, script_bytes, txid_bytes, secret_bytes, privkey_bytes) {
|
||||
Ok(txid) => { object!{ "txid" => txid } },
|
||||
Err(e) => { object!{ "error" => e } }
|
||||
}.pretty(2)
|
||||
},
|
||||
Err(e) => e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SaveCommand {}
|
||||
impl Command for SaveCommand {
|
||||
fn help(&self) -> String {
|
||||
@@ -980,6 +1137,7 @@ pub fn get_commands() -> Box<HashMap<String, Box<dyn Command>>> {
|
||||
map.insert("info".to_string(), Box::new(InfoCommand{}));
|
||||
map.insert("send".to_string(), Box::new(SendCommand{}));
|
||||
map.insert("sendp2sh".to_string(), Box::new(SendP2shCommand{}));
|
||||
map.insert("redeemp2sh".to_string(), Box::new(RedeemP2shCommand{}));
|
||||
map.insert("save".to_string(), Box::new(SaveCommand{}));
|
||||
map.insert("quit".to_string(), Box::new(QuitCommand{}));
|
||||
map.insert("list".to_string(), Box::new(TransactionsCommand{}));
|
||||
|
@@ -1746,6 +1746,30 @@ impl LightClient {
|
||||
|
||||
result.map(|(txid, _)| txid)
|
||||
}
|
||||
|
||||
pub fn do_redeem_p2sh(&self, from: &str, addrs: Vec<(&str, u64, Option<String>)>, fee: &u64, script: &[u8], txid: &[u8], secret: &[u8], privkey: &[u8]) -> Result<String, String> {
|
||||
if !self.wallet.read().unwrap().is_unlocked_for_spending() {
|
||||
error!("Wallet is locked");
|
||||
return Err("Wallet is locked".to_string());
|
||||
}
|
||||
|
||||
info!("Creating transaction to redeem funds from P2SH");
|
||||
|
||||
let result = {
|
||||
let _lock = self.sync_lock.lock().unwrap();
|
||||
|
||||
self.wallet.write().unwrap().redeem_p2sh(
|
||||
u32::from_str_radix(&self.config.consensus_branch_id, 16).unwrap(),
|
||||
&self.sapling_spend, &self.sapling_output,
|
||||
from, addrs, script, txid, secret, privkey, fee,
|
||||
|txbytes| broadcast_raw_tx(&self.get_server_uri(), txbytes)
|
||||
)
|
||||
};
|
||||
|
||||
info!("Transaction Complete");
|
||||
|
||||
result.map(|(txid, _)| txid)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@@ -2,6 +2,7 @@ use std::time::{SystemTime, Duration};
|
||||
use std::io::{self, Read, Write};
|
||||
use std::cmp;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::convert::TryFrom;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::io::{Error, ErrorKind};
|
||||
|
||||
@@ -1565,7 +1566,7 @@ impl LightWallet {
|
||||
nd.spent = None;
|
||||
}
|
||||
|
||||
if nd.unconfirmed_spent.is_some() && txids_to_remove.contains(&nd.spent.unwrap()) {
|
||||
if nd.unconfirmed_spent.is_some() && txids_to_remove.contains(&nd.spent.unwrap()) { // TODO: fix bug when nd.spent already set to None
|
||||
nd.unconfirmed_spent = None;
|
||||
}
|
||||
})
|
||||
@@ -2773,6 +2774,340 @@ impl LightWallet {
|
||||
Ok((txid, raw_tx))
|
||||
}
|
||||
|
||||
pub fn redeem_p2sh<F> (
|
||||
&self,
|
||||
consensus_branch_id: u32,
|
||||
spend_params: &[u8],
|
||||
output_params: &[u8],
|
||||
from: &str,
|
||||
tos: Vec<(&str, u64, Option<String>)>,
|
||||
redeem_script_pubkey: &[u8],
|
||||
outpoint_txid: &[u8],
|
||||
secret: &[u8],
|
||||
privkey: &[u8],
|
||||
fee: &u64,
|
||||
broadcast_fn: F
|
||||
) -> Result<(String, Vec<u8>), String>
|
||||
where F: Fn(Box<[u8]>) -> Result<String, String>
|
||||
{
|
||||
if !self.unlocked {
|
||||
return Err("Cannot spend while wallet is locked".to_string());
|
||||
}
|
||||
|
||||
let start_time = now();
|
||||
if tos.len() == 0 {
|
||||
return Err("Need at least one destination address".to_string());
|
||||
}
|
||||
|
||||
let total_value = tos.iter().map(|to| to.1).sum::<u64>();
|
||||
println!(
|
||||
"0: Creating transaction sending {} zatoshis to {} addresses",
|
||||
total_value, tos.len()
|
||||
);
|
||||
|
||||
// Convert address (str) to RecepientAddress and value to Amount
|
||||
let recepients = tos.iter().map(|to| {
|
||||
let ra = match address::RecipientAddress::from_str(to.0,
|
||||
self.config.hrp_sapling_address(),
|
||||
self.config.base58_pubkey_address(),
|
||||
self.config.base58_script_address()) {
|
||||
Some(to) => to,
|
||||
None => {
|
||||
let e = format!("Invalid recipient address: '{}'", to.0);
|
||||
error!("{}", e);
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
let value = Amount::from_u64(to.1).unwrap();
|
||||
|
||||
Ok((ra, value, to.2.clone()))
|
||||
}).collect::<Result<Vec<(address::RecipientAddress, Amount, Option<String>)>, String>>()?;
|
||||
|
||||
// Target the next block, assuming we are up-to-date.
|
||||
let (height, anchor_offset) = match self.get_target_height_and_anchor_offset() {
|
||||
Some(res) => res,
|
||||
None => {
|
||||
let e = format!("Cannot send funds before scanning any blocks");
|
||||
error!("{}", e);
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
// Select notes to cover the target value
|
||||
println!("{}: Selecting notes", now() - start_time);
|
||||
let target_value = Amount::from_u64(total_value).unwrap() + Amount::from_u64(*fee).unwrap();
|
||||
|
||||
// Select the candidate notes that are eligible to be spent
|
||||
let mut candidate_notes: Vec<_> = self.txs.read().unwrap().iter()
|
||||
.map(|(txid, tx)| tx.notes.iter().map(move |note| (*txid, note)))
|
||||
.flatten()
|
||||
.filter_map(|(txid, note)| {
|
||||
// Filter out notes that are already spent
|
||||
if note.spent.is_some() || note.unconfirmed_spent.is_some() {
|
||||
None
|
||||
} else {
|
||||
// Get the spending key for the selected fvk, if we have it
|
||||
let extsk = self.zkeys.read().unwrap().iter()
|
||||
.find(|zk| zk.extfvk == note.extfvk)
|
||||
.and_then(|zk| zk.extsk.clone());
|
||||
//filter only on Notes with a matching from address
|
||||
if from == LightWallet::note_address(self.config.hrp_sapling_address(), note).unwrap() {
|
||||
SpendableNote::from(txid, note, anchor_offset, &extsk)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}).collect();
|
||||
|
||||
// Sort by highest value-notes first.
|
||||
candidate_notes.sort_by(|a, b| b.note.value.cmp(&a.note.value));
|
||||
|
||||
// Select the minimum number of notes required to satisfy the target value
|
||||
let notes: Vec<_> = candidate_notes.iter()
|
||||
.scan(0, |running_total, spendable| {
|
||||
let value = spendable.note.value;
|
||||
let ret = if *running_total < u64::from(target_value) {
|
||||
Some(spendable)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
*running_total = *running_total + value;
|
||||
ret
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut builder = Builder::new(height);
|
||||
|
||||
//set fre
|
||||
builder.set_fee(Amount::from_u64(*fee).unwrap());
|
||||
|
||||
|
||||
println!("{}: Adding P2SH transaction as transparent input", now() - start_time);
|
||||
|
||||
// Add P2SH transaction as transparent input, including secret and redeem script
|
||||
|
||||
let outpoint: OutPoint = OutPoint {
|
||||
hash: <[u8; 32]>::try_from(outpoint_txid).unwrap(),
|
||||
n: 0
|
||||
};
|
||||
|
||||
let coin = TxOut {
|
||||
value: Amount::from_u64(total_value).unwrap(),
|
||||
script_pubkey: Script { 0: redeem_script_pubkey.to_vec() },
|
||||
};
|
||||
|
||||
let sk = SecretKey::from_slice(privkey).unwrap();
|
||||
|
||||
if let Err(e) = builder.add_transparent_input_with_secret(sk, outpoint.clone(), coin.clone(), secret.to_vec(), redeem_script_pubkey.to_vec()
|
||||
) {
|
||||
let e = format!("Error adding transparent input: {:?}", e);
|
||||
error!("{}", e);
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
|
||||
// "Sufficient value check" disabled since the uxto input details are supplied manually (and are not in this wallet).
|
||||
// It is down to the caller to verify the amount; we would generally expect to spend the full value held in the P2SH.
|
||||
|
||||
// // Confirm we were able to select sufficient value
|
||||
// let selected_value = notes.iter().map(|selected| selected.note.value).sum::<u64>()
|
||||
// + tinputs.iter().map::<u64, _>(|utxo| utxo.value.into()).sum::<u64>();
|
||||
|
||||
// if selected_value < u64::from(target_value) {
|
||||
// let e = format!(
|
||||
// "Insufficient verified funds (have {}, need {:?}). NOTE: funds need {} confirmations before they can be spent.",
|
||||
// selected_value, target_value, self.config.anchor_offset + 1
|
||||
// );
|
||||
// error!("{}", e);
|
||||
// return Err(e);
|
||||
// }
|
||||
|
||||
// Create the transaction
|
||||
println!("{}: Adding {} notes and {} utxos", now() - start_time, notes.len(), 1);
|
||||
|
||||
for selected in notes.iter() {
|
||||
if let Err(e) = builder.add_sapling_spend(
|
||||
selected.extsk.clone(),
|
||||
selected.diversifier,
|
||||
selected.note.clone(),
|
||||
selected.witness.path().unwrap(),
|
||||
) {
|
||||
let e = format!("Error adding note: {:?}", e);
|
||||
error!("{}", e);
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Use the ovk belonging to the address being sent from, if not using any notes
|
||||
// use the first address in the wallet for the ovk.
|
||||
let ovk = if notes.len() == 0 {
|
||||
self.zkeys.read().unwrap()[0].extfvk.fvk.ovk
|
||||
} else {
|
||||
ExtendedFullViewingKey::from(¬es[0].extsk).fvk.ovk
|
||||
};
|
||||
|
||||
|
||||
// Change disabled, since this function is only designed to be used when redeeming the full amount held in the P2SH.
|
||||
// This will need some attention if used for anything more complex than the originally intended use case.
|
||||
|
||||
// // If no Sapling notes were added, add the change address manually. That is,
|
||||
// // send the change back to the transparent address being used,
|
||||
// // the builder will automatically send change back to the sapling address if notes are used.
|
||||
// if notes.len() == 0 && selected_value - u64::from(target_value) > 0 {
|
||||
|
||||
// println!("{}: Adding change output", now() - start_time);
|
||||
|
||||
// let from_addr = address::RecipientAddress::from_str(from,
|
||||
// self.config.hrp_sapling_address(),
|
||||
// self.config.base58_pubkey_address(),
|
||||
// self.config.base58_script_address()).unwrap();
|
||||
|
||||
// if let Err(e) = match from_addr {
|
||||
// address::RecipientAddress::Shielded(from_addr) => {
|
||||
// builder.add_sapling_output(ovk, from_addr.clone(), Amount::from_u64(selected_value - u64::from(target_value)).unwrap(), None)
|
||||
// }
|
||||
// address::RecipientAddress::Transparent(from_addr) => {
|
||||
// builder.add_transparent_output(&from_addr, Amount::from_u64(selected_value - u64::from(target_value)).unwrap())
|
||||
// }
|
||||
// } {
|
||||
// let e = format!("Error adding transparent change output: {:?}", e);
|
||||
// error!("{}", e);
|
||||
// return Err(e);
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
for (to, value, memo) in recepients {
|
||||
// Compute memo if it exists
|
||||
let encoded_memo = match memo {
|
||||
None => None,
|
||||
Some(s) => {
|
||||
// If the string starts with an "0x", and contains only hex chars ([a-f0-9]+) then
|
||||
// interpret it as a hex
|
||||
match utils::interpret_memo_string(&s) {
|
||||
Ok(m) => Some(m),
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
println!("{}: Adding outputs", now() - start_time);
|
||||
|
||||
if let Err(e) = match to {
|
||||
address::RecipientAddress::Shielded(to) => {
|
||||
builder.add_sapling_output(ovk, to.clone(), value, encoded_memo)
|
||||
}
|
||||
address::RecipientAddress::Transparent(to) => {
|
||||
builder.add_transparent_output(&to, value)
|
||||
}
|
||||
} {
|
||||
let e = format!("Error adding output: {:?}", e);
|
||||
error!("{}", e);
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
println!("{}: Building transaction", now() - start_time);
|
||||
let (tx, _) = match builder.build(
|
||||
consensus_branch_id,
|
||||
&prover::InMemTxProver::new(spend_params, output_params),
|
||||
) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
let e = format!("Error creating transaction: {:?}", e);
|
||||
error!("{}", e);
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
println!("{}: Transaction created", now() - start_time);
|
||||
println!("Transaction ID: {}", tx.txid());
|
||||
|
||||
// Create the TX bytes
|
||||
let mut raw_tx = vec![];
|
||||
tx.write(&mut raw_tx).unwrap();
|
||||
|
||||
let txid = broadcast_fn(raw_tx.clone().into_boxed_slice())?;
|
||||
|
||||
// Mark notes as spent.
|
||||
{
|
||||
// Mark sapling notes as unconfirmed spent
|
||||
let mut txs = self.txs.write().unwrap();
|
||||
for selected in notes {
|
||||
let mut spent_note = txs.get_mut(&selected.txid).unwrap()
|
||||
.notes.iter_mut()
|
||||
.find(|nd| &nd.nullifier[..] == &selected.nullifier[..])
|
||||
.unwrap();
|
||||
spent_note.unconfirmed_spent = Some(tx.txid());
|
||||
}
|
||||
|
||||
|
||||
// Disabled below code since the uxto didn't derive from this wallet
|
||||
|
||||
// // Mark this utxo as unconfirmed spent
|
||||
// for utxo in tinputs {
|
||||
// let mut spent_utxo = txs.get_mut(&utxo.txid).unwrap().utxos.iter_mut()
|
||||
// .find(|u| utxo.txid == u.txid && utxo.output_index == u.output_index)
|
||||
// .unwrap();
|
||||
// spent_utxo.unconfirmed_spent = Some(tx.txid());
|
||||
// }
|
||||
}
|
||||
|
||||
// Add this Tx to the mempool structure
|
||||
{
|
||||
let mut mempool_txs = self.mempool_txs.write().unwrap();
|
||||
|
||||
match mempool_txs.get_mut(&tx.txid()) {
|
||||
None => {
|
||||
// Collect the outgoing metadata
|
||||
let outgoing_metadata = tos.iter().map(|(addr, amt, maybe_memo)| {
|
||||
OutgoingTxMetadata {
|
||||
address: addr.to_string(),
|
||||
value: *amt,
|
||||
memo: match maybe_memo {
|
||||
None => Memo::default(),
|
||||
Some(s) => {
|
||||
// If the address is not a z-address, then drop the memo
|
||||
if !LightWallet::is_shielded_address(&addr.to_string(), &self.config) {
|
||||
Memo::default()
|
||||
} else {
|
||||
match utils::interpret_memo_string(s) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
Memo::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}).collect::<Vec<_>>();
|
||||
|
||||
// Create a new WalletTx
|
||||
let mut wtx = WalletTx::new(height as i32, now() as u64, &tx.txid());
|
||||
wtx.outgoing_metadata = outgoing_metadata;
|
||||
wtx.total_shielded_value_spent = total_value + fee;
|
||||
|
||||
// Add it into the mempool
|
||||
mempool_txs.insert(tx.txid(), wtx);
|
||||
},
|
||||
Some(_) => {
|
||||
warn!("A newly created Tx was already in the mempool! How's that possible? Txid: {}", tx.txid());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((txid, raw_tx))
|
||||
}
|
||||
|
||||
// After some blocks have been mined, we need to remove the Txns from the mempool_tx structure
|
||||
// if they :
|
||||
// 1. Have expired
|
||||
|
Reference in New Issue
Block a user