From 02daacd81dd63394202d248ff20f1a56830d6be7 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Fri, 4 Oct 2024 14:06:05 +0200 Subject: [PATCH 01/22] During the initial sync, add to the wallet txs that are decrypted by the Shield manager --- scripts/wallet.js | 56 +++++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/scripts/wallet.js b/scripts/wallet.js index 60593dde4..0d8ca3d24 100644 --- a/scripts/wallet.js +++ b/scripts/wallet.js @@ -736,14 +736,20 @@ export class Wallet { block = await cNet.getBlock(blockHeights[i], true); downloaded++; blocks[i] = block; - // We need to process blocks monotically + // We need to process blocks monotonically // When we get a block, start from the first unhandled // One and handle as many as possible for (let j = handled; blocks[j]; j = handled) { if (syncing) break; syncing = true; handled++; - await this.#shield.handleBlock(blocks[j]); + // Transactions belonging to the transparent wallet has already been added + // in the initial transparent sync. Therefore, set allowOwn = false. + await this.#handleBlock( + blocks[j], + blockHeights[j], + false + ); // Backup every 500 handled blocks if (handled % 500 == 0) await this.saveShieldOnDisk(); // Delete so we don't have to hold all blocks in memory @@ -831,25 +837,7 @@ export class Wallet { ) { try { block = await cNet.getBlock(blockHeight); - if (block.txs) { - if ( - this.hasShield() && - blockHeight > this.#shield.getLastSyncedBlock() - ) { - await this.#shield.handleBlock(block); - } - for (const tx of block.txs) { - const parsed = Transaction.fromHex(tx.hex); - parsed.blockHeight = blockHeight; - parsed.blockTime = tx.blocktime; - // Avoid wasting memory on txs that do not regard our wallet - if (this.ownTransaction(parsed)) { - await this.addTransaction(parsed); - } - } - } else { - break; - } + await this.#handleBlock(block, blockHeight); this.#lastProcessedBlock = blockHeight; } catch (e) { console.error(e); @@ -1156,6 +1144,32 @@ export class Wallet { } } + /** + * Handle the various transactions of a block + * @param block - block outputted from any PIVX node + * @param {number} blockHeight - the height of the block in the chain + * @param {boolean} allowOwn - whether to add transaction that satisfy ownTransaction() + */ + async #handleBlock(block, blockHeight, allowOwn = true) { + let shieldTxs = []; + if ( + this.hasShield() && + blockHeight > this.#shield.getLastSyncedBlock() + ) { + shieldTxs = await this.#shield.handleBlock(block); + } + for (const tx of block.txs) { + const parsed = Transaction.fromHex(tx.hex); + parsed.blockHeight = blockHeight; + parsed.blockTime = tx.blocktime; + // Avoid wasting memory on txs that do not regard our wallet + const isOwned = allowOwn ? this.ownTransaction(parsed) : false; + if (isOwned || shieldTxs.includes(tx.hex)) { + await this.addTransaction(parsed); + } + } + } + /** * Check if any vin or vout of the transaction belong to the wallet * @param {import('./transaction.js').Transaction} transaction From 5d6b8225422b270c336d0bf4b09f58383b77c22f Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Fri, 4 Oct 2024 14:07:05 +0200 Subject: [PATCH 02/22] Make getCredit and getDebit return whether all vins/vouts belongs to the wallet --- scripts/mempool.js | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/scripts/mempool.js b/scripts/mempool.js index a2ca7deff..ea8a2944a 100644 --- a/scripts/mempool.js +++ b/scripts/mempool.js @@ -117,13 +117,15 @@ export class Mempool { * @param {import('./transaction.js').Transaction} tx */ getDebit(tx) { - return tx.vin - .filter( - (input) => - this.getOutpointStatus(input.outpoint) & OutpointState.OURS - ) + const filteredVin = tx.vin.filter( + (input) => + this.getOutpointStatus(input.outpoint) & OutpointState.OURS + ); + const debit = filteredVin .map((i) => this.outpointToUTXO(i.outpoint)) .reduce((acc, u) => acc + (u?.value || 0), 0); + const ownAllVin = tx.vin.length == filteredVin.length; + return { debit, ownAllVin }; } /** @@ -133,17 +135,21 @@ export class Mempool { getCredit(tx) { const txid = tx.txid; - return tx.vout - .filter( - (_, i) => - this.getOutpointStatus( - new COutpoint({ - txid, - n: i, - }) - ) & OutpointState.OURS - ) - .reduce((acc, u) => acc + u?.value ?? 0, 0); + const filteredVout = tx.vout.filter( + (_, i) => + this.getOutpointStatus( + new COutpoint({ + txid, + n: i, + }) + ) & OutpointState.OURS + ); + const credit = filteredVout.reduce((acc, u) => acc + u?.value ?? 0, 0); + const ownAllVout = tx.vout.length == filteredVout.length; + return { + credit, + ownAllVout, + }; } /** From 2d2e8d28e6a15047cee057274e5694c320c5bf0b Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Fri, 4 Oct 2024 14:08:09 +0200 Subject: [PATCH 03/22] Update HistoricalTx class, to take in account shield data --- scripts/historical_tx.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/historical_tx.js b/scripts/historical_tx.js index 92883d9ef..2e149be70 100644 --- a/scripts/historical_tx.js +++ b/scripts/historical_tx.js @@ -9,7 +9,9 @@ export class HistoricalTx { * @param {boolean} shieldedOutputs - If this transaction contains Shield outputs. * @param {number} time - The block time of the transaction. * @param {number} blockHeight - The block height of the transaction. - * @param {number} amount - The amount transacted, in coins. + * @param {number} amount - The transparent amount transacted, in coins. + * @param {number} shieldAmount - The shielded amount transacted, in coins. + * @param {boolean} isToSelf - If the transaction is to self. */ constructor( type, @@ -18,7 +20,9 @@ export class HistoricalTx { shieldedOutputs, time, blockHeight, - amount + amount, + shieldAmount, + isToSelf ) { this.type = type; this.id = id; @@ -27,6 +31,8 @@ export class HistoricalTx { this.time = time; this.blockHeight = blockHeight; this.amount = amount; + this.shieldAmount = shieldAmount; + this.isToSelf = isToSelf; } } From 4a22816f5b15334ecbcac4957cee1069ac65db12 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Fri, 4 Oct 2024 14:09:55 +0200 Subject: [PATCH 04/22] make toHistoricalTXs take in account shield data --- scripts/wallet.js | 67 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 12 deletions(-) diff --git a/scripts/wallet.js b/scripts/wallet.js index 0d8ca3d24..927302df6 100644 --- a/scripts/wallet.js +++ b/scripts/wallet.js @@ -620,20 +620,24 @@ export class Wallet { /** * Convert a list of Blockbook transactions to HistoricalTxs * @param {import('./transaction.js').Transaction[]} arrTXs - An array of the Blockbook TXs - * @returns {Array} - A new array of `HistoricalTx`-formatted transactions + * @returns {Promise>} - A new array of `HistoricalTx`-formatted transactions */ - // TODO: add shield data to txs - toHistoricalTXs(arrTXs) { + async toHistoricalTXs(arrTXs) { let histTXs = []; for (const tx of arrTXs) { + const { credit, ownAllVout } = this.#mempool.getCredit(tx); + const { debit, ownAllVin } = this.#mempool.getDebit(tx); // The total 'delta' or change in balance, from the Tx's sums - let nAmount = - (this.#mempool.getCredit(tx) - this.#mempool.getDebit(tx)) / - COIN; + let nAmount = (credit - debit) / COIN; + // Shielded data + const { shieldCredit, shieldDebit, arrShieldReceivers } = + await this.extractSaplingAmounts(tx); + const nShieldAmount = (shieldCredit - shieldDebit) / COIN; + const ownAllShield = shieldDebit - shieldCredit === tx.valueBalance; // The receiver addresses, if any - let arrReceivers = this.getOutAddress(tx); - + let arrReceivers = + this.getOutAddress(tx).concat(arrShieldReceivers); const getFilteredCredit = (filter) => { return tx.vout .filter((_, i) => { @@ -661,9 +665,9 @@ export class Wallet { return addr[0] === cChainParams.current.STAKING_PREFIX; }); nAmount = getFilteredCredit(OutpointState.P2CS) / COIN; - } else if (nAmount > 0) { + } else if (nAmount + nShieldAmount > 0) { type = HistoricalTxType.RECEIVED; - } else if (nAmount < 0) { + } else if (nAmount + nShieldAmount < 0) { type = HistoricalTxType.SENT; } @@ -672,15 +676,54 @@ export class Wallet { type, tx.txid, arrReceivers, - false, + tx.hasShieldData, tx.blockTime, tx.blockHeight, - Math.abs(nAmount) + nAmount, + nShieldAmount, + ownAllVin && ownAllVout && ownAllShield ) ); } return histTXs; } + + /** + * Extract the sapling spent, received and shield addressed, regarding the wallet, from a tx + * @param {import('./transaction.js').Transaction} tx - a Transaction object + */ + async extractSaplingAmounts(tx) { + let shieldCredit = 0; + let shieldDebit = 0; + let arrShieldReceivers = []; + if (!tx.hasShieldData || !wallet.hasShield()) { + return { shieldCredit, shieldDebit, arrShieldReceivers }; + } + + for (const shieldSpend of tx.shieldSpend) { + //TODO: find a better way to reverse and swap endianess + const stringArr = shieldSpend.nullifier.split('').reverse(); + for (let i = 0; i < stringArr.length - 1; i += 2) { + const temp = stringArr[i + 1]; + stringArr[i + 1] = stringArr[i]; + stringArr[i] = temp; + } + const spentNote = this.#shield.getNoteFromNullifier( + stringArr.join('') + ); + if (spentNote) { + shieldDebit += spentNote.value; + } + } + const myOutputNotes = await this.#shield.decryptTransactionOutputs( + tx.serialize() + ); + for (const note of myOutputNotes) { + shieldCredit += note.value; + arrShieldReceivers.push(note.recipient); + } + return { shieldCredit, shieldDebit, arrShieldReceivers }; + } sync = lockableFunction(async () => { if (this.#isSynced) { throw new Error('Attempting to sync when already synced'); From d03eabeb4bf05116e8d70f4739ca4303a8978bc1 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Fri, 4 Oct 2024 14:10:26 +0200 Subject: [PATCH 05/22] make the Activity show shielded txs --- scripts/dashboard/Activity.vue | 108 +++++++++++++++++++++------------ 1 file changed, 69 insertions(+), 39 deletions(-) diff --git a/scripts/dashboard/Activity.vue b/scripts/dashboard/Activity.vue index fe3431d83..35b27806e 100644 --- a/scripts/dashboard/Activity.vue +++ b/scripts/dashboard/Activity.vue @@ -8,7 +8,7 @@ import { Database } from '../database.js'; import { HistoricalTx, HistoricalTxType } from '../historical_tx.js'; import { getNameOrAddress } from '../contacts-book.js'; import { getEventEmitter } from '../event_bus'; -import { beautifyNumber } from '../misc'; +import { beautifyNumber, isShieldAddress } from '../misc'; import iCheck from '../../assets/icons/icon-check.svg'; import iHourglass from '../../assets/icons/icon-hourglass.svg'; @@ -62,6 +62,39 @@ const txMap = computed(() => { }; }); +/** + * Returns the information that we need to show (icon + label + amount) for a self transaction + * @param {number} amount - The net amount of transparent PIVs in a transaction + * @param {number} shieldAmount - The net amount of shielded PIVs in a transaction + */ +function txSelfMap(amount, shieldAmount) { + if (shieldAmount == 0 || amount == 0) { + return { + icon: 'fa-recycle', + colour: 'white', + content: + shieldAmount == 0 + ? translation.activitySentTo + : 'Shield sent to self', + amount: Math.abs(shieldAmount + amount), + }; + } else if (shieldAmount > 0) { + return { + icon: 'fa-shield', + colour: 'white', + content: 'Shielding', + amount: shieldAmount, + }; + } else if (shieldAmount < 0) { + return { + icon: 'fa-shield', + colour: 'white', + content: 'De-Shielding', + amount: amount, + }; + } +} + async function update(txToAdd = 0) { const cNet = getNetwork(); // Return if wallet is not synced yet @@ -117,7 +150,7 @@ async function update(txToAdd = 0) { } // Convert to MPW's Activity format and render it - const arrTXs = wallet.toHistoricalTXs(newTxs); + const arrTXs = await wallet.toHistoricalTXs(newTxs); await parseTXs(arrTXs); txCount = found; updating.value = false; @@ -180,46 +213,31 @@ async function parseTXs(arrTXs) { blockCount - cTx.blockHeight >= (props.rewards ? cChainParams.current.coinbaseMaturity : 6); - // Choose the content type, for the Dashboard; use a generative description, otherwise, a TX-ID - // let txContent = props.rewards ? cTx.id : 'Block Reward'; - - // Format the amount to reduce text size - let formattedAmt = ''; - if (cTx.amount < 0.01) { - formattedAmt = beautifyNumber('0.01', '13px'); - } else if (cTx.amount >= 100) { - formattedAmt = beautifyNumber( - Math.round(cTx.amount).toString(), - '13px' - ); - } else { - formattedAmt = beautifyNumber(`${cTx.amount.toFixed(2)}`, '13px'); - } - - // For 'Send' TXs: Check if this is a send-to-self transaction - let fSendToSelf = false; - if (cTx.type === HistoricalTxType.SENT) { - fSendToSelf = true; - // Check all addresses to find our own, caching them for performance - for (const strAddr of cTx.receivers) { - // If a previous Tx checked this address, skip it, otherwise, check it against our own address(es) - if (!wallet.isOwnAddress(strAddr)) { - // External address, this is not a self-only Tx - fSendToSelf = false; - } - } - } + let amountToShow = Math.abs(cTx.amount + cTx.shieldAmount); // Take the icon, colour and content based on the type of the transaction let { icon, colour, content } = txMap.value[cTx.type]; const match = content.match(/{(.)}/); if (match) { let who = ''; - if (fSendToSelf) { + if (cTx.isToSelf) { who = translation.activitySelf; - } else if (cTx.shieldedOutputs) { + const descriptor = txSelfMap(cTx.amount, cTx.shieldAmount); + icon = descriptor.icon; + colour = descriptor.colour; + content = descriptor.content; + amountToShow = descriptor.amount; + } else if ( + cTx.shieldedOutputs && + cTx.type === HistoricalTxType.SENT + ) { + // We sent a shield note to someone, but we cannot decrypt the recipient + // So show a generic "Sent to shield address" who = translation.activityShieldedAddress; } else { + const shieldAddresses = cTx.receivers.filter((addr) => { + return isShieldAddress(addr); + }); const arrAddresses = cTx.receivers .map((addr) => [wallet.isOwnAddress(addr), addr]) .filter(([isOwnAddress, _]) => { @@ -231,23 +249,35 @@ async function parseTXs(arrTXs) { who = [ ...new Set( - arrAddresses.map((addr) => - addr?.length >= 32 - ? addr?.substring(0, 6) - : addr - ) + arrAddresses + .concat(shieldAddresses) + .map((addr) => + addr?.length >= 32 + ? addr?.substring(0, 6) + : addr + ) ), ].join(', ') + '...'; } content = content.replace(/{.}/, who); } + // Format the amount to reduce text size + let formattedAmt = ''; + if (amountToShow < 0.01) { + formattedAmt = '<0.01'; + } else if (amountToShow >= 100) { + formattedAmt = Math.round(amountToShow).toString(); + } else { + formattedAmt = amountToShow.toFixed(2); + } + newTxs.push({ date: strDate, id: cTx.id, content: props.rewards ? cTx.id : content, formattedAmt, - amount: cTx.amount, + amount: amountToShow, confirmed: fConfirmed, icon, colour, From 451697656db268844c8a8770a3ec35e96d558bc2 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Fri, 4 Oct 2024 14:11:52 +0200 Subject: [PATCH 06/22] Update broken tests --- tests/unit/mempool.spec.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/unit/mempool.spec.js b/tests/unit/mempool.spec.js index 2294f7883..f41370cca 100644 --- a/tests/unit/mempool.spec.js +++ b/tests/unit/mempool.spec.js @@ -121,20 +121,20 @@ describe('mempool tests', () => { vout: [], }); mempool.addTransaction(spendTx); - expect(mempool.getDebit(spendTx)).toBe(5000000 + 4992400); + expect(mempool.getDebit(spendTx).debit).toBe(5000000 + 4992400); - expect(mempool.getDebit(new Transaction())).toBe(0); + expect(mempool.getDebit(new Transaction()).debit).toBe(0); }); it('gives correct credit', () => { - expect(mempool.getCredit(tx)).toBe(5000000 + 4992400); + expect(mempool.getCredit(tx).credit).toBe(5000000 + 4992400); // Result should stay the same even if the UTXOs are spent mempool.setSpent(new COutpoint({ txid: tx.txid, n: 1 })); - expect(mempool.getCredit(tx)).toBe(5000000 + 4992400); + expect(mempool.getCredit(tx).credit).toBe(5000000 + 4992400); mempool.setSpent(new COutpoint({ txid: tx.txid, n: 0 })); - expect(mempool.getCredit(tx)).toBe(5000000 + 4992400); - expect(mempool.getCredit(new Transaction())).toBe(0); + expect(mempool.getCredit(tx).credit).toBe(5000000 + 4992400); + expect(mempool.getCredit(new Transaction()).credit).toBe(0); }); it('marks outpoint as spent correctly', () => { From 04796841b1fce15a0b638097b86a1446aa5fb438 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Thu, 10 Oct 2024 10:14:27 +0200 Subject: [PATCH 07/22] Add missing await --- scripts/dashboard/Activity.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/dashboard/Activity.vue b/scripts/dashboard/Activity.vue index 35b27806e..aff19c553 100644 --- a/scripts/dashboard/Activity.vue +++ b/scripts/dashboard/Activity.vue @@ -129,7 +129,9 @@ async function update(txToAdd = 0) { // Only compute rewards if (!tx.isCoinStake()) continue; // Aggregate the total rewards - rewardAmount.value += wallet.toHistoricalTXs([tx])[0].amount; + rewardAmount.value += ( + await wallet.toHistoricalTXs([tx]) + )[0].amount; } // Keep track of the scan block height if (orderedTxs.length) { From c9dd54a1c211f5c09d011adfbb3b267d9243aa71 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Thu, 10 Oct 2024 11:40:47 +0200 Subject: [PATCH 08/22] Make old wallet resync if load operation is not succesful --- scripts/wallet.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/scripts/wallet.js b/scripts/wallet.js index 927302df6..38b3f6b17 100644 --- a/scripts/wallet.js +++ b/scripts/wallet.js @@ -911,8 +911,7 @@ export class Wallet { if (saplingRoot !== networkSaplingRoot) { createAlert('warning', translation.badSaplingRoot, 5000); this.#mempool = new Mempool(); - // TODO: take the wallet creation height in input from users - await this.#shield.reloadFromCheckpoint(4200000); + await this.#resetShield(); await this.#transparentSync(); await this.#syncShield(); return false; @@ -946,11 +945,25 @@ export class Wallet { if (!cAccount || cAccount.shieldData == '') { return; } - this.#shield = await PIVXShield.load(cAccount.shieldData); + const loadRes = await PIVXShield.load(cAccount.shieldData); + this.#shield = loadRes.pivxShield; + // Load operation was not successful! + // Provided data are not compatible with the latest PIVX shield version. + // Resetting the shield object is required + if (!loadRes.success) { + await this.#resetShield(); + } + getEventEmitter().emit('shield-loaded-from-disk'); return; } + async #resetShield() { + // TODO: take the wallet creation height in input from users + await this.#shield.reloadFromCheckpoint(4200000); + await this.saveShieldOnDisk(); + } + /** * @returns {Promise} Number of shield satoshis of the account */ From dd370935fcffe00253803757c87bafefea56a938 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Thu, 10 Oct 2024 15:10:29 +0200 Subject: [PATCH 09/22] Add utils function to reverse and swap endianess --- scripts/utils.js | 4 ++++ scripts/wallet.js | 24 +++++++++++------------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/scripts/utils.js b/scripts/utils.js index fc0fbad9d..359e712d4 100644 --- a/scripts/utils.js +++ b/scripts/utils.js @@ -13,6 +13,10 @@ export function bytesToHex(bytes) { return Buffer.from(bytes).toString('hex'); } +export function reverseAndSwapEndianess(hex) { + return bytesToHex(hexToBytes(hex).reverse()); +} + /** * Double SHA256 hash a byte array * @param {Array} buff - Bytes to hash diff --git a/scripts/wallet.js b/scripts/wallet.js index 38b3f6b17..945ec725a 100644 --- a/scripts/wallet.js +++ b/scripts/wallet.js @@ -15,7 +15,13 @@ import { Database } from './database.js'; import { RECEIVE_TYPES } from './contacts-book.js'; import { Account } from './accounts.js'; import { fAdvancedMode } from './settings.js'; -import { bytesToHex, hexToBytes, sleep, startBatch } from './utils.js'; +import { + bytesToHex, + hexToBytes, + reverseAndSwapEndianess, + sleep, + startBatch, +} from './utils.js'; import { strHardwareName } from './ledger.js'; import { OutpointState, Mempool } from './mempool.js'; import { getEventEmitter } from './event_bus.js'; @@ -701,16 +707,8 @@ export class Wallet { } for (const shieldSpend of tx.shieldSpend) { - //TODO: find a better way to reverse and swap endianess - const stringArr = shieldSpend.nullifier.split('').reverse(); - for (let i = 0; i < stringArr.length - 1; i += 2) { - const temp = stringArr[i + 1]; - stringArr[i + 1] = stringArr[i]; - stringArr[i] = temp; - } - const spentNote = this.#shield.getNoteFromNullifier( - stringArr.join('') - ); + const nullifier = reverseAndSwapEndianess(shieldSpend.nullifier); + const spentNote = this.#shield.getNoteFromNullifier(nullifier); if (spentNote) { shieldDebit += spentNote.value; } @@ -904,8 +902,8 @@ export class Wallet { ); async #checkShieldSaplingRoot(networkSaplingRoot) { - const saplingRoot = bytesToHex( - hexToBytes(await this.#shield.getSaplingRoot()).reverse() + const saplingRoot = reverseAndSwapEndianess( + await this.#shield.getSaplingRoot() ); // If explorer sapling root is different from ours, there must be a sync error if (saplingRoot !== networkSaplingRoot) { From e9782c9bbd63f2b7d2fa0e8e00e6c72a72c2e2de Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Fri, 11 Oct 2024 10:31:27 +0200 Subject: [PATCH 10/22] Simplify logic and fix bug of sent to shield address --- scripts/dashboard/Activity.vue | 35 +++++++++++++++++----------------- scripts/historical_tx.js | 6 +++--- scripts/wallet.js | 5 ++--- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/scripts/dashboard/Activity.vue b/scripts/dashboard/Activity.vue index aff19c553..659693691 100644 --- a/scripts/dashboard/Activity.vue +++ b/scripts/dashboard/Activity.vue @@ -229,18 +229,8 @@ async function parseTXs(arrTXs) { colour = descriptor.colour; content = descriptor.content; amountToShow = descriptor.amount; - } else if ( - cTx.shieldedOutputs && - cTx.type === HistoricalTxType.SENT - ) { - // We sent a shield note to someone, but we cannot decrypt the recipient - // So show a generic "Sent to shield address" - who = translation.activityShieldedAddress; } else { - const shieldAddresses = cTx.receivers.filter((addr) => { - return isShieldAddress(addr); - }); - const arrAddresses = cTx.receivers + let arrAddresses = cTx.receivers .map((addr) => [wallet.isOwnAddress(addr), addr]) .filter(([isOwnAddress, _]) => { return cTx.type === HistoricalTxType.RECEIVED @@ -248,18 +238,27 @@ async function parseTXs(arrTXs) { : !isOwnAddress; }) .map(([_, addr]) => getNameOrAddress(cAccount, addr)); + if (cTx.type == HistoricalTxType.RECEIVED) { + arrAddresses = arrAddresses.concat(cTx.shieldReceivers); + } who = [ ...new Set( - arrAddresses - .concat(shieldAddresses) - .map((addr) => - addr?.length >= 32 - ? addr?.substring(0, 6) - : addr - ) + arrAddresses.map((addr) => + addr?.length >= 32 + ? addr?.substring(0, 6) + : addr + ) ), ].join(', ') + '...'; + if ( + cTx.type == HistoricalTxType.SENT && + arrAddresses.length == 0 + ) { + // We sent a shield note to someone, but we cannot decrypt the recipient + // So show a generic "Sent to shield address" + who = translation.activityShieldedAddress; + } } content = content.replace(/{.}/, who); } diff --git a/scripts/historical_tx.js b/scripts/historical_tx.js index 2e149be70..6846423ae 100644 --- a/scripts/historical_tx.js +++ b/scripts/historical_tx.js @@ -6,7 +6,7 @@ export class HistoricalTx { * @param {HistoricalTxType} type - The type of transaction. * @param {string} id - The transaction ID. * @param {Array} receivers - The list of 'output addresses'. - * @param {boolean} shieldedOutputs - If this transaction contains Shield outputs. + * @param {Array} shieldReceivers - The list of decrypted 'shield output addresses'. * @param {number} time - The block time of the transaction. * @param {number} blockHeight - The block height of the transaction. * @param {number} amount - The transparent amount transacted, in coins. @@ -17,7 +17,7 @@ export class HistoricalTx { type, id, receivers, - shieldedOutputs, + shieldReceivers, time, blockHeight, amount, @@ -27,7 +27,7 @@ export class HistoricalTx { this.type = type; this.id = id; this.receivers = receivers; - this.shieldedOutputs = shieldedOutputs; + this.shieldReceivers = shieldReceivers; this.time = time; this.blockHeight = blockHeight; this.amount = amount; diff --git a/scripts/wallet.js b/scripts/wallet.js index 945ec725a..550585a6a 100644 --- a/scripts/wallet.js +++ b/scripts/wallet.js @@ -642,8 +642,7 @@ export class Wallet { const nShieldAmount = (shieldCredit - shieldDebit) / COIN; const ownAllShield = shieldDebit - shieldCredit === tx.valueBalance; // The receiver addresses, if any - let arrReceivers = - this.getOutAddress(tx).concat(arrShieldReceivers); + let arrReceivers = this.getOutAddress(tx); const getFilteredCredit = (filter) => { return tx.vout .filter((_, i) => { @@ -682,7 +681,7 @@ export class Wallet { type, tx.txid, arrReceivers, - tx.hasShieldData, + arrShieldReceivers, tx.blockTime, tx.blockHeight, nAmount, From 7ae5fb88226881e82c6de0e961d2a7bfdcfb7622 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Fri, 11 Oct 2024 10:33:50 +0200 Subject: [PATCH 11/22] Remove unused imports --- scripts/dashboard/Activity.vue | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/dashboard/Activity.vue b/scripts/dashboard/Activity.vue index 659693691..d1283453b 100644 --- a/scripts/dashboard/Activity.vue +++ b/scripts/dashboard/Activity.vue @@ -2,17 +2,16 @@ import { ref, computed, watch, onMounted } from 'vue'; import { getNetwork } from '../network.js'; import { wallet } from '../wallet.js'; -import { COIN, cChainParams } from '../chain_params.js'; +import { cChainParams } from '../chain_params.js'; import { translation } from '../i18n.js'; import { Database } from '../database.js'; import { HistoricalTx, HistoricalTxType } from '../historical_tx.js'; import { getNameOrAddress } from '../contacts-book.js'; import { getEventEmitter } from '../event_bus'; -import { beautifyNumber, isShieldAddress } from '../misc'; import iCheck from '../../assets/icons/icon-check.svg'; import iHourglass from '../../assets/icons/icon-hourglass.svg'; -import { blockCount, optimiseCurrencyLocale } from '../global.js'; +import { blockCount } from '../global.js'; const props = defineProps({ title: String, From 6e90c82af9a47de8b3fa736fe261ebeefc0c28da Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Mon, 28 Oct 2024 11:46:58 +0100 Subject: [PATCH 12/22] DebugLog in case of failure --- scripts/wallet.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/wallet.js b/scripts/wallet.js index 32ba05b3f..ea3004b77 100644 --- a/scripts/wallet.js +++ b/scripts/wallet.js @@ -40,7 +40,7 @@ import { guiToggleReceiveType } from './contacts-book.js'; import { TransactionBuilder } from './transaction_builder.js'; import { createAlert } from './alerts/alert.js'; import { AsyncInterval } from './async_interval.js'; -import { debugError, DebugTopics } from './debug.js'; +import { debugError, debugLog, DebugTopics } from './debug.js'; /** * Class Wallet, at the moment it is just a "realization" of Masterkey with a given nAccount @@ -977,6 +977,10 @@ export class Wallet { // Provided data are not compatible with the latest PIVX shield version. // Resetting the shield object is required if (!loadRes.success) { + debugLog( + DebugTopics.WALLET, + 'Shield backup is not compatible with latest library version' + ); await this.#resetShield(); } From 63fb1086643e2ca46f2d5b880995d8c7b83f1bcd Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Thu, 7 Nov 2024 11:12:24 +0100 Subject: [PATCH 13/22] apply new lint rules --- scripts/mempool.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/mempool.js b/scripts/mempool.js index 462651aeb..93812e118 100644 --- a/scripts/mempool.js +++ b/scripts/mempool.js @@ -124,7 +124,7 @@ export class Mempool { const debit = filteredVin .map((i) => this.outpointToUTXO(i.outpoint)) .reduce((acc, u) => acc + (u?.value || 0), 0); - const ownAllVin = tx.vin.length == filteredVin.length; + const ownAllVin = tx.vin.length === filteredVin.length; return { debit, ownAllVin }; } @@ -145,7 +145,7 @@ export class Mempool { ) & OutpointState.OURS ); const credit = filteredVout.reduce((acc, u) => acc + u?.value ?? 0, 0); - const ownAllVout = tx.vout.length == filteredVout.length; + const ownAllVout = tx.vout.length === filteredVout.length; return { credit, ownAllVout, From 9f7f08254804b438d9d4fedbf4457e1a892bcffb Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Tue, 12 Nov 2024 14:43:44 +0100 Subject: [PATCH 14/22] fix: load first the shield from disk --- scripts/wallet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/wallet.js b/scripts/wallet.js index eac0d810a..4908853ef 100644 --- a/scripts/wallet.js +++ b/scripts/wallet.js @@ -758,8 +758,8 @@ export class Wallet { // This is done to avoid a huge spam of event. getEventEmitter().disableEvent('balance-update'); - await this.loadFromDisk(); await this.loadShieldFromDisk(); + await this.loadFromDisk(); // Let's set the last processed block 5 blocks behind the actual chain tip // This is just to be sure since blockbook (as we know) // usually does not return txs of the actual last block. From 379f55630165a138a99e5645c01e8540089b2da3 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Tue, 12 Nov 2024 14:44:11 +0100 Subject: [PATCH 15/22] fix: Do not use txSelfMap for DELEGATION --- scripts/dashboard/Activity.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/dashboard/Activity.vue b/scripts/dashboard/Activity.vue index 501ee1231..c0f8c2b6d 100644 --- a/scripts/dashboard/Activity.vue +++ b/scripts/dashboard/Activity.vue @@ -221,7 +221,7 @@ async function parseTXs(arrTXs) { const match = content.match(/{(.)}/); if (match) { let who = ''; - if (cTx.isToSelf) { + if (cTx.isToSelf && cTx.type !== HistoricalTxType.DELEGATION) { who = translation.activitySelf; const descriptor = txSelfMap(cTx.amount, cTx.shieldAmount); icon = descriptor.icon; From c5e68a0fdc3e299228163a1f2b5482ea50a2fe19 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Tue, 12 Nov 2024 14:44:27 +0100 Subject: [PATCH 16/22] feat: Use new pivx-shield beta version --- package-lock.json | 28 ++++++++++++++-------------- package.json | 6 +++--- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4d6db5a0d..1b67dddf6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,9 +35,9 @@ "jquery": "^3.6.3", "pinia": "^2.1.7", "pivx-promos": "^0.2.0", - "pivx-shield": "^1.1.6", - "pivx-shield-rust": "^1.1.6", - "pivx-shield-rust-multicore": "^1.1.10", + "pivx-shield": "^1.2.0-7", + "pivx-shield-rust": "^1.2.0-7", + "pivx-shield-rust-multicore": "^1.2.0-7", "qr-scanner": "^1.4.2", "qrcode-generator": "^1.4.4", "vue": "^3.3.4", @@ -10136,29 +10136,29 @@ "license": "MIT" }, "node_modules/pivx-shield": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/pivx-shield/-/pivx-shield-1.1.6.tgz", - "integrity": "sha512-CSU8WGZLmx5GAVf0SznPmsWZV4RKhpb2IGWHdw86nJa+GQXFulF3ajMcoQDaTbVmx2kTpm8eo3kIpqCED1Sdxw==", + "version": "1.2.0-7", + "resolved": "https://registry.npmjs.org/pivx-shield/-/pivx-shield-1.2.0-7.tgz", + "integrity": "sha512-4ANn5N0RAc5OIsYALtwD4Nq81+6L/FLq2hmQFIqckcuVDfY5Yt8pud9aFsD0EZsV3wCi82+Fiaf99XDVopU0lw==", "license": "MIT", "dependencies": { "bs58": "^5.0.0", "comlink": "^4.4.1", - "pivx-shield-rust": "^1.1.6", - "pivx-shield-rust-multicore": "^1.1.6", + "pivx-shield-rust": "^1.2.0-7", + "pivx-shield-rust-multicore": "^1.2.0-7", "uuid": "^9.0.0", "wasm-feature-detect": "^1.5.1" } }, "node_modules/pivx-shield-rust": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/pivx-shield-rust/-/pivx-shield-rust-1.1.6.tgz", - "integrity": "sha512-baW1m2ugHda8n5QRypJ/l7fpydl5bAuSPlgMDEhN8nZNn0rBvQV3965+noEv0KmwJVhw2GdpsRR7CK01Rf/7uQ==", + "version": "1.2.0-7", + "resolved": "https://registry.npmjs.org/pivx-shield-rust/-/pivx-shield-rust-1.2.0-7.tgz", + "integrity": "sha512-3i5LIIy9cBXGtfw4KDl+KL8nUuTUOHtj+zDupLPqlp1SMnYvqO2Ma2M8Tb/KBnw4OjUwIxE/YbGiQky+Ab/Edg==", "license": "MIT" }, "node_modules/pivx-shield-rust-multicore": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/pivx-shield-rust-multicore/-/pivx-shield-rust-multicore-1.1.10.tgz", - "integrity": "sha512-9Nz0B+bp1dui9VericT0Ig3sVOX3Y5nzDMMR6RmGvIaOzuSsszDIUjbouXRaXYNHXtjSCknCsRoT+hoEwDorJw==", + "version": "1.2.0-7", + "resolved": "https://registry.npmjs.org/pivx-shield-rust-multicore/-/pivx-shield-rust-multicore-1.2.0-7.tgz", + "integrity": "sha512-oH/q5Tfz641mvOGhWES+l0u3EkwmI3C7UMEWX8H3QrKlR/RGOebP2fQTIOodEFCmIECx2ViLivDFZJO75M/qNw==", "license": "MIT" }, "node_modules/pkg-dir": { diff --git a/package.json b/package.json index 5275045dc..c496c03c9 100644 --- a/package.json +++ b/package.json @@ -96,9 +96,9 @@ "jquery": "^3.6.3", "pinia": "^2.1.7", "pivx-promos": "^0.2.0", - "pivx-shield": "^1.1.6", - "pivx-shield-rust": "^1.1.6", - "pivx-shield-rust-multicore": "^1.1.10", + "pivx-shield": "^1.2.0-7", + "pivx-shield-rust": "^1.2.0-7", + "pivx-shield-rust-multicore": "^1.2.0-7", "qr-scanner": "^1.4.2", "qrcode-generator": "^1.4.4", "vue": "^3.3.4", From 17f7b0f9b50a8cc011cd26c892eaf4c0fd7c4ae4 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Tue, 12 Nov 2024 18:02:37 +0100 Subject: [PATCH 17/22] beautify numbers --- scripts/dashboard/Activity.vue | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts/dashboard/Activity.vue b/scripts/dashboard/Activity.vue index c0f8c2b6d..255f64919 100644 --- a/scripts/dashboard/Activity.vue +++ b/scripts/dashboard/Activity.vue @@ -12,6 +12,7 @@ import { getEventEmitter } from '../event_bus'; import iCheck from '../../assets/icons/icon-check.svg'; import iHourglass from '../../assets/icons/icon-hourglass.svg'; import { blockCount } from '../global.js'; +import { beautifyNumber } from '../misc.js'; const props = defineProps({ title: String, @@ -265,11 +266,14 @@ async function parseTXs(arrTXs) { // Format the amount to reduce text size let formattedAmt = ''; if (amountToShow < 0.01) { - formattedAmt = '<0.01'; + formattedAmt = beautifyNumber('0.01', '13px'); } else if (amountToShow >= 100) { - formattedAmt = Math.round(amountToShow).toString(); + formattedAmt = beautifyNumber( + Math.round(amountToShow).toString(), + '13px' + ); } else { - formattedAmt = amountToShow.toFixed(2); + formattedAmt = beautifyNumber(`${amountToShow.toFixed(2)}`, '13px'); } newTxs.push({ From 7f2ad19146775580e05d7e4b8fccbfaeb874c58e Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Tue, 12 Nov 2024 17:58:12 +0100 Subject: [PATCH 18/22] Update E2E tests (new icons) --- cypress/snapshots/html/snapshot.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/snapshots/html/snapshot.html b/cypress/snapshots/html/snapshot.html index 66567e073..76d4d6140 100644 --- a/cypress/snapshots/html/snapshot.html +++ b/cypress/snapshots/html/snapshot.html @@ -134,7 +134,7 @@ -09/30/24Sent to self0.01 PIV +09/30/24Sent to self0.01 PIV From 9e81bae61091277951d06cb341d3e83675ce825b Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Wed, 13 Nov 2024 09:12:28 +0100 Subject: [PATCH 19/22] remove merge trash --- scripts/historical_tx.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/scripts/historical_tx.js b/scripts/historical_tx.js index 868d42d47..83c3446f9 100644 --- a/scripts/historical_tx.js +++ b/scripts/historical_tx.js @@ -9,14 +9,10 @@ export class HistoricalTx { * @param {Array} shieldReceivers - The list of decrypted 'shield output addresses'. * @param {number} time - The block time of the transaction. * @param {number} blockHeight - The block height of the transaction. -<<<<<<< HEAD * @param {number} amount - The transparent amount transacted, in coins. * @param {number} shieldAmount - The shielded amount transacted, in coins. * @param {boolean} isToSelf - If the transaction is to self. * @param {boolean} isConfirmed - Whether the transaction has been confirmed. -======= - * @param {number} amount - The amount transacted, in coins. ->>>>>>> upstream/master */ constructor( type, From 371e908161cceb96539f8a7a29f7963c0d22c7be Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Tue, 19 Nov 2024 14:28:03 +0100 Subject: [PATCH 20/22] fix infinite await bug --- scripts/reader.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/reader.js b/scripts/reader.js index 65623b49a..7fc70595f 100644 --- a/scripts/reader.js +++ b/scripts/reader.js @@ -38,6 +38,7 @@ export class Reader { } if (done) { this.#done = true; + if (this.#awaiter) this.#awaiter(); break; } } From 00145e5e07f014b35beda60dd2506d809ad98fa9 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Tue, 19 Nov 2024 14:44:38 +0100 Subject: [PATCH 21/22] fix: Use block.time instead of mediantime --- scripts/wallet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/wallet.js b/scripts/wallet.js index 3f8d9a3d3..9598cbec0 100644 --- a/scripts/wallet.js +++ b/scripts/wallet.js @@ -1311,7 +1311,7 @@ export class Wallet { for (const tx of block.txs) { const parsed = Transaction.fromHex(tx.hex); parsed.blockHeight = blockHeight; - parsed.blockTime = block.mediantime; + parsed.blockTime = block.time; // Avoid wasting memory on txs that do not regard our wallet const isOwned = allowOwn ? this.ownTransaction(parsed) : false; if (isOwned || shieldTxs.includes(tx.hex)) { From 98c00ed3a9400a616ef993452359f622e74f3e50 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Tue, 19 Nov 2024 15:15:17 +0100 Subject: [PATCH 22/22] handle block at batches of 50 --- scripts/wallet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/wallet.js b/scripts/wallet.js index 9598cbec0..e3f8ce0ff 100644 --- a/scripts/wallet.js +++ b/scripts/wallet.js @@ -895,7 +895,7 @@ export class Wallet { // This is neither a block or a tx. throw new Error('Failed to parse shield binary'); } - if (blocksArray.length > 1000) { + if (blocksArray.length > 50) { await handleAllBlocks(); } }