Skip to content

Commit

Permalink
move getUTXOs to wallet
Browse files Browse the repository at this point in the history
  • Loading branch information
panleone committed May 3, 2024
1 parent 05c3b03 commit 2279398
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 87 deletions.
54 changes: 13 additions & 41 deletions scripts/mempool.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,59 +160,29 @@ export class Mempool {
return this.getOwnedVout(tx).reduce((acc, u) => acc + u?.value ?? 0, 0);
}

/**
* @param {object} o - options
* @param {number} [o.filter] - A filter to apply to all UTXOs. For example
* `OutpointState.P2CS` will NOT return P2CS transactions.
* By default it's `OutpointState.SPENT | OutpointState.LOCKED`
* @param {number} [o.requirement] - A requirement to apply to all UTXOs. For example
* `OutpointState.P2CS` will only return P2CS transactions.
* By default it's MAX_SAFE_INTEGER
* @returns {UTXO[]} a list of unspent transaction outputs
*/
getUTXOs({
filter = OutpointState.SPENT | OutpointState.LOCKED,
requirement = 0,
target = Number.POSITIVE_INFINITY,
} = {}) {
const utxos = [];
let value = 0;
for (const [o, status] of this.#outpointStatus) {
const outpoint = COutpoint.fromUnique(o);
if (status & filter) {
continue;
}
if ((status & requirement) !== requirement) {
continue;
}
utxos.push(this.outpointToUTXO(outpoint));
value += utxos.at(-1).value;
if (value >= (target * 11) / 10) {
break;
}
}
return utxos;
}

/**
* Loop through the unspent balance of the wallet
* @param {number} filters - A filter to apply to all UTXOs
* @template T
* @param {number} requirement - Requirement that outpoints must have
* @param {T} initialValue - initial value of the result
* @param {balanceIterator} fn
* @returns {number}
* @returns {T}
*/
loopUnspentBalance(filters, fn) {
let balance = 0;
loopSpendableBalance(requirement, initialValue, fn) {
for (const tx of this.#txmap.values()) {
for (const [index, vout] of tx.vout.entries()) {
const status = this.getOutpointStatus(
new COutpoint({ txid: tx.txid, n: index })
);
if (!(status & OutpointState.SPENT) && status & filters) {
balance += fn(tx, vout);
if (status & (OutpointState.SPENT | OutpointState.LOCKED)) {
continue;
}
if ((status & requirement) === requirement) {
initialValue = fn(tx, vout, initialValue);
}
}
}
return balance;
return initialValue;
}

/**
Expand All @@ -224,8 +194,10 @@ export class Mempool {
}

/**
* @template T
* @typedef {Function} balanceIterator
* @param {import('./transaction.js').Transaction} tx
* @param {CTxOut} vout
* @param {T} currentValue - the current value iterated
* @returns {number} amount
*/
144 changes: 98 additions & 46 deletions scripts/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { beforeUnloadListener, blockCount } from './global.js';
import { getNetwork } from './network.js';
import { MAX_ACCOUNT_GAP, SHIELD_BATCH_SYNC_SIZE } from './chain_params.js';
import { HistoricalTx, HistoricalTxType } from './historical_tx.js';
import { COutpoint, Transaction } from './transaction.js';
import { COutpoint, Transaction, UTXO } from './transaction.js';
import { confirmPopup, createAlert, isShieldAddress } from './misc.js';
import { cChainParams } from './chain_params.js';
import { COIN } from './chain_params.js';
Expand Down Expand Up @@ -798,21 +798,19 @@ export class Wallet {
* But for now we can just recalculate the UTXOs
*/
#getUTXOsForShield() {
return this.#mempool
.getUTXOs({
requirement: OutpointState.P2PKH | OutpointState.OURS,
})
.map((u) => {
return {
vout: u.outpoint.n,
amount: u.value,
private_key: parseWIF(
this.#masterKey.getPrivateKey(this.getPath(u.script))
),
script: hexToBytes(u.script),
txid: u.outpoint.txid,
};
});
return this.getUTXOs({
requirement: OutpointState.P2PKH | OutpointState.OURS,
}).map((u) => {
return {
vout: u.outpoint.n,
amount: u.value,
private_key: parseWIF(
this.#masterKey.getPrivateKey(this.getPath(u.script))
),
script: hexToBytes(u.script),
txid: u.outpoint.txid,
};
});
}

subscribeToNetworkEvents() {
Expand Down Expand Up @@ -977,11 +975,11 @@ export class Wallet {
) {
let balance;
if (useDelegatedInputs) {
balance = this.#mempool.coldBalance;
balance = this.coldBalance;
} else if (useShieldInputs) {
balance = this.#shield.getBalance();
} else {
balance = this.#mempool.balance;
balance = this.balance;
}
if (balance < value) {
throw new Error('Not enough balance');
Expand Down Expand Up @@ -1017,7 +1015,7 @@ export class Wallet {
const requirement = useDelegatedInputs
? OutpointState.P2CS
: OutpointState.P2PKH;
const utxos = this.#mempool.getUTXOs({
const utxos = this.getUTXOs({
requirement: requirement | OutpointState.OURS,
target: value,
});
Expand Down Expand Up @@ -1185,11 +1183,23 @@ export class Wallet {
*/
getMasternodeUTXOs() {
const collateralValue = cChainParams.current.collateralInSats;
return this.#mempool
.getUTXOs({
requirement: OutpointState.P2PKH | OutpointState.OURS,
})
.filter((u) => u.value === collateralValue);
return this.getUTXOs({
requirement: OutpointState.P2PKH | OutpointState.OURS,
}).filter((u) => u.value === collateralValue);
}

/**
* @param {import('./transaction.js').Transaction} tx - transaction we want to check
* @returns {boolean}
*/
isTxImmature(tx) {
if (tx.isCoinStake() || tx.isCoinBase()) {
return (
blockCount - tx.blockHeight <
cChainParams.current.coinbaseMaturity
);
}
return false;
}

/**
Expand All @@ -1199,38 +1209,80 @@ export class Wallet {
return this.#mempool.getTransactions();
}

#balanceInteral(requirement, includeImmature = false) {
return this.#mempool.loopSpendableBalance(
requirement,
0,
(tx, vout, currentValue) => {
if (!this.isTxImmature(tx)) {
return currentValue + vout.value;
} else if (includeImmature) {
return currentValue + vout.value;
}
return currentValue;
}
);
}

get balance() {
const filter = OutpointState.OURS | OutpointState.P2PKH;
return this.#balances.balance.getOrUpdateInvalid(() => {
return this.#mempool.loopUnspentBalance(filter, (tx, vout) => {
return vout.value;
});
return this.#balanceInteral(
OutpointState.OURS | OutpointState.P2PKH
);
});
}

get coldBalance() {
return this.#balances.coldBalance.getOrUpdateInvalid(() => {
return this.#balanceInteral(
OutpointState.OURS | OutpointState.P2CS
);
});
}

get immatureBalance() {
const filter = OutpointState.OURS;
return this.#balances.immatureBalance.getOrUpdateInvalid(() => {
return this.#mempool.loopUnspentBalance(filter, (tx, vout) => {
let retval = 0;
if (tx.isCoinStake() || tx.isCoinBase()) {
const isImmature =
blockCount - tx.blockHeight <
cChainParams.current.coinbaseMaturity;
retval = vout.value ? isImmature : 0;
}
return retval;
});
return (
this.#balanceInteral(OutpointState.OURS, true) - this.balance
);
});
}

get coldBalance() {
const filter = OutpointState.OURS | OutpointState.P2CS;
return this.#balances.coldBalance.getOrUpdateInvalid(() => {
return this.#mempool.loopUnspentBalance(filter, (tx, vout) => {
return vout.value;
});
});
/**
* @param {object} o - options
* @param {number} [o.requirement] - A requirement to apply to all UTXOs. For example
* `OutpointState.P2CS` will only return P2CS transactions.
* By default it's MAX_SAFE_INTEGER
* @param {boolean} [o.includeImmature] - If set to true immature UTXOs will be included
* @returns {UTXO[]} a list of unspent transaction outputs
*/
getUTXOs({
requirement = 0,
includeImmature = false,
target = Number.POSITIVE_INFINITY,
} = {}) {
return this.#mempool.loopSpendableBalance(
requirement,
{ utxos: [], bal: 0 },
(tx, vout, currentValue) => {
if (
(!includeImmature && this.isTxImmature(tx)) ||
currentValue.bal >= (target * 11) / 10
) {
return currentValue;
}
const n = tx.vout.findIndex((element) => element === vout);
currentValue.utxos.push(
new UTXO({
outpoint: new COutpoint({ txid: tx.txid, n }),
script: vout.script,
value: vout.value,
})
);
currentValue.bal += vout.value;
return currentValue;
}
).utxos;
}

#invalidateBalance() {
Expand Down

0 comments on commit 2279398

Please sign in to comment.