From aff627f378ec58571b41c81a59966fa84bc81acf Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Fri, 8 Nov 2024 15:12:56 +0100 Subject: [PATCH 1/6] feat: Add OrderedArray class --- scripts/ordered_array.js | 62 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 scripts/ordered_array.js diff --git a/scripts/ordered_array.js b/scripts/ordered_array.js new file mode 100644 index 000000000..00c3b2a1a --- /dev/null +++ b/scripts/ordered_array.js @@ -0,0 +1,62 @@ +/** + * @template T + */ +export class OrderedArray { + /** + * Actual data of the array + * @type {T[]} + */ + #data = []; + + /** + * Comparison function used to keep the array ordered + * @type {(e1: T, e2: T) => boolean} + */ + #fun; + + /** + * @param {(e1: T, e2: T) => boolean} fun + */ + constructor(fun) { + this.#fun = fun; + } + + /** + * insert an element in the array. + * @param{T} x + */ + insert(x) { + // TODO: optimize with binary search so that running time is O(log(N)) instead of O(N) + for (const [i, el] of this.#data.entries()) { + if (this.#fun(x, el)) { + this.#data.splice(i, 0, x); + return; + } + } + this.#data.push(x); + } + + /** + * Remove the first element that satisfies the condition cond + * @param {(e1: T) => boolean} cond + */ + remove(cond) { + for (const [i, el] of this.#data.entries()) { + if (cond(el)) { + this.#data.splice(i, 1); + return; + } + } + } + + /** + * @returns {T[]} + */ + get() { + return this.#data; + } + + clear() { + this.#data = []; + } +} From dd9bbde757e06845f5e593550b9413aa854061c0 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Fri, 8 Nov 2024 15:13:16 +0100 Subject: [PATCH 2/6] refactor: Add explicit JSDoc to remove warnings" --- scripts/transaction.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/transaction.js b/scripts/transaction.js index 771f15dab..5e6c5f7bc 100644 --- a/scripts/transaction.js +++ b/scripts/transaction.js @@ -98,7 +98,10 @@ export class Transaction { blockTime; /** @type{number} */ lockTime; - /** Cached txid */ + /** + * Cached txid + * @type {string} + */ #txid = ''; constructor({ From 1fee3d84d9164db4362eec403d34b72e5ae39cd2 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Fri, 8 Nov 2024 15:13:29 +0100 Subject: [PATCH 3/6] refactor: Use OrderedArray to keep track of the historicalTxs --- scripts/wallet.js | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/scripts/wallet.js b/scripts/wallet.js index 9c9e41dba..a2cfff2ba 100644 --- a/scripts/wallet.js +++ b/scripts/wallet.js @@ -35,6 +35,7 @@ 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 { OrderedArray } from './ordered_array.js'; /** * Class Wallet, at the moment it is just a "realization" of Masterkey with a given nAccount @@ -101,9 +102,11 @@ export class Wallet { #lastProcessedBlock = 0; /** * Array of historical txs, ordered by block height - * @type HistoricalTx[] + * @type OrderedArray */ - #historicalTxs; + #historicalTxs = new OrderedArray( + (hTx1, hTx2) => hTx1.blockHeight > hTx2.blockHeight + ); constructor({ nAccount, masterKey, shield, mempool = new Mempool() }) { this.#nAccount = nAccount; @@ -253,7 +256,7 @@ export class Wallet { } this.#mempool = new Mempool(); this.#lastProcessedBlock = 0; - this.#historicalTxs = []; + this.#historicalTxs.clear(); } /** @@ -698,26 +701,15 @@ export class Wallet { * @param {Transaction} tx */ #pushToHistoricalTx(tx) { - const historicalTx = this.toHistoricalTXs([tx])[0]; - let prevHeight = Number.POSITIVE_INFINITY; - for (const [i, hTx] of this.#historicalTxs.entries()) { - if ( - historicalTx.blockHeight <= prevHeight && - historicalTx.blockHeight >= hTx.blockHeight - ) { - this.#historicalTxs.splice(i, 0, historicalTx); - return; - } - prevHeight = hTx.blockHeight; - } - this.#historicalTxs.push(historicalTx); + const hTx = this.toHistoricalTXs([tx])[0]; + this.#historicalTxs.insert(hTx); } /** * @returns {HistoricalTx[]} */ getHistoricalTxs() { - return this.#historicalTxs; + return this.#historicalTxs.get(); } sync = lockableFunction(async () => { if (this.#isSynced) { @@ -1211,10 +1203,12 @@ export class Wallet { const db = await Database.getInstance(); await db.storeTx(transaction); } - if (!tx || tx.blockHeight === -1) { - // Do not add unconfirmed txs to history - if (transaction.blockHeight !== -1) - this.#pushToHistoricalTx(transaction); + + if (tx && tx.blockHeight !== -1) { + this.#historicalTxs.remove((hTx) => hTx.id === tx.txid); + this.#pushToHistoricalTx(transaction); + } else if (transaction.blockHeight !== -1) { + this.#pushToHistoricalTx(transaction); } } From 483980fca68f8f574de0e2c455d9460bfff2b8d6 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Fri, 8 Nov 2024 15:39:23 +0100 Subject: [PATCH 4/6] fix: >= instead of > --- scripts/wallet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/wallet.js b/scripts/wallet.js index a2cfff2ba..92324d7e6 100644 --- a/scripts/wallet.js +++ b/scripts/wallet.js @@ -105,7 +105,7 @@ export class Wallet { * @type OrderedArray */ #historicalTxs = new OrderedArray( - (hTx1, hTx2) => hTx1.blockHeight > hTx2.blockHeight + (hTx1, hTx2) => hTx1.blockHeight >= hTx2.blockHeight ); constructor({ nAccount, masterKey, shield, mempool = new Mempool() }) { From 7da032375c3a18ae13f9293d882bbdcdd403c223 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Tue, 12 Nov 2024 09:26:52 +0100 Subject: [PATCH 5/6] test: Add OrderedArray unit tests --- tests/unit/ordered_array.spec.js | 54 ++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 tests/unit/ordered_array.spec.js diff --git a/tests/unit/ordered_array.spec.js b/tests/unit/ordered_array.spec.js new file mode 100644 index 000000000..a18a7bf8e --- /dev/null +++ b/tests/unit/ordered_array.spec.js @@ -0,0 +1,54 @@ +import { OrderedArray } from '../../scripts/ordered_array.js'; +import { beforeEach, it } from 'vitest'; +import { getRandomInt } from '../../scripts/utils.js'; + +function verifyOrder(arr, fn) { + let prev = arr[0]; + for (let i = 1; i < arr.length; i++) { + expect(fn(prev, arr[i])); + prev = arr[i]; + } +} +function insertFirstNaturals(arr, N) { + for (let i = 0; i < N; i++) { + arr.insert(i); + } +} +describe('Ordered array tests', () => { + /** + * @type OrderedArray + */ + let arr; + const sleep_time = 1000; + beforeEach(() => { + arr = new OrderedArray((x, y) => x >= y); + }); + it('is actually ordered', () => { + const Nit = 100; + const Max = 1000; + for (let i = 0; i < Nit; i++) { + arr.insert(getRandomInt(Max)); + } + verifyOrder(arr); + }); + it('returns the internal data correctly', () => { + insertFirstNaturals(arr, 3); + expect(arr.get()).toStrictEqual([2, 1, 0]); + }); + it('deletes correctly', () => { + insertFirstNaturals(arr, 6); + expect(arr.get()).toStrictEqual([5, 4, 3, 2, 1, 0]); + arr.remove((x) => x === 1); + expect(arr.get()).toStrictEqual([5, 4, 3, 2, 0]); + arr.remove((x) => x < 4); + expect(arr.get()).toStrictEqual([5, 4, 2, 0]); + arr.remove((x) => x > 5); + expect(arr.get()).toStrictEqual([5, 4, 2, 0]); + }); + it('clears correctly', () => { + insertFirstNaturals(arr, 6); + expect(arr.get()).toStrictEqual([5, 4, 3, 2, 1, 0]); + arr.clear(); + expect(arr.get()).toStrictEqual([]); + }); +}); From ca7ce6f50801ebd5a313a104977741c623bfc216 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Tue, 12 Nov 2024 09:30:15 +0100 Subject: [PATCH 6/6] fix: export getRandomInt --- scripts/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/utils.js b/scripts/utils.js index fa16eb0e8..bcd78d6f3 100644 --- a/scripts/utils.js +++ b/scripts/utils.js @@ -143,7 +143,7 @@ export function sleep(ms) { * @param {number} N * @returns {number} */ -function getRandomInt(N) { +export function getRandomInt(N) { return Math.floor(Math.random() * N); }