From 773fd657ecd4fdefe2da04a9fa5322bae8596b39 Mon Sep 17 00:00:00 2001 From: Duddino Date: Fri, 18 Oct 2024 13:37:00 +0200 Subject: [PATCH 1/6] Add handle blocks function --- js/pivx_shield.ts | 21 +++++++++++++ src/transaction.rs | 74 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 90 insertions(+), 5 deletions(-) diff --git a/js/pivx_shield.ts b/js/pivx_shield.ts index 0766c31..469efca 100644 --- a/js/pivx_shield.ts +++ b/js/pivx_shield.ts @@ -304,6 +304,27 @@ export class PIVXShield { return { pivxShield, success: currVersion == PIVXShield.version }; } + async handleBlocks(blocks: Block[]) { + const walletTransactions: string[] = []; + const { decrypted_notes, nullifiers, commitment_tree } = await this.callWorker("handle_blocks", this.commitmentTree, blocks, this.extfvk, this.isTestnet, this.unspentNotes); + this.commitmentTree = commitment_tree; + this.unspentNotes = decrypted_notes; + for (const note of decrypted_notes) { + const nullifier = await this.generateNullifierFromNote(note); + const simplifiedNote = { + value: note[0].value, + recipient: await this.getShieldAddressFromNote(note[0]), + }; + + this.mapNullifierNote.set(nullifier, simplifiedNote); + } + await this.removeSpentNotes(nullifiers); + this.lastProcessedBlock = blocks[blocks.length - 1]?.height ?? this.lastProcessedBlock; + // Delete the corresponding pending transaction + // this.pendingUnspentNotes.delete(tx.txid); + return walletTransactions; + } + /** * Loop through the txs of a block and update useful shield data * @param block - block outputted from any PIVX node diff --git a/src/transaction.rs b/src/transaction.rs index 2dc6c4c..19756e0 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -90,6 +90,74 @@ pub struct JSTxSaplingData { pub commitment_tree: String, } +#[derive(Serialize, Deserialize)] +pub struct Block { + height: u32, + txs: Vec, +} + +fn read_commitment_tree(tree_hex: &str) -> Result, Box> { + let buff = Cursor::new(hex::decode(tree_hex)?); + Ok(CommitmentTree::::read(buff)?) +} + +#[wasm_bindgen] +pub fn handle_blocks( + tree_hex: &str, + blocks: JsValue, + enc_extfvk: &str, + is_testnet: bool, + comp_notes: JsValue, +) -> Result { + let blocks: Vec = serde_wasm_bindgen::from_value(blocks)?; + let mut tree = read_commitment_tree(tree_hex).map_err(|_| "Couldn't read commitment tree")?; + let comp_note: Vec<(Note, String)> = serde_wasm_bindgen::from_value(comp_notes)?; + let extfvk = + decode_extended_full_viewing_key(enc_extfvk, is_testnet).map_err(|e| e.to_string())?; + let key = UnifiedFullViewingKey::new(Some(extfvk.to_diversifiable_full_viewing_key()), None) + .ok_or("Failed to create unified full viewing key")?; + let mut comp_note = comp_note + .into_iter() + .map(|(note, witness)| { + let wit = Cursor::new(hex::decode(witness).unwrap()); + (note, IncrementalWitness::read(wit).unwrap()) + }) + .collect::>(); + let mut nullifiers = vec![]; + for block in blocks { + for tx in block.txs { + nullifiers.extend( + handle_transaction_internal(&mut tree, &tx, &key, is_testnet, &mut comp_note) + .map_err(|_| "Couldn't handle transaction")?, + ); + } + } + + let mut ser_comp_note: Vec<(Note, String)> = Vec::with_capacity(comp_note.len()); + for (note, witness) in comp_note.iter() { + let mut buff = Vec::new(); + witness + .write(&mut buff) + .map_err(|_| "Cannot write witness to buffer")?; + ser_comp_note.push((note.clone(), hex::encode(&buff))); + } + + let mut ser_nullifiers: Vec = Vec::with_capacity(nullifiers.len()); + for nullif in nullifiers.iter() { + ser_nullifiers.push(hex::encode(nullif.0)); + } + + let mut buff = Vec::new(); + tree.write(&mut buff) + .map_err(|_| "Cannot write tree to buffer")?; + + Ok(serde_wasm_bindgen::to_value(&JSTxSaplingData { + decrypted_notes: ser_comp_note, + nullifiers: ser_nullifiers, + commitment_tree: hex::encode(buff), + })?) +} + //Input a tx and return: the updated commitment merkletree, all the nullifier found in the tx and all the node decoded with the corresponding witness #[wasm_bindgen] pub fn handle_transaction( @@ -99,11 +167,7 @@ pub fn handle_transaction( is_testnet: bool, comp_notes: JsValue, ) -> Result { - let buff = Cursor::new( - hex::decode(tree_hex).map_err(|_| "Cannot decode commitment tree from hexadecimal")?, - ); - let mut tree = - CommitmentTree::::read(buff).map_err(|_| "Cannot decode commitment tree!")?; + let mut tree = read_commitment_tree(tree_hex).map_err(|_| "Couldn't read commitment tree")?; let extfvk = decode_extended_full_viewing_key(enc_extfvk, is_testnet).map_err(|e| e.to_string())?; let key = UnifiedFullViewingKey::new(Some(extfvk.to_diversifiable_full_viewing_key()), None) From d717025800895ec9b579df8b47322c287a4a0f72 Mon Sep 17 00:00:00 2001 From: Duddino Date: Sun, 27 Oct 2024 14:45:14 +0100 Subject: [PATCH 2/6] Review --- js/pivx_shield.ts | 98 ++++++++++++++++++++++++---------------------- src/transaction.rs | 73 ++++++---------------------------- 2 files changed, 65 insertions(+), 106 deletions(-) diff --git a/js/pivx_shield.ts b/js/pivx_shield.ts index 469efca..b0715cd 100644 --- a/js/pivx_shield.ts +++ b/js/pivx_shield.ts @@ -304,60 +304,66 @@ export class PIVXShield { return { pivxShield, success: currVersion == PIVXShield.version }; } - async handleBlocks(blocks: Block[]) { - const walletTransactions: string[] = []; - const { decrypted_notes, nullifiers, commitment_tree } = await this.callWorker("handle_blocks", this.commitmentTree, blocks, this.extfvk, this.isTestnet, this.unspentNotes); - this.commitmentTree = commitment_tree; - this.unspentNotes = decrypted_notes; - for (const note of decrypted_notes) { - const nullifier = await this.generateNullifierFromNote(note); - const simplifiedNote = { - value: note[0].value, - recipient: await this.getShieldAddressFromNote(note[0]), - }; - - this.mapNullifierNote.set(nullifier, simplifiedNote); - } - await this.removeSpentNotes(nullifiers); - this.lastProcessedBlock = blocks[blocks.length - 1]?.height ?? this.lastProcessedBlock; - // Delete the corresponding pending transaction - // this.pendingUnspentNotes.delete(tx.txid); - return walletTransactions; + async handleBlocks(blocks: Block[]) { + if (blocks.length === 0) return []; + const walletTransactions: string[] = []; + if ( + !blocks.every((block, i) => { + if (i === 0) { + return block.height > this.lastProcessedBlock; + } else { + return block.height > blocks[i - 1].height; + } + }) + ) { + throw new Error( + "Blocks must be provided in monotonically increaisng order", + ); } + for (const block of blocks) { + for (const tx of block.txs) { + this.pendingUnspentNotes.delete(tx.txid); + } + } + + const { + decrypted_notes, + decrypted_new_notes, + nullifiers, + commitment_tree, + } = await this.callWorker( + "handle_blocks", + this.commitmentTree, + blocks, + this.extfvk, + this.isTestnet, + this.unspentNotes, + ); + this.commitmentTree = commitment_tree; + this.unspentNotes = decrypted_notes; + for (const note of decrypted_new_notes) { + const nullifier = await this.generateNullifierFromNote(note); + const simplifiedNote = { + value: note[0].value, + recipient: await this.getShieldAddressFromNote(note[0]), + }; + + this.mapNullifierNote.set(nullifier, simplifiedNote); + } + await this.removeSpentNotes(nullifiers); + this.lastProcessedBlock = blocks[blocks.length - 1].height; + + return walletTransactions; + } + /** * Loop through the txs of a block and update useful shield data * @param block - block outputted from any PIVX node * @returns list of transactions belonging to the wallet */ async handleBlock(block: Block) { - let walletTransactions: string[] = []; - if (this.lastProcessedBlock > block.height) { - throw new Error( - "Blocks must be processed in a monotonically increasing order!", - ); - } - for (const tx of block.txs) { - const { belongToWallet, decryptedNewNotes } = await this.addTransaction( - tx.hex, - ); - if (belongToWallet) { - walletTransactions.push(tx.hex); - } - // Add all the decryptedNotes to the Nullifier->Note map - for (const note of decryptedNewNotes) { - const nullifier = await this.generateNullifierFromNote(note); - const simplifiedNote = { - value: note[0].value, - recipient: await this.getShieldAddressFromNote(note[0]), - }; - this.mapNullifierNote.set(nullifier, simplifiedNote); - } - // Delete the corresponding pending transaction - this.pendingUnspentNotes.delete(tx.txid); - } - this.lastProcessedBlock = block.height; - return walletTransactions; + return await this.handleBlocks([block]); } /** diff --git a/src/transaction.rs b/src/transaction.rs index 19756e0..ea8db6b 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -9,7 +9,6 @@ pub use pivx_primitives::consensus::{BlockHeight, MAIN_NETWORK, TEST_NETWORK}; use pivx_primitives::legacy::Script; pub use pivx_primitives::memo::MemoBytes; pub use pivx_primitives::merkle_tree::{CommitmentTree, IncrementalWitness, MerklePath}; -pub use pivx_primitives::sapling::PaymentAddress; pub use pivx_primitives::transaction::builder::Progress; use crate::keys::decode_generic_address; @@ -124,23 +123,25 @@ pub fn handle_blocks( }) .collect::>(); let mut nullifiers = vec![]; + let mut new_notes = vec![]; for block in blocks { for tx in block.txs { nullifiers.extend( - handle_transaction_internal(&mut tree, &tx, &key, is_testnet, &mut comp_note) - .map_err(|_| "Couldn't handle transaction")?, + handle_transaction_internal( + &mut tree, + &tx, + key.clone(), + is_testnet, + &mut comp_note, + &mut new_notes, + ) + .map_err(|_| "Couldn't handle transaction")?, ); } } - let mut ser_comp_note: Vec<(Note, String)> = Vec::with_capacity(comp_note.len()); - for (note, witness) in comp_note.iter() { - let mut buff = Vec::new(); - witness - .write(&mut buff) - .map_err(|_| "Cannot write witness to buffer")?; - ser_comp_note.push((note.clone(), hex::encode(&buff))); - } + let ser_comp_note = serialize_comp_note(comp_note).map_err(|_| "couldn't decrypt notes")?; + let ser_new_comp_note = serialize_comp_note(new_notes).map_err(|_| "couldn't decrypt notes")?; let mut ser_nullifiers: Vec = Vec::with_capacity(nullifiers.len()); for nullif in nullifiers.iter() { @@ -155,56 +156,8 @@ pub fn handle_blocks( decrypted_notes: ser_comp_note, nullifiers: ser_nullifiers, commitment_tree: hex::encode(buff), - })?) -} - -//Input a tx and return: the updated commitment merkletree, all the nullifier found in the tx and all the node decoded with the corresponding witness -#[wasm_bindgen] -pub fn handle_transaction( - tree_hex: &str, - tx: &str, - enc_extfvk: &str, - is_testnet: bool, - comp_notes: JsValue, -) -> Result { - let mut tree = read_commitment_tree(tree_hex).map_err(|_| "Couldn't read commitment tree")?; - let extfvk = - decode_extended_full_viewing_key(enc_extfvk, is_testnet).map_err(|e| e.to_string())?; - let key = UnifiedFullViewingKey::new(Some(extfvk.to_diversifiable_full_viewing_key()), None) - .ok_or("Failed to create unified full viewing key")?; - let comp_note: Vec<(Note, String)> = serde_wasm_bindgen::from_value(comp_notes)?; - let mut comp_note = comp_note - .into_iter() - .map(|(note, witness)| { - let wit = Cursor::new(hex::decode(witness).unwrap()); - (note, IncrementalWitness::read(wit).unwrap()) - }) - .collect::>(); - let mut new_comp_note: Vec<(Note, IncrementalWitness)> = vec![]; - let nullifiers = - handle_transaction_internal(&mut tree, tx, key, true, &mut comp_note, &mut new_comp_note) - .map_err(|_| "Cannot decode tx")?; - let ser_comp_note: Vec<(Note, String)> = - serialize_comp_note(comp_note).map_err(|_| "Cannot serialize notes")?; - let ser_new_comp_note: Vec<(Note, String)> = - serialize_comp_note(new_comp_note).map_err(|_| "Cannot serialize notes")?; - let mut ser_nullifiers: Vec = vec![]; - - for nullif in nullifiers.iter() { - ser_nullifiers.push(hex::encode(nullif.0)); - } - - let mut buff = Vec::new(); - tree.write(&mut buff) - .map_err(|_| "Cannot write tree to buffer")?; - - let res: JSTxSaplingData = JSTxSaplingData { - decrypted_notes: ser_comp_note, decrypted_new_notes: ser_new_comp_note, - nullifiers: ser_nullifiers, - commitment_tree: hex::encode(buff), - }; - Ok(serde_wasm_bindgen::to_value(&res).map_err(|_| "Cannot serialize tx output")?) + })?) } pub fn serialize_comp_note( From 81d0a75b01140a786cd28a6861251e24a011df9d Mon Sep 17 00:00:00 2001 From: Duddino Date: Mon, 28 Oct 2024 09:44:21 +0100 Subject: [PATCH 3/6] Rename handle_transaction_internal -> handle_transaction --- src/transaction.rs | 4 ++-- src/transaction/test.rs | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/transaction.rs b/src/transaction.rs index ea8db6b..57d74d9 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -127,7 +127,7 @@ pub fn handle_blocks( for block in blocks { for tx in block.txs { nullifiers.extend( - handle_transaction_internal( + handle_transaction( &mut tree, &tx, key.clone(), @@ -175,7 +175,7 @@ pub fn serialize_comp_note( } //add a tx to a given commitment tree and the return a witness to each output -pub fn handle_transaction_internal( +pub fn handle_transaction( tree: &mut CommitmentTree, tx: &str, key: UnifiedFullViewingKey, diff --git a/src/transaction/test.rs b/src/transaction/test.rs index fb9f947..0ee2dac 100644 --- a/src/transaction/test.rs +++ b/src/transaction/test.rs @@ -2,7 +2,7 @@ use crate::transaction::{create_transaction_internal, get_nullifier_from_note_internal}; -use super::handle_transaction_internal; +use super::handle_transaction; use either::Either; use jubjub::Fr; use pivx_client_backend::encoding; @@ -30,8 +30,7 @@ fn check_tx_decryption() { .expect("Failed to create key"); let mut new_comp_note = vec![]; let nullifiers = - handle_transaction_internal(&mut tree, tx, key, true, &mut vec![], &mut new_comp_note) - .unwrap(); + handle_transaction(&mut tree, tx, key, true, &mut vec![], &mut new_comp_note).unwrap(); //This was a t-s tx assert_eq!(nullifiers.len(), 0); //Successfully decrypt exactly 1 note @@ -86,7 +85,7 @@ pub async fn test_create_transaction() -> Result<(), Box> { let input_tx = "0300000001a347f398c8957afee7ef0fae759ff29feda25f3e72ab5052ea09729389fd48ca000000006b483045022100c332effdceaa20b3225d52d20059e443ed112d561329b81f78a9db338637e6a102204f948d70c37bfe96bbe776f8279ad5fa857c638338d8ce49f553a3ec60993d8f0121025c6802ec58464d8e65d5f01be0b7ce6e8404e4a99f28ea3bfe47efe40df9108cffffffff01e89bd55a050000001976a9147888a1affe25e5c7af03fffdbea29f13ee1be22b88ac0000000001006cca88ffffffff000150585de8e31e6c65dfa07981275f13ebb8c9c67d8c7d088622aacca6c35c67a23642ad16653acda3cf9b5230f652592197e578ea1eae78c2496a3dc274a4ba0b522216af4c44abd4e9b81964d0a801929df1cb543c4fea041d056cc493b2f8dd90db662a0a43dae4d80a8cb0bd93e22b7000c0bcdab93f94800b88268a78a4d77147f2f16bde98b2386e5ac4025260df5f63adaef13bc8d7a920dbd14fa7e8ef0c5ff29f00942341e29b15509bfa99b4b1bd0ba29c5cf2c419113c27288b3a8d8f4919a4845e47d4e5fe1d1081a98e0ee49bb0e422b339e949276a1264c236850d9beb94c7855143a4f00689d1bf8d996eee9f0ee865ca780713f5aa1990aa848d47a39ea45c926141a1ff5a5a45c2e2e78d470a180e02b3dd47e0b206a4542d4dbfc540023ee5cb35e54a086942657232c27a15c87eef6dd11587e871ea690a45002e0b60605d7c4ac7fde81a71aadde9d0cc0d5c347fbe942993bd2a69ca2ca98ea0885454e7387d609192094bea075b96f020a8ed7080b5ef0aaf13e73da67a68e377db62720724e8c0d2913487be2a3e39380b33a90f0336f07fa031345a42784460356987da3227bd40a8cf933e4b8661020cf566af785a5c9b404c84153a69d9280739cb567c6cdf41f7a1a38b4d5847b33956b4dfa847b386850eff2a3e9fe7434fb551d1c6d31fae868a2f491ebd4f382a0ac203652f4be9fb3cff3ed10e6295639af76a41e40e562862d4359e4874b565aa1bae4b68abb0a7fe66884b75250d16276521925ead4821c7f04338286c2e52e7772f980d7a228ad2b89c18c8eeaf3ee1b4d5c5a959fc93c1cda3f9340f8256a88076b96a8718efc5dcb3733e3e11f6ca1198a97a248ff4ab0a7e883e360b8495470badc7ec75f84e58d87ff83d03c594a11b9029177efa5fea026a71c2c328a6356bd447eb154ac39e43963118033fc1a72702b12e641e7dfa8f98a58e43d75f6b3350af9fc54e683c6074cfd76e86752d7f598b6816696a4f17ba5f10c983ad2f8e102f44f42b2d07b24fb599abbfd067373c4b00f9ae830fcdd79ca8fa8c90eb414f8f5bb070d1199b9e9fae7124772865e0d6f486d7f10f073a0d61bd9e8c94b7a963c831e76b5c07cef22c06877a683aca53396289b115f8b59989f3d5906c4961891ef4677ce73d752ee0ba8929056f38d7630b02db2188d512d733126fa2479217dcd5ed4061928e5ba374300d7a5fa08af2b64cbf5a2176e07b3a4a5bb4812c46c2e608d364d8589225f9b7620116e0cd6a175ab397d295ff0ee0100d2415db6c6736a0f6e2248a62c4c47b39103f67e30814cf3c9b0b82936546d4b81826cd8fdebe24ae91a81b69e7188f4b18c3422d61b367bc4ca92f8815c0fc42caf524b3337a8b9a6737557e1d471745e02a8e88a19fe730e224126d290a"; let mut new_notes = vec![]; - let _nullifiers = handle_transaction_internal( + let _nullifiers = handle_transaction( &mut commitment_tree, input_tx, key, From a5d18f53160ce9d735a7a5138b3b58126a6bf865 Mon Sep 17 00:00:00 2001 From: Duddino Date: Mon, 28 Oct 2024 09:44:52 +0100 Subject: [PATCH 4/6] Fix ts to use correct interface and remove addTransaction --- js/pivx_shield.ts | 44 +++++++++++++++----------------------------- src/transaction.rs | 1 - 2 files changed, 15 insertions(+), 30 deletions(-) diff --git a/js/pivx_shield.ts b/js/pivx_shield.ts index b0715cd..2b15f51 100644 --- a/js/pivx_shield.ts +++ b/js/pivx_shield.ts @@ -18,6 +18,13 @@ interface Block { height: number; } +/** + * Block that's deserialized in rust + */ +interface RustBlock { + txs: string[], +} + interface TransactionResult { decrypted_notes: [Note, string][]; decrypted_new_notes: [Note, string][]; @@ -335,7 +342,11 @@ export class PIVXShield { } = await this.callWorker( "handle_blocks", this.commitmentTree, - blocks, + blocks.map(block=>{ + return { + txs: block.txs.map(({hex}) => hex) + } + }) satisfies RustBlock[], this.extfvk, this.isTestnet, this.unspentNotes, @@ -398,37 +409,12 @@ export class PIVXShield { } return simplifiedNotes; } - async addTransaction(hex: string) { - const res = await this.callWorker( - "handle_transaction", - this.commitmentTree, - hex, - this.extfvk, - this.isTestnet, - this.unspentNotes, - ); - this.commitmentTree = res.commitment_tree; - this.unspentNotes = res.decrypted_notes.concat(res.decrypted_new_notes); - - if (res.nullifiers.length > 0) { - await this.removeSpentNotes(res.nullifiers); - } - // Check if the transaction belongs to the wallet: - let belongToWallet = res.decrypted_new_notes.length > 0; - for (const nullifier of res.nullifiers) { - if (belongToWallet) { - break; - } - belongToWallet = belongToWallet || this.mapNullifierNote.has(nullifier); - } - return { belongToWallet, decryptedNewNotes: res.decrypted_new_notes }; - } - async decryptTransaction(hex: string) { + async decryptTransaction(hex: string) { const res = await this.callWorker( - "handle_transaction", + "handle_blocks", this.commitmentTree, - hex, + [{txs: [hex]}] satisfies RustBlock[], this.extfvk, this.isTestnet, [], diff --git a/src/transaction.rs b/src/transaction.rs index 57d74d9..1c0741c 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -91,7 +91,6 @@ pub struct JSTxSaplingData { #[derive(Serialize, Deserialize)] pub struct Block { - height: u32, txs: Vec, } From 9e8cf5a479eea318d9fd3c427a68a495bc8b1b18 Mon Sep 17 00:00:00 2001 From: Duddino Date: Mon, 28 Oct 2024 09:49:32 +0100 Subject: [PATCH 5/6] Concat new notes --- js/pivx_shield.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/js/pivx_shield.ts b/js/pivx_shield.ts index 2b15f51..a44e443 100644 --- a/js/pivx_shield.ts +++ b/js/pivx_shield.ts @@ -22,7 +22,7 @@ interface Block { * Block that's deserialized in rust */ interface RustBlock { - txs: string[], + txs: string[]; } interface TransactionResult { @@ -342,17 +342,17 @@ export class PIVXShield { } = await this.callWorker( "handle_blocks", this.commitmentTree, - blocks.map(block=>{ - return { - txs: block.txs.map(({hex}) => hex) - } - }) satisfies RustBlock[], + blocks.map((block) => { + return { + txs: block.txs.map(({ hex }) => hex), + }; + }) satisfies RustBlock[], this.extfvk, this.isTestnet, this.unspentNotes, ); this.commitmentTree = commitment_tree; - this.unspentNotes = decrypted_notes; + this.unspentNotes = [...decrypted_notes, ...decrypted_new_notes]; for (const note of decrypted_new_notes) { const nullifier = await this.generateNullifierFromNote(note); const simplifiedNote = { @@ -410,11 +410,11 @@ export class PIVXShield { return simplifiedNotes; } - async decryptTransaction(hex: string) { + async decryptTransaction(hex: string) { const res = await this.callWorker( "handle_blocks", this.commitmentTree, - [{txs: [hex]}] satisfies RustBlock[], + [{ txs: [hex] }] satisfies RustBlock[], this.extfvk, this.isTestnet, [], From cc51013a1df2a790d9743ed17179b0aca9a42ee1 Mon Sep 17 00:00:00 2001 From: Duddino Date: Mon, 28 Oct 2024 10:32:37 +0100 Subject: [PATCH 6/6] Properly compute walletTransactions --- js/pivx_shield.ts | 9 +++++++-- src/transaction.rs | 28 +++++++++++++++++----------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/js/pivx_shield.ts b/js/pivx_shield.ts index a44e443..9ef7c99 100644 --- a/js/pivx_shield.ts +++ b/js/pivx_shield.ts @@ -30,6 +30,11 @@ interface TransactionResult { decrypted_new_notes: [Note, string][]; commitment_tree: string; nullifiers: string[]; + /** + * hex of the transactions belonging to the wallet + * i.e. either the spend or output belongs to us + */ + wallet_transactions: string[]; } interface Transaction { @@ -313,7 +318,6 @@ export class PIVXShield { async handleBlocks(blocks: Block[]) { if (blocks.length === 0) return []; - const walletTransactions: string[] = []; if ( !blocks.every((block, i) => { if (i === 0) { @@ -339,6 +343,7 @@ export class PIVXShield { decrypted_new_notes, nullifiers, commitment_tree, + wallet_transactions, } = await this.callWorker( "handle_blocks", this.commitmentTree, @@ -365,7 +370,7 @@ export class PIVXShield { await this.removeSpentNotes(nullifiers); this.lastProcessedBlock = blocks[blocks.length - 1].height; - return walletTransactions; + return wallet_transactions; } /** diff --git a/src/transaction.rs b/src/transaction.rs index 1c0741c..05e6bc5 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -87,6 +87,7 @@ pub struct JSTxSaplingData { pub decrypted_new_notes: Vec<(Note, String)>, pub nullifiers: Vec, pub commitment_tree: String, + pub wallet_transactions: Vec, } #[derive(Serialize, Deserialize)] @@ -123,19 +124,23 @@ pub fn handle_blocks( .collect::>(); let mut nullifiers = vec![]; let mut new_notes = vec![]; + let mut wallet_transactions = vec![]; for block in blocks { for tx in block.txs { - nullifiers.extend( - handle_transaction( - &mut tree, - &tx, - key.clone(), - is_testnet, - &mut comp_note, - &mut new_notes, - ) - .map_err(|_| "Couldn't handle transaction")?, - ); + let old_note_length = new_notes.len(); + let tx_nullifiers = handle_transaction( + &mut tree, + &tx, + key.clone(), + is_testnet, + &mut comp_note, + &mut new_notes, + ) + .map_err(|_| "Couldn't handle transaction")?; + if !tx_nullifiers.is_empty() || old_note_length != new_notes.len() { + wallet_transactions.push(tx); + } + nullifiers.extend(tx_nullifiers); } } @@ -155,6 +160,7 @@ pub fn handle_blocks( decrypted_notes: ser_comp_note, nullifiers: ser_nullifiers, commitment_tree: hex::encode(buff), + wallet_transactions, decrypted_new_notes: ser_new_comp_note, })?) }