Skip to content

Commit

Permalink
Shield Activity (#416)
Browse files Browse the repository at this point in the history
* During the initial sync, add to the wallet txs that are decrypted by the Shield manager

* Make getCredit and getDebit return whether all vins/vouts belongs to the wallet

* Update HistoricalTx class, to take in account shield data

* make toHistoricalTXs take in account shield data

* make the Activity show shielded txs

* Update broken tests

* Add missing await

* Make old wallet resync if load operation is not succesful

* Add utils function to reverse and swap endianess

* Simplify logic and fix bug of sent to shield address

* Remove unused imports

* DebugLog in case of failure

* apply new lint rules

* fix: load first the shield from disk

* fix: Do not use txSelfMap for DELEGATION

* feat: Use new pivx-shield beta version

* beautify numbers

* Update E2E tests (new icons)

* remove merge trash

* fix infinite await bug

* fix: Use block.time instead of mediantime

* handle block at batches of 50
  • Loading branch information
panleone authored Nov 20, 2024
1 parent a44f5f0 commit 60399c6
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 104 deletions.
2 changes: 1 addition & 1 deletion cypress/snapshots/html/snapshot.html
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@
<path d="M31.7,10l-18,18c-0.7,0.7-1.8,0.7-2.5,0c0,0,0,0,0,0l-11-11c-0.3-0.3-0.3-0.9,0-1.2c0,0,0,0,0,0l5-5c0.3-0.3,0.9-0.3,1.2,0
c0,0,0,0,0,0l6,6l13-13c0.3-0.3,0.9-0.3,1.3,0l5,5C32.1,9.1,32.1,9.6,31.7,10C31.8,10,31.8,10,31.7,10C31.7,10,31.7,10,31.7,10"></path>
</svg>
</span></span></td></tr><tr><td class="align-middle pr-10px" style="font-size: 12px;"><span style="opacity: 0.5;">Sep 30</span></td><td class="align-middle pr-10px txcode"><a href="https://explorer.duddino.com/tx/163586261d25476a4ce637caf3eadf478a04a91ec8497760a35e478e47f4df9d" target="_blank" rel="noopener noreferrer"><code class="wallet-code text-center active ptr" style="padding: 4px 9px;">Sent to self</code></a></td><td class="align-middle pr-10px"><b style="font-family: &quot;Montserrat Medium&quot;; font-size: 13px; font-weight: 100;"><i class="fa-solid fa-minus" style="padding-right: 5px; color: rgb(249, 60, 76);"></i><span style="font-weight: 300;">0<span style="opacity: 0.55; font-size: 13px">.01</span></span><span style="font-weight: 300; opacity: 0.55;">&nbsp;PIV</span></b></td><td class="text-right pr-10px align-middle"><span class="badge mb-0 badge-purple"><span class="checkIcon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
</span></span></td></tr><tr><td class="align-middle pr-10px" style="font-size: 12px;"><span style="opacity: 0.5;">Sep 30</span></td><td class="align-middle pr-10px txcode"><a href="https://explorer.duddino.com/tx/163586261d25476a4ce637caf3eadf478a04a91ec8497760a35e478e47f4df9d" target="_blank" rel="noopener noreferrer"><code class="wallet-code text-center active ptr" style="padding: 4px 9px;">Sent to self</code></a></td><td class="align-middle pr-10px"><b style="font-family: &quot;Montserrat Medium&quot;; font-size: 13px; font-weight: 100;"><i class="fa-solid fa-recycle" style="padding-right: 5px; color: white;"></i><span style="font-weight: 300;">0<span style="opacity: 0.55; font-size: 13px">.01</span></span><span style="font-weight: 300; opacity: 0.55;">&nbsp;PIV</span></b></td><td class="text-right pr-10px align-middle"><span class="badge mb-0 badge-purple"><span class="checkIcon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<path d="M31.7,10l-18,18c-0.7,0.7-1.8,0.7-2.5,0c0,0,0,0,0,0l-11-11c-0.3-0.3-0.3-0.9,0-1.2c0,0,0,0,0,0l5-5c0.3-0.3,0.9-0.3,1.2,0
c0,0,0,0,0,0l6,6l13-13c0.3-0.3,0.9-0.3,1.3,0l5,5C32.1,9.1,32.1,9.6,31.7,10C31.8,10,31.8,10,31.7,10C31.7,10,31.7,10,31.7,10"></path>
</svg>
Expand Down
102 changes: 69 additions & 33 deletions scripts/dashboard/Activity.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ 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 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,
Expand Down Expand Up @@ -69,6 +69,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,
};
}
}
function updateReward() {
if (!props.rewards) return;
let res = 0;
Expand All @@ -78,6 +111,7 @@ function updateReward() {
}
rewardAmount.value = res;
}
async function update(txToAdd = 0) {
// Return if wallet is not synced yet
if (!wallet.isSynced) {
Expand Down Expand Up @@ -168,6 +202,8 @@ async function parseTXs(arrTXs) {
}
}
let amountToShow = Math.abs(cTx.amount + cTx.shieldAmount);
// Coinbase Transactions (rewards) require coinbaseMaturity confs
let fConfirmed =
cTx.blockHeight > 0 &&
Expand All @@ -176,51 +212,30 @@ async function parseTXs(arrTXs) {
? cChainParams.current.coinbaseMaturity
: 6);
// 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;
}
}
}
// 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 && cTx.type !== HistoricalTxType.DELEGATION) {
who = translation.activitySelf;
} else if (cTx.shieldedOutputs) {
who = translation.activityShieldedAddress;
const descriptor = txSelfMap(cTx.amount, cTx.shieldAmount);
icon = descriptor.icon;
colour = descriptor.colour;
content = descriptor.content;
amountToShow = descriptor.amount;
} else {
const arrAddresses = cTx.receivers
let arrAddresses = cTx.receivers
.map((addr) => [wallet.isOwnAddress(addr), addr])
.filter(([isOwnAddress, _]) => {
return cTx.type === HistoricalTxType.RECEIVED
? isOwnAddress
: !isOwnAddress;
})
.map(([_, addr]) => getNameOrAddress(cAccount, addr));
if (cTx.type == HistoricalTxType.RECEIVED) {
arrAddresses = arrAddresses.concat(cTx.shieldReceivers);
}
who =
[
...new Set(
Expand All @@ -231,16 +246,37 @@ async function parseTXs(arrTXs) {
)
),
].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);
}
// Format the amount to reduce text size
let formattedAmt = '';
if (amountToShow < 0.01) {
formattedAmt = beautifyNumber('0.01', '13px');
} else if (amountToShow >= 100) {
formattedAmt = beautifyNumber(
Math.round(amountToShow).toString(),
'13px'
);
} else {
formattedAmt = beautifyNumber(`${amountToShow.toFixed(2)}`, '13px');
}
newTxs.push({
date: strDate,
id: cTx.id,
content: props.rewards ? cTx.id : content,
formattedAmt,
amount: cTx.amount,
amount: amountToShow,
confirmed: fConfirmed,
icon,
colour,
Expand Down
17 changes: 12 additions & 5 deletions scripts/historical_tx.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,34 @@ export class HistoricalTx {
* @param {HistoricalTxType} type - The type of transaction.
* @param {string} id - The transaction ID.
* @param {Array<string>} receivers - The list of 'output addresses'.
* @param {boolean} shieldedOutputs - If this transaction contains Shield outputs.
* @param {Array<string>} 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 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.
* @param {boolean} isConfirmed - Whether the transaction has been confirmed.
*/
constructor(
type,
id,
receivers,
shieldedOutputs,
shieldReceivers,
time,
blockHeight,
amount
amount,
shieldAmount,
isToSelf
) {
this.type = type;
this.id = id;
this.receivers = receivers;
this.shieldedOutputs = shieldedOutputs;
this.shieldReceivers = shieldReceivers;
this.time = time;
this.blockHeight = blockHeight;
this.amount = amount;
this.shieldAmount = shieldAmount;
this.isToSelf = isToSelf;
}
}

Expand Down
38 changes: 22 additions & 16 deletions scripts/mempool.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
}

/**
Expand All @@ -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,
};
}

/**
Expand Down
4 changes: 1 addition & 3 deletions scripts/reader.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,7 @@ export class Reader {
}
if (done) {
this.#done = true;
if (this.#awaiter) {
this.#awaiter();
}
if (this.#awaiter) this.#awaiter();
break;
}
}
Expand Down
4 changes: 4 additions & 0 deletions scripts/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<number>} buff - Bytes to hash
Expand Down
Loading

0 comments on commit 60399c6

Please sign in to comment.