From b97913fa85187dd62b0646842d923ea610d24f5f Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Tue, 28 Feb 2023 08:16:42 -0800 Subject: [PATCH] calculates deltas from unconfirmed transactions in bulk (#3581) to calculate the confirmed balance for a nasset we start with the unconfirmed balance and subtract the balance delta for each unconfirmed transaction. when we calculate confirmed balances for all assets we iterate over unconfirmed transactions once for each asset. defines 'getUnconfirmedDeltas' to sum the transaction balance deltas for all assets in one pass over unconfirmed transactions. defines 'getUnconfirmedDelta' to do the same for a particular asset. adds unit test removes outdated fixture --- .../__fixtures__/account.test.ts.fixture | 126 ++++++++++-------- ironfish/src/wallet/account.test.ts | 35 +++++ ironfish/src/wallet/account.ts | 84 ++++++++---- 3 files changed, 163 insertions(+), 82 deletions(-) diff --git a/ironfish/src/wallet/__fixtures__/account.test.ts.fixture b/ironfish/src/wallet/__fixtures__/account.test.ts.fixture index d8f343dd5a..67d1e05a54 100644 --- a/ironfish/src/wallet/__fixtures__/account.test.ts.fixture +++ b/ironfish/src/wallet/__fixtures__/account.test.ts.fixture @@ -2217,60 +2217,6 @@ ] } ], - "Accounts calculatePendingBalance should calculate pending balance from unconfirmed balance and pending transactions": [ - { - "version": 1, - "id": "0de91883-8667-4336-9e34-552b0494daa0", - "name": "accountA", - "spendingKey": "92d279f4385c4b6583ba6c1a6f02824ec68bf5529c6129167595a741b8227cdf", - "viewKey": "a66fa447ff2777eaf6a1be29dacf36f6757e620951debc62e42da75a2850644a89f0f4525e2807336b425e9858b38c7ad3b2ac551e5ba60eabe430eab3bd7bc5", - "incomingViewKey": "1220c9a3920ef484e3bae68dfb426938aa63499a615d22356d77537db6a0dd00", - "outgoingViewKey": "2c6a7ddd25a871165d54295b8833fc8f13462c7063ba3a077e0553b1d910567c", - "publicAddress": "f4a1748bea8dba563511b867bfaac182d00fa0a37c32afe043a461bba799ae97", - "default": false - }, - { - "version": 1, - "id": "bf6b0bef-7deb-434a-ab90-808ccf05349d", - "name": "accountB", - "spendingKey": "f5000183f1924111076450025d63f344e4c8994c2a29a1985808fbc3b01eb558", - "viewKey": "6c89ca6c663fe89ae0735556128399c50b0da3f3eaca7fbd3cb1ed0a21682cb6595512e0e122c51dc7d68ea5266c4ec14e2e70c8334e1e48e8ebe788b556cf5c", - "incomingViewKey": "6c6af287c517912086787dc9ad2fea42930002d4451a49d629b34fe3abde3b02", - "outgoingViewKey": "b704beb04884aca6ee7a3398727b2d31198289407cf53ab1c6fad7eb694e969f", - "publicAddress": "33ea2da201aafcd33b3389a21b6ac98a02a9fe0b8c74fe16ad6ff4451b2be545", - "default": false - }, - { - "header": { - "sequence": 2, - "previousBlockHash": "D179D8B74987D6617267D46F4958554BA0DF02D7E5E6117DB02D6FF38FD0F6DA", - "noteCommitment": { - "type": "Buffer", - "data": "base64:2XB9wuCGMam8oFAovD8T1hAZjmtDBfLuF9piKecBS1Y=" - }, - "transactionCommitment": { - "type": "Buffer", - "data": "base64:J1r77OJ/WKeWIQhzVwmTkvL3uRgY10KfonqrLD6gnkg=" - }, - "target": "883423532389192164791648750371459257913741948437809479060803100646309888", - "randomness": "0", - "timestamp": 1677002028240, - "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", - "noteSize": 4, - "work": "0" - }, - "transactions": [ - { - "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAOv3YX40rv8AUNopjF5HxVl3hfpU7EmQzfAaOCxOKAUel4qv7JLchCx71EamO5q59AVSn3oVUPez0z5oSSUrQZUzyOsPEc2zhtcjoqmqCbsqTPfxGhlBSfl5Pd5PbABjkmGChZLzpf7FSbuoyCPA+X9ppGR+iNrZLFJcwDutUzXMJ0ESVHO15PjiT9C0Mo2UWEXMqr7fW2IZ/JNvMSpGkYbyrcrX7chOVSItPlD0ncAGXphu73ZyK1CnPe6koPPDuFWi0nBpmLmpgBkyKDQRLkiQn3q5a1ZYiJz7VT0c8JN2V5XwpM+tvCN/hiLCzWAZij1VJJ/35if3/VAaKSx9GNFjDeYXN86anQ9pLbmBT7ZWVu/skhx5x5lhLR0MNHvhBsrm3W/bVFgYxvMWeNiyODr/I1bybJY66bi6HVbubvDKJTZPI+UawtVnrTpeYgvN6UoZMFM7VqjwZUVs6GUDX6NenpOUAKW7hzO/TPtZPW1rA91PsZ7SnUBRMSANwBMCTqnBUICxF0iCMDwwSU1eiUgAp1kuO4t+Fn93qa/AxLQcEbS/c0a8kOrz7BV/q74EtYNwOCuAiL4NL4pyroxNhVXMSk5s8meg0OxPxhRUVPTO2Bbf/UIUgRklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw/np5YMV1dg5vkM+o+/iKujYANYQ0F8fhmDkX6X8PVSz5Z3w5KKwZxpJcFapjHsmB8CarE4FsDa9TlRZm9hkyCg==" - } - ] - }, - { - "type": "Buffer", - "data": "base64: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" - } - ], "Accounts connectTransaction should add received notes to unspentNoteHashes": [ { "version": 1, @@ -3277,5 +3223,77 @@ } ] } + ], + "Accounts getUnconfirmedDeltas should calculate deltas from unconfirmed transactions for all assets": [ + { + "version": 1, + "id": "c99bdd0b-c30d-4b69-90f0-f1a19ddcb39a", + "name": "accountA", + "spendingKey": "f59e661367b6b2316387221b2f13d3978fb7ccbbdc8eeff39110db1f5d78a5e1", + "viewKey": "6cfcff17ff7214baf216a100b297fbb67662130ff610ea7013edde22e720a78296e9fd665c034b4000622aa314fe96d4437b2f618ef2820b0aea96e72c9c0c2d", + "incomingViewKey": "61b851044ad62befff240ada820736c7aafd12af45fca326e76d18d3ea74f001", + "outgoingViewKey": "01ab5362bdcbc7e79d79722f2a9b2c297db64255b0cff4279fed8201e553bf07", + "publicAddress": "27347f6540eb02766d89fb9e7895b01af6e987186494327e2e9f2423b90f7581" + }, + { + "header": { + "sequence": 2, + "previousBlockHash": "D179D8B74987D6617267D46F4958554BA0DF02D7E5E6117DB02D6FF38FD0F6DA", + "noteCommitment": { + "type": "Buffer", + "data": "base64:GfkI2hJTiRkupCdpEVtK5Kl3dpcKGIoZAZ7Gsj86ziI=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:aMWX4FOIw/g5jSQHCCoDgpQD644F1O7ZtlleXovj8Xs=" + }, + "target": "883423532389192164791648750371459257913741948437809479060803100646309888", + "randomness": "0", + "timestamp": 1677534920182, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 4, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAAZVuB1CMfUNWIOx0MaBzFUxd1iB/eEU5U4PUOZCxvDqquf2ONUBFpkkB31VsiiI6QIQh/rg7YlYiW0aNJUCcbR2Cp+McaNh5PJuQ9LIi9O+LC67Z+Sc0i9HYaNMx1Sgt1D8QB0EhWem2/8WaSC36Qnao7VcauyDBkOG3w00PwG4INqQRYaTu794BijOnWMy/pPzxY+4+vkSgLkR5gpudTMFPc/KVwl0voVeRx6TRA++QHAPC0F53JAm2XxX7DA0n2c2M22iPTzubuVvJIWkbCfsmKhDSgj2XCXnkVWd1Ooo5MssfG2hh9ynkiTgtzRnUv0osCn5uOM3XYeE1plCyGEqcELNAT0x59FTkZZ3FpZFsmk5+1x10lPUiDpPJoY4o/iOUnB1JLubM6ZEQUKqkDqb2Z6miqNtf0dTbfEYPt94m4Iy7CrNCzD/mVK9yOQDv2E536MPTBflikFuH4VHq3s7Wk5O2q6peRkDCweXaw/gFrg7SlBWeRDA3B9gvPt3e/eZ7V0BBGeHIi6jLEALKTokUGFFHejMSO4NwDhNkf1IlequaHQoiAmsf7AM4V4ZsgiEVwoqQH+L7L10r6kjzZdPGrEbxtQ8ZWp2//cxQJvFEIfSY8xrRzUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwRwgWTV0H0heCOwB6HgQExY3BsV+HhSbrYw8ycICiIlexOHdMy0Fqfkv/yQNwlcsUOnl/SK9t4goBCmxHeT5wDQ==" + } + ] + }, + { + "type": "Buffer", + "data": "base64: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" + }, + { + "header": { + "sequence": 3, + "previousBlockHash": "098DB2966E8C131A893DDA9D26BB2402602A8B7F5490817CDF9E3099C77400DB", + "noteCommitment": { + "type": "Buffer", + "data": "base64:3rYtokLa95W/DaQTOxdHTGLIZy2h6Sr5+Nj4az5RDVQ=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:0zA3rA0VJuenlbayg/xlXmnqDSNR/cyz+mkqEIVFA2Y=" + }, + "target": "880842937844725196442695540779332307793253899902937591585455087694081134", + "randomness": "0", + "timestamp": 1677534923314, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 7, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA5vHXhPlUkX4sRJvJ2UpazPr8rBnLd2Vsn7ovdSqY2beHSBwtT+8TE775lFE+916+TwEpO0ntDG5RPH/cJZTsQ29h4xDFOcUDqEx9OSEbPLiymPmRwLHKn2N4C00Qf6o8GF6brAp1fxzRQ+vo/vugfZJpPmAshi5grQ8ImFIwG7QYBDOxPq11utUb9e5Sq0aIlBwn0tiheszYwbLuHT6KDL4NK/ts2/pl0H6amfZp++OycsN7ytaKDVHhgtSsnLZyHg1pvvvEAlAuE7WG5mSu7JTbAOI/0cDl63gspFb1/xp6oVMbB/gjbsu08qxsk7fo65aCWBb3J2/dXwO05JFcwamyv6mscSQNbr6gMXV7OVQhagXT0BcuVpy5Wt8bRxQmI9jD0StRs3gv4/+XLofnLcnFkrT6/CjodNe1ZNGV72ZxAPC5d4Td0rY9F6+72hrZfb5lWNAXLWKApdD9muSNALNVrGm+71PqmmW6vMFpUU69ET2kI6Z3FPwZScH1vJtLVLJs5v9tB2xQJdLT7hV6uS2faoWFFJIjenwwfBTPbrwqjY6F5Np480w23aY2fvbjOQGRdZ8FqwY695sFmeMekQXRavTpFjNqdMn3WFELGh6CC3RzDvhNqklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwyktLEFTM2A+sZR8hOgxEKmD+unf//XAfMXZNpfr4kCfzoaFj0dZKE+XMso2gynnlpG3LV4X/YG6/g3b629jJDA==" + }, + { + "type": "Buffer", + "data": "base64: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" + } + ] + } ] } \ No newline at end of file diff --git a/ironfish/src/wallet/account.test.ts b/ironfish/src/wallet/account.test.ts index 547b583da7..bdc7d43dd1 100644 --- a/ironfish/src/wallet/account.test.ts +++ b/ironfish/src/wallet/account.test.ts @@ -1704,6 +1704,41 @@ describe('Accounts', () => { }) }) + describe('getUnconfirmedDeltas', () => { + it('should calculate deltas from unconfirmed transactions for all assets', async () => { + const { node } = nodeTest + + const accountA = await useAccountFixture(node.wallet, 'accountA') + + const block2 = await useMinerBlockFixture(node.chain, undefined, accountA, node.wallet) + await node.chain.addBlock(block2) + await node.wallet.updateHead() + + const balanceA = await accountA.getBalance(Asset.nativeId(), 0) + + expect(balanceA).toMatchObject({ + confirmed: 2000000000n, + unconfirmed: 2000000000n, + }) + + const asset = new Asset(accountA.spendingKey, 'mint-asset', 'metadata') + + const block3 = await useMintBlockFixture({ + node, + account: accountA, + asset, + value: 10n, + }) + await node.chain.addBlock(block3) + await node.wallet.updateHead() + + const unconfirmedDeltas = await accountA['getUnconfirmedDeltas'](3, 1) + + expect(unconfirmedDeltas.get(Asset.nativeId())).toMatchObject({ delta: 0n, count: 1 }) + expect(unconfirmedDeltas.get(asset.id())).toMatchObject({ delta: 10n, count: 1 }) + }) + }) + describe('expireTransaction', () => { it('removes the nullifier to transaction hash if we are expiring the matching hash', async () => { const { node } = nodeTest diff --git a/ironfish/src/wallet/account.ts b/ironfish/src/wallet/account.ts index 9e7e10bafc..5741a736ff 100644 --- a/ironfish/src/wallet/account.ts +++ b/ironfish/src/wallet/account.ts @@ -813,16 +813,15 @@ export class Account { } const pendingByAsset = await this.getPendingDeltas(head.sequence, tx) + const unconfirmedByAsset = await this.getUnconfirmedDeltas(head.sequence, confirmations, tx) for await (const { assetId, balance } of this.walletDb.getUnconfirmedBalances(this, tx)) { - const { confirmed, unconfirmedCount } = - await this.calculateUnconfirmedCountAndConfirmedBalance( - head.sequence, - assetId, - confirmations, - balance.unconfirmed, - tx, - ) + const { delta: unconfirmedDelta, count: unconfirmedCount } = unconfirmedByAsset.get( + assetId, + ) ?? { + delta: 0n, + count: 0, + } const { delta: pendingDelta, count: pendingCount } = pendingByAsset.get(assetId) ?? { delta: 0n, @@ -840,7 +839,7 @@ export class Account { assetId, unconfirmed: balance.unconfirmed, unconfirmedCount, - confirmed, + confirmed: balance.unconfirmed - unconfirmedDelta, pending: balance.unconfirmed + pendingDelta, pendingCount, available, @@ -885,14 +884,12 @@ export class Account { const balance = await this.getUnconfirmedBalance(assetId, tx) - const { confirmed, unconfirmedCount } = - await this.calculateUnconfirmedCountAndConfirmedBalance( - head.sequence, - assetId, - confirmations, - balance.unconfirmed, - tx, - ) + const { delta: unconfirmedDelta, count: unconfirmedCount } = await this.getUnconfirmedDelta( + head.sequence, + confirmations, + assetId, + tx, + ) const { delta: pendingDelta, count: pendingCount } = await this.getPendingDelta( head.sequence, @@ -910,7 +907,7 @@ export class Account { return { unconfirmed: balance.unconfirmed, unconfirmedCount, - confirmed, + confirmed: balance.unconfirmed - unconfirmedDelta, pending: balance.unconfirmed + pendingDelta, available, pendingCount, @@ -958,16 +955,15 @@ export class Account { return pendingByAsset } - private async calculateUnconfirmedCountAndConfirmedBalance( + private async getUnconfirmedDelta( headSequence: number, - assetId: Buffer, confirmations: number, - unconfirmed: bigint, + assetId: Buffer, tx?: IDatabaseTransaction, - ): Promise<{ confirmed: bigint; unconfirmedCount: number }> { - let unconfirmedCount = 0 + ): Promise<{ delta: bigint; count: number }> { + let delta = 0n + let count = 0 - let confirmed = unconfirmed if (confirmations > 0) { const unconfirmedSequenceEnd = headSequence @@ -988,15 +984,47 @@ export class Account { continue } - unconfirmedCount++ - confirmed -= balanceDelta + count++ + delta += balanceDelta } } return { - confirmed, - unconfirmedCount, + delta, + count, + } + } + + private async getUnconfirmedDeltas( + headSequence: number, + confirmations: number, + tx?: IDatabaseTransaction, + ): Promise> { + const unconfirmedByAsset = new BufferMap<{ delta: bigint; count: number }>() + + if (confirmations > 0) { + const unconfirmedSequenceEnd = headSequence + + const unconfirmedSequenceStart = Math.max( + unconfirmedSequenceEnd - confirmations + 1, + GENESIS_BLOCK_SEQUENCE, + ) + + for await (const transaction of this.walletDb.loadTransactionsInSequenceRange( + this, + unconfirmedSequenceStart, + unconfirmedSequenceEnd, + tx, + )) { + for (const [assetId, assetDelta] of transaction.assetBalanceDeltas.entries()) { + const { delta, count } = unconfirmedByAsset.get(assetId) ?? { delta: 0n, count: 0 } + + unconfirmedByAsset.set(assetId, { delta: delta + assetDelta, count: count + 1 }) + } + } } + + return unconfirmedByAsset } async calculateAvailableBalance(