mirror of
https://github.com/Qortal/piratewallet-light-cli.git
synced 2025-07-30 03:41:28 +00:00
Multi z and t addresses
This commit is contained in:
@@ -666,17 +666,23 @@ impl LightWallet {
|
||||
|
||||
// TODO: Iterate over all transparent addresses. This is currently looking only at
|
||||
// the first one.
|
||||
let pubkey = secp256k1::PublicKey::from_secret_key(&secp, &self.tkeys.read().unwrap()[0]).serialize();
|
||||
// Scan for t outputs
|
||||
for (n, vout) in tx.vout.iter().enumerate() {
|
||||
match vout.script_pubkey.address() {
|
||||
Some(TransparentAddress::PublicKey(hash)) => {
|
||||
if hash[..] == ripemd160::Ripemd160::digest(&Sha256::digest(&pubkey))[..] {
|
||||
// This is our address. Add this as an output to the txid
|
||||
self.add_toutput_to_wtx(height, &tx.txid(), &vout, n as u64);
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
let all_pubkeys = self.tkeys.read().unwrap().iter()
|
||||
.map(|sk|
|
||||
secp256k1::PublicKey::from_secret_key(&secp, sk).serialize()
|
||||
)
|
||||
.collect::<Vec<[u8; secp256k1::constants::PUBLIC_KEY_SIZE]>>();
|
||||
for pubkey in all_pubkeys {
|
||||
for (n, vout) in tx.vout.iter().enumerate() {
|
||||
match vout.script_pubkey.address() {
|
||||
Some(TransparentAddress::PublicKey(hash)) => {
|
||||
if hash[..] == ripemd160::Ripemd160::digest(&Sha256::digest(&pubkey))[..] {
|
||||
// This is our address. Add this as an output to the txid
|
||||
self.add_toutput_to_wtx(height, &tx.txid(), &vout, n as u64);
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1109,16 +1115,20 @@ impl LightWallet {
|
||||
// Specifically, if you send an outgoing transaction that is sent to a shielded address,
|
||||
// ZecWallet will add all your t-address funds into that transaction, and send them to your shielded
|
||||
// address as change.
|
||||
let tinputs = self.get_utxos().iter()
|
||||
.filter(|utxo| utxo.unconfirmed_spent.is_none()) // Remove any unconfirmed spends
|
||||
.map(|utxo| utxo.clone())
|
||||
.collect::<Vec<Utxo>>();
|
||||
|
||||
let mut tinputs = vec![];
|
||||
|
||||
if let Err(e) = match to {
|
||||
address::RecipientAddress::Shielded(_) => {
|
||||
// The destination is a sapling address, so add all transparent inputs
|
||||
// TODO: This only spends from the first address right now.
|
||||
let sk = self.tkeys.read().unwrap()[0];
|
||||
tinputs.extend(self.get_utxos().iter()
|
||||
.filter(|utxo| utxo.unconfirmed_spent.is_none()) // Remove any unconfirmed spends
|
||||
.map(|utxo| utxo.clone()));
|
||||
|
||||
// Create a map from address -> sk for all taddrs, so we can spend from the
|
||||
// right address
|
||||
let address_to_sk: HashMap<_, _> = self.tkeys.read().unwrap().iter().map(|sk|
|
||||
(self.address_from_sk(&sk), sk.clone())
|
||||
).collect();
|
||||
|
||||
// Add all tinputs
|
||||
tinputs.iter()
|
||||
@@ -1130,7 +1140,18 @@ impl LightWallet {
|
||||
script_pubkey: Script { 0: utxo.script.clone() },
|
||||
};
|
||||
|
||||
builder.add_transparent_input(sk, outpoint.clone(), coin.clone())
|
||||
match address_to_sk.get(&utxo.address) {
|
||||
Some(sk) => builder.add_transparent_input(*sk, outpoint.clone(), coin.clone()),
|
||||
None => {
|
||||
// Something is very wrong
|
||||
let e = format!("Couldn't find the secreykey for taddr {}", utxo.address);
|
||||
error!("{}", e);
|
||||
eprintln!("{}", e);
|
||||
|
||||
Err(zcash_primitives::transaction::builder::Error::InvalidAddress)
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
},
|
||||
@@ -1865,7 +1886,7 @@ pub mod tests {
|
||||
chain_name: "test".to_string(),
|
||||
sapling_activation_height: 0,
|
||||
consensus_branch_id: "000000".to_string(),
|
||||
anchor_offset: 1
|
||||
anchor_offset: 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1967,7 +1988,107 @@ pub mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[test]
|
||||
fn test_multi_z() {
|
||||
const AMOUNT1: u64 = 50000;
|
||||
let (wallet, txid1, block_hash) = get_test_wallet(AMOUNT1);
|
||||
|
||||
let zaddr2 = wallet.add_zaddr();
|
||||
|
||||
const AMOUNT_SENT: u64 = 20;
|
||||
|
||||
let outgoing_memo = "Outgoing Memo".to_string();
|
||||
let fee: u64 = DEFAULT_FEE.try_into().unwrap();
|
||||
|
||||
let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap();
|
||||
let (ss, so) =get_sapling_params().unwrap();
|
||||
|
||||
// Create a tx and send to address
|
||||
let raw_tx = wallet.send_to_address(branch_id, &ss, &so,
|
||||
&zaddr2, AMOUNT_SENT, Some(outgoing_memo.clone())).unwrap();
|
||||
|
||||
let sent_tx = Transaction::read(&raw_tx[..]).unwrap();
|
||||
let sent_txid = sent_tx.txid();
|
||||
|
||||
let mut cb3 = FakeCompactBlock::new(2, block_hash);
|
||||
cb3.add_tx(&sent_tx);
|
||||
wallet.scan_block(&cb3.as_bytes()).unwrap();
|
||||
wallet.scan_full_tx(&sent_tx, 2);
|
||||
|
||||
// Because the builder will randomize notes outputted, we need to find
|
||||
// which note number is the change and which is the output note (Because this tx
|
||||
// had both outputs in the same Tx)
|
||||
let (change_note_number, ext_note_number) = {
|
||||
let txs = wallet.txs.read().unwrap();
|
||||
if txs[&sent_txid].notes[0].is_change { (0,1) } else { (1,0) }
|
||||
};
|
||||
|
||||
// Now this new Spent tx should be in, so the note should be marked confirmed spent
|
||||
{
|
||||
let txs = wallet.txs.read().unwrap();
|
||||
assert_eq!(txs[&txid1].notes.len(), 1);
|
||||
assert_eq!(txs[&txid1].notes[0].note.value, AMOUNT1);
|
||||
assert_eq!(txs[&txid1].notes[0].spent, Some(sent_txid));
|
||||
assert_eq!(txs[&txid1].notes[0].unconfirmed_spent, None);
|
||||
|
||||
// The sent tx should generate change + the new incoming note
|
||||
assert_eq!(txs[&sent_txid].notes.len(), 2);
|
||||
|
||||
assert_eq!(txs[&sent_txid].notes[change_note_number].note.value, AMOUNT1 - AMOUNT_SENT - fee);
|
||||
assert_eq!(txs[&sent_txid].notes[change_note_number].account, 0);
|
||||
assert_eq!(txs[&sent_txid].notes[change_note_number].is_change, true);
|
||||
assert_eq!(txs[&sent_txid].notes[change_note_number].spent, None);
|
||||
assert_eq!(txs[&sent_txid].notes[change_note_number].unconfirmed_spent, None);
|
||||
assert_eq!(LightWallet::memo_str(&txs[&sent_txid].notes[change_note_number].memo), None);
|
||||
|
||||
assert_eq!(txs[&sent_txid].notes[ext_note_number].note.value, AMOUNT_SENT);
|
||||
assert_eq!(txs[&sent_txid].notes[ext_note_number].account, 1);
|
||||
assert_eq!(txs[&sent_txid].notes[ext_note_number].is_change, false);
|
||||
assert_eq!(txs[&sent_txid].notes[ext_note_number].spent, None);
|
||||
assert_eq!(txs[&sent_txid].notes[ext_note_number].unconfirmed_spent, None);
|
||||
assert_eq!(LightWallet::memo_str(&txs[&sent_txid].notes[ext_note_number].memo), Some(outgoing_memo));
|
||||
|
||||
assert_eq!(txs[&sent_txid].total_shielded_value_spent, AMOUNT1);
|
||||
|
||||
// No Outgoing meta data, since this is a wallet -> wallet tx
|
||||
assert_eq!(txs[&sent_txid].outgoing_metadata.len(), 0);
|
||||
}
|
||||
|
||||
// Now spend the money, which should pick notes from both addresses
|
||||
let amount_all:u64 = (AMOUNT1 - AMOUNT_SENT - fee) + (AMOUNT_SENT) - fee;
|
||||
let taddr = wallet.address_from_sk(&SecretKey::from_slice(&[1u8; 32]).unwrap());
|
||||
|
||||
let raw_tx = wallet.send_to_address(branch_id, &ss, &so,
|
||||
&taddr, amount_all, None).unwrap();
|
||||
let sent_tx = Transaction::read(&raw_tx[..]).unwrap();
|
||||
let sent_ext_txid = sent_tx.txid();
|
||||
|
||||
let mut cb4 = FakeCompactBlock::new(3, cb3.hash());
|
||||
cb4.add_tx(&sent_tx);
|
||||
wallet.scan_block(&cb4.as_bytes()).unwrap();
|
||||
wallet.scan_full_tx(&sent_tx, 3);
|
||||
|
||||
{
|
||||
// Both notes should be spent now.
|
||||
let txs = wallet.txs.read().unwrap();
|
||||
|
||||
assert_eq!(txs[&sent_txid].notes[change_note_number].is_change, true);
|
||||
assert_eq!(txs[&sent_txid].notes[change_note_number].spent, Some(sent_ext_txid));
|
||||
assert_eq!(txs[&sent_txid].notes[change_note_number].unconfirmed_spent, None);
|
||||
|
||||
assert_eq!(txs[&sent_txid].notes[ext_note_number].is_change, false);
|
||||
assert_eq!(txs[&sent_txid].notes[ext_note_number].spent, Some(sent_ext_txid));
|
||||
assert_eq!(txs[&sent_txid].notes[ext_note_number].unconfirmed_spent, None);
|
||||
|
||||
// Check outgoing metadata for the external sent tx
|
||||
assert_eq!(txs[&sent_ext_txid].notes.len(), 0); // No change was generated
|
||||
assert_eq!(txs[&sent_ext_txid].outgoing_metadata.len(), 1);
|
||||
assert_eq!(txs[&sent_ext_txid].outgoing_metadata[0].address, taddr);
|
||||
assert_eq!(txs[&sent_ext_txid].outgoing_metadata[0].value, amount_all);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_z_spend_to_taddr() {
|
||||
const AMOUNT1: u64 = 50000;
|
||||
let (wallet, txid1, block_hash) = get_test_wallet(AMOUNT1);
|
||||
@@ -2227,6 +2348,139 @@ pub mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multi_t() {
|
||||
const AMOUNT: u64 = 5000000;
|
||||
const AMOUNT_SENT1: u64 = 20000;
|
||||
const AMOUNT_SENT2: u64 = 10000;
|
||||
|
||||
let (wallet, txid1, block_hash) = get_test_wallet(AMOUNT);
|
||||
|
||||
// Add a new taddr
|
||||
let taddr2 = wallet.add_taddr();
|
||||
|
||||
let fee: u64 = DEFAULT_FEE.try_into().unwrap();
|
||||
|
||||
let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap();
|
||||
let (ss, so) = get_sapling_params().unwrap();
|
||||
|
||||
// Create a Tx and send to the second t address
|
||||
let raw_tx = wallet.send_to_address(branch_id, &ss, &so,
|
||||
&taddr2, AMOUNT_SENT1, None).unwrap();
|
||||
let sent_tx = Transaction::read(&raw_tx[..]).unwrap();
|
||||
let sent_txid1 = sent_tx.txid();
|
||||
|
||||
// Add it to a block
|
||||
let mut cb3 = FakeCompactBlock::new(2, block_hash);
|
||||
cb3.add_tx(&sent_tx);
|
||||
wallet.scan_block(&cb3.as_bytes()).unwrap();
|
||||
wallet.scan_full_tx(&sent_tx, 2);
|
||||
|
||||
// Check that the send to the second taddr worked
|
||||
{
|
||||
let txs = wallet.txs.read().unwrap();
|
||||
|
||||
// We have the original note
|
||||
assert_eq!(txs[&txid1].notes.len(), 1);
|
||||
assert_eq!(txs[&txid1].notes[0].note.value, AMOUNT);
|
||||
|
||||
// We have the spent tx
|
||||
assert_eq!(txs[&sent_txid1].notes.len(), 1);
|
||||
assert_eq!(txs[&sent_txid1].notes[0].note.value, AMOUNT - AMOUNT_SENT1 - fee);
|
||||
assert_eq!(txs[&sent_txid1].notes[0].is_change, true);
|
||||
assert_eq!(txs[&sent_txid1].notes[0].spent, None);
|
||||
assert_eq!(txs[&sent_txid1].notes[0].unconfirmed_spent, None);
|
||||
|
||||
// Since we sent the Tx to ourself, there should be no outgoing
|
||||
// metadata
|
||||
assert_eq!(txs[&sent_txid1].total_shielded_value_spent, AMOUNT);
|
||||
assert_eq!(txs[&sent_txid1].outgoing_metadata.len(), 0);
|
||||
|
||||
|
||||
// We have the taddr utxo in the same Tx
|
||||
assert_eq!(txs[&sent_txid1].utxos.len(), 1);
|
||||
assert_eq!(txs[&sent_txid1].utxos[0].address, taddr2);
|
||||
assert_eq!(txs[&sent_txid1].utxos[0].value, AMOUNT_SENT1);
|
||||
assert_eq!(txs[&sent_txid1].utxos[0].spent, None);
|
||||
assert_eq!(txs[&sent_txid1].utxos[0].unconfirmed_spent, None);
|
||||
}
|
||||
|
||||
// Send some money to the 3rd t addr
|
||||
let taddr3 = wallet.add_taddr();
|
||||
|
||||
// Create a Tx and send to the second t address
|
||||
let raw_tx = wallet.send_to_address(branch_id, &ss, &so,
|
||||
&taddr3, AMOUNT_SENT2, None).unwrap();
|
||||
let sent_tx = Transaction::read(&raw_tx[..]).unwrap();
|
||||
let sent_txid2 = sent_tx.txid();
|
||||
|
||||
// Add it to a block
|
||||
let mut cb4 = FakeCompactBlock::new(3, cb3.hash());
|
||||
cb4.add_tx(&sent_tx);
|
||||
wallet.scan_block(&cb4.as_bytes()).unwrap();
|
||||
wallet.scan_full_tx(&sent_tx, 3);
|
||||
|
||||
// Quickly check we have it
|
||||
{
|
||||
let txs = wallet.txs.read().unwrap();
|
||||
|
||||
// We have the taddr utxo in the same Tx
|
||||
assert_eq!(txs[&sent_txid2].utxos.len(), 1);
|
||||
assert_eq!(txs[&sent_txid2].utxos[0].address, taddr3);
|
||||
assert_eq!(txs[&sent_txid2].utxos[0].value, AMOUNT_SENT2);
|
||||
|
||||
// Old UTXO was NOT spent here, because we sent it to a taddr
|
||||
assert_eq!(txs[&sent_txid1].utxos.len(), 1);
|
||||
assert_eq!(txs[&sent_txid1].utxos[0].value, AMOUNT_SENT1);
|
||||
assert_eq!(txs[&sent_txid1].utxos[0].address, taddr2);
|
||||
assert_eq!(txs[&sent_txid1].utxos[0].spent, None);
|
||||
assert_eq!(txs[&sent_txid1].utxos[0].unconfirmed_spent, None);
|
||||
}
|
||||
|
||||
// Now, spend to an external z address, which will select all the utxos
|
||||
let fvk = ExtendedFullViewingKey::from(&ExtendedSpendingKey::master(&[1u8; 32]));
|
||||
let ext_address = encode_payment_address(wallet.config.hrp_sapling_address(),
|
||||
&fvk.default_address().unwrap().1);
|
||||
|
||||
const AMOUNT_SENT_EXT: u64 = 45;
|
||||
let outgoing_memo = "Outgoing Memo".to_string();
|
||||
|
||||
// Create a tx and send to address
|
||||
let raw_tx = wallet.send_to_address(branch_id, &ss, &so,
|
||||
&ext_address, AMOUNT_SENT_EXT, Some(outgoing_memo.clone())).unwrap();
|
||||
|
||||
let sent_tx = Transaction::read(&raw_tx[..]).unwrap();
|
||||
let sent_txid3 = sent_tx.txid();
|
||||
|
||||
let mut cb5 = FakeCompactBlock::new(4, cb4.hash());
|
||||
cb5.add_tx(&sent_tx);
|
||||
wallet.scan_block(&cb5.as_bytes()).unwrap();
|
||||
wallet.scan_full_tx(&sent_tx, 4);
|
||||
|
||||
{
|
||||
let txs = wallet.txs.read().unwrap();
|
||||
assert_eq!(txs[&sent_txid3].outgoing_metadata.len(), 1);
|
||||
|
||||
assert_eq!(txs[&sent_txid3].outgoing_metadata[0].address, ext_address);
|
||||
assert_eq!(txs[&sent_txid3].outgoing_metadata[0].value, AMOUNT_SENT_EXT);
|
||||
assert_eq!(txs[&sent_txid3].outgoing_metadata[0].memo.to_utf8().unwrap().unwrap(), outgoing_memo);
|
||||
|
||||
// Test to see both UTXOs were spent.
|
||||
// UTXO1
|
||||
assert_eq!(txs[&sent_txid1].utxos[0].value, AMOUNT_SENT1);
|
||||
assert_eq!(txs[&sent_txid1].utxos[0].address, taddr2);
|
||||
assert_eq!(txs[&sent_txid1].utxos[0].spent, Some(sent_txid3));
|
||||
assert_eq!(txs[&sent_txid1].utxos[0].unconfirmed_spent, None);
|
||||
|
||||
// UTXO2
|
||||
assert_eq!(txs[&sent_txid2].utxos[0].value, AMOUNT_SENT2);
|
||||
assert_eq!(txs[&sent_txid2].utxos[0].address, taddr3);
|
||||
assert_eq!(txs[&sent_txid2].utxos[0].spent, Some(sent_txid3));
|
||||
assert_eq!(txs[&sent_txid2].utxos[0].unconfirmed_spent, None);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Test helper to add blocks
|
||||
fn add_blocks(wallet: &LightWallet, start: i32, num: i32, mut prev_hash: BlockHash) -> Result<BlockHash, i32>{
|
||||
// Add it to a block
|
||||
|
Reference in New Issue
Block a user