Skip to content

Commit

Permalink
Merge pull request #89 from Duddino/add_syncblocks
Browse files Browse the repository at this point in the history
Add handle blocks
  • Loading branch information
panleone authored Nov 7, 2024
2 parents 0e998ee + cc51013 commit 644a202
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 82 deletions.
126 changes: 72 additions & 54 deletions js/pivx_shield.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,23 @@ 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][];
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 {
Expand Down Expand Up @@ -304,39 +316,70 @@ export class PIVXShield {
return { pivxShield, success: currVersion == PIVXShield.version };
}

/**
* 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) {
async handleBlocks(blocks: Block[]) {
if (blocks.length === 0) return [];
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 processed in a monotonically increasing order!",
"Blocks must be provided in monotonically increaisng order",
);
}
for (const tx of block.txs) {
const { belongToWallet, decryptedNewNotes } = await this.addTransaction(
tx.hex,
);
if (belongToWallet) {
walletTransactions.push(tx.hex);

for (const block of blocks) {
for (const tx of block.txs) {
this.pendingUnspentNotes.delete(tx.txid);
}
// 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]),
}

const {
decrypted_notes,
decrypted_new_notes,
nullifiers,
commitment_tree,
wallet_transactions,
} = await this.callWorker<TransactionResult>(
"handle_blocks",
this.commitmentTree,
blocks.map((block) => {
return {
txs: block.txs.map(({ hex }) => hex),
};
this.mapNullifierNote.set(nullifier, simplifiedNote);
}
// Delete the corresponding pending transaction
this.pendingUnspentNotes.delete(tx.txid);
}) satisfies RustBlock[],
this.extfvk,
this.isTestnet,
this.unspentNotes,
);
this.commitmentTree = commitment_tree;
this.unspentNotes = [...decrypted_notes, ...decrypted_new_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);
}
this.lastProcessedBlock = block.height;
return walletTransactions;
await this.removeSpentNotes(nullifiers);
this.lastProcessedBlock = blocks[blocks.length - 1].height;

return wallet_transactions;
}

/**
* 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) {
return await this.handleBlocks([block]);
}

/**
Expand Down Expand Up @@ -371,37 +414,12 @@ export class PIVXShield {
}
return simplifiedNotes;
}
async addTransaction(hex: string) {
const res = await this.callWorker<TransactionResult>(
"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) {
const res = await this.callWorker<TransactionResult>(
"handle_transaction",
"handle_blocks",
this.commitmentTree,
hex,
[{ txs: [hex] }] satisfies RustBlock[],
this.extfvk,
this.isTestnet,
[],
Expand Down
70 changes: 46 additions & 24 deletions src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -88,44 +87,67 @@ pub struct JSTxSaplingData {
pub decrypted_new_notes: Vec<(Note, String)>,
pub nullifiers: Vec<String>,
pub commitment_tree: String,
pub wallet_transactions: Vec<String>,
}

#[derive(Serialize, Deserialize)]
pub struct Block {
txs: Vec<String>,
}

fn read_commitment_tree(tree_hex: &str) -> Result<CommitmentTree<Node>, Box<dyn Error>> {
let buff = Cursor::new(hex::decode(tree_hex)?);
Ok(CommitmentTree::<Node>::read(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(
pub fn handle_blocks(
tree_hex: &str,
tx: &str,
blocks: JsValue,
enc_extfvk: &str,
is_testnet: bool,
comp_notes: JsValue,
) -> Result<JsValue, JsValue> {
let buff = Cursor::new(
hex::decode(tree_hex).map_err(|_| "Cannot decode commitment tree from hexadecimal")?,
);
let mut tree =
CommitmentTree::<Node>::read(buff).map_err(|_| "Cannot decode commitment tree!")?;
let blocks: Vec<Block> = 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 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::<Vec<_>>();
let mut new_comp_note: Vec<(Note, IncrementalWitness<Node>)> = 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<String> = vec![];
let mut nullifiers = vec![];
let mut new_notes = vec![];
let mut wallet_transactions = vec![];
for block in blocks {
for tx in block.txs {
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);
}
}

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<String> = Vec::with_capacity(nullifiers.len());
for nullif in nullifiers.iter() {
ser_nullifiers.push(hex::encode(nullif.0));
}
Expand All @@ -134,13 +156,13 @@ pub fn handle_transaction(
tree.write(&mut buff)
.map_err(|_| "Cannot write tree to buffer")?;

let res: JSTxSaplingData = JSTxSaplingData {
Ok(serde_wasm_bindgen::to_value(&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")?)
wallet_transactions,
decrypted_new_notes: ser_new_comp_note,
})?)
}

pub fn serialize_comp_note(
Expand All @@ -158,7 +180,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<Node>,
tx: &str,
key: UnifiedFullViewingKey,
Expand Down
7 changes: 3 additions & 4 deletions src/transaction/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -86,7 +85,7 @@ pub async fn test_create_transaction() -> Result<(), Box<dyn Error>> {
let input_tx = "0300000001a347f398c8957afee7ef0fae759ff29feda25f3e72ab5052ea09729389fd48ca000000006b483045022100c332effdceaa20b3225d52d20059e443ed112d561329b81f78a9db338637e6a102204f948d70c37bfe96bbe776f8279ad5fa857c638338d8ce49f553a3ec60993d8f0121025c6802ec58464d8e65d5f01be0b7ce6e8404e4a99f28ea3bfe47efe40df9108cffffffff01e89bd55a050000001976a9147888a1affe25e5c7af03fffdbea29f13ee1be22b88ac0000000001006cca88ffffffff000150585de8e31e6c65dfa07981275f13ebb8c9c67d8c7d088622aacca6c35c67a23642ad16653acda3cf9b5230f652592197e578ea1eae78c2496a3dc274a4ba0b522216af4c44abd4e9b81964d0a801929df1cb543c4fea041d056cc493b2f8dd90db662a0a43dae4d80a8cb0bd93e22b7000c0bcdab93f94800b88268a78a4d77147f2f16bde98b2386e5ac4025260df5f63adaef13bc8d7a920dbd14fa7e8ef0c5ff29f00942341e29b15509bfa99b4b1bd0ba29c5cf2c419113c27288b3a8d8f4919a4845e47d4e5fe1d1081a98e0ee49bb0e422b339e949276a1264c236850d9beb94c7855143a4f00689d1bf8d996eee9f0ee865ca780713f5aa1990aa848d47a39ea45c926141a1ff5a5a45c2e2e78d470a180e02b3dd47e0b206a4542d4dbfc540023ee5cb35e54a086942657232c27a15c87eef6dd11587e871ea690a45002e0b60605d7c4ac7fde81a71aadde9d0cc0d5c347fbe942993bd2a69ca2ca98ea0885454e7387d609192094bea075b96f020a8ed7080b5ef0aaf13e73da67a68e377db62720724e8c0d2913487be2a3e39380b33a90f0336f07fa031345a42784460356987da3227bd40a8cf933e4b8661020cf566af785a5c9b404c84153a69d9280739cb567c6cdf41f7a1a38b4d5847b33956b4dfa847b386850eff2a3e9fe7434fb551d1c6d31fae868a2f491ebd4f382a0ac203652f4be9fb3cff3ed10e6295639af76a41e40e562862d4359e4874b565aa1bae4b68abb0a7fe66884b75250d16276521925ead4821c7f04338286c2e52e7772f980d7a228ad2b89c18c8eeaf3ee1b4d5c5a959fc93c1cda3f9340f8256a88076b96a8718efc5dcb3733e3e11f6ca1198a97a248ff4ab0a7e883e360b8495470badc7ec75f84e58d87ff83d03c594a11b9029177efa5fea026a71c2c328a6356bd447eb154ac39e43963118033fc1a72702b12e641e7dfa8f98a58e43d75f6b3350af9fc54e683c6074cfd76e86752d7f598b6816696a4f17ba5f10c983ad2f8e102f44f42b2d07b24fb599abbfd067373c4b00f9ae830fcdd79ca8fa8c90eb414f8f5bb070d1199b9e9fae7124772865e0d6f486d7f10f073a0d61bd9e8c94b7a963c831e76b5c07cef22c06877a683aca53396289b115f8b59989f3d5906c4961891ef4677ce73d752ee0ba8929056f38d7630b02db2188d512d733126fa2479217dcd5ed4061928e5ba374300d7a5fa08af2b64cbf5a2176e07b3a4a5bb4812c46c2e608d364d8589225f9b7620116e0cd6a175ab397d295ff0ee0100d2415db6c6736a0f6e2248a62c4c47b39103f67e30814cf3c9b0b82936546d4b81826cd8fdebe24ae91a81b69e7188f4b18c3422d61b367bc4ca92f8815c0fc42caf524b3337a8b9a6737557e1d471745e02a8e88a19fe730e224126d290a";

let mut new_notes = vec![];
let _nullifiers = handle_transaction_internal(
let _nullifiers = handle_transaction(
&mut commitment_tree,
input_tx,
key,
Expand Down

0 comments on commit 644a202

Please sign in to comment.