From f785bf540e869d7edb304975942c4e8a35de39aa Mon Sep 17 00:00:00 2001 From: Matthew Zipkin Date: Wed, 16 Nov 2022 14:03:46 -0500 Subject: [PATCH] txdb: un/lockBalances for every input and output --- lib/wallet/txdb.js | 218 +++++++++++--------------------------------- test/wallet-test.js | 4 +- 2 files changed, 56 insertions(+), 166 deletions(-) diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index 5bb6f5ee1..bf714430f 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -958,17 +958,13 @@ class TXDB { state.coin(path, -1); state.unconfirmed(path, -coin.value); - // FINALIZE is a special case: locked coins _leave_ the wallet. - if (tx.output(i) && tx.covenant(i).isFinalize()) { - if (!block) { - state.ulocked(path, -tx.outputs[i].value); - } else { - state.clocked(path, -tx.outputs[i].value); - // This is the first time we've seen this tx and it is in a block - // (probably from a rescan). Update unconfirmed locked balance also. - state.ulocked(path, -tx.outputs[i].value); - } - } + // If the first time we see a TX is in a block + // (i.e. during a rescan) update the "unconfirmed" unlocked balance + // before updating the "confirmed" locked balance. + if (height !== -1) + this.unlockBalances(state, coin, path, -1); + + this.unlockBalances(state, coin, path, height); if (!block) { // If the tx is not mined, we do not @@ -1009,9 +1005,9 @@ class TXDB { // (i.e. during a rescan) update the "unconfirmed" locked balance // before updating the "confirmed" locked balance. if (height !== -1) - await this.lockBalances(b, state, tx, i, path, -1); + this.lockBalances(state, output, path, -1); - await this.lockBalances(b, state, tx, i, path, height); + this.lockBalances(state, output, path, height); details.setOutput(i, path); @@ -1162,6 +1158,8 @@ class TXDB { assert(path); own = true; + this.unlockBalances(state, coin, path, height); + details.setInput(i, path, coin); if (resolved) { @@ -1174,10 +1172,6 @@ class TXDB { // been removed on-chain. state.confirmed(path, -coin.value); - // FINALIZE is a special case: locked coins _leave_ the wallet. - if (tx.output(i) && tx.covenant(i).isFinalize()) - state.clocked(path, -tx.outputs[i].value); - await this.removeCredit(b, credit, path); view.addCoin(coin); @@ -1192,7 +1186,7 @@ class TXDB { if (!path) continue; - await this.lockBalances(b, state, tx, i, path, height); + this.lockBalances(state, output, path, height); details.setOutput(i, path); @@ -1321,14 +1315,7 @@ class TXDB { state.coin(path, 1); state.unconfirmed(path, coin.value); - // FINALIZE is a special case: locked coins _leave_ the wallet. - // In this case a TX is erased, adding them back. - if (tx.output(i) && tx.covenant(i).isFinalize()) { - if (!block) - state.ulocked(path, tx.outputs[i].value); - else - state.clocked(path, tx.outputs[i].value); - } + this.lockBalances(state, coin, path, height); if (block) state.confirmed(path, coin.value); @@ -1349,7 +1336,7 @@ class TXDB { if (!path) continue; - await this.unlockBalances(b, state, tx, i, path, height); + this.unlockBalances(state, output, path, height); details.setOutput(i, path); @@ -1541,15 +1528,12 @@ class TXDB { const path = await this.getPath(coin); assert(path); + this.lockBalances(state, coin, path, height); + details.setInput(i, path, coin); state.confirmed(path, coin.value); - // FINALIZE is a special case: locked coins _leave_ the wallet. - // In this case a TX is reversed, adding them back. - if (tx.output(i) && tx.covenant(i).isFinalize()) - state.clocked(path, tx.outputs[i].value); - // Resave the credit and mark it // as spent in the mempool instead. credit.spent = true; @@ -1566,7 +1550,7 @@ class TXDB { if (!path) continue; - await this.unlockBalances(b, state, tx, i, path, height); + this.unlockBalances(state, output, path, height); const credit = await this.getCredit(hash, i); @@ -1703,167 +1687,73 @@ class TXDB { } /** - * Lock balances according to covenants. - * @param {Object} b + * Lock balances according to covenant. + * Inserting or confirming: TX outputs. + * Removing or undoing: Coins spent by the wallet in tx inputs. * @param {State} state - * @param {TX} tx - * @param {Number} i + * @param {Coin|Output} coin * @param {Path} path * @param {Number} height */ - async lockBalances(b, state, tx, i, path, height) { - const output = tx.outputs[i]; - const covenant = output.covenant; + lockBalances(state, coin, path, height) { + const {value, covenant} = coin; switch (covenant.type) { - case types.CLAIM: - case types.BID: { + case types.CLAIM: // output is locked until REGISTER + case types.BID: // output is locked until REVEAL + case types.REVEAL: // output is locked until REDEEM + case types.REGISTER: // output is now locked or "burned" + case types.UPDATE: // output has been locked since REGISTER + case types.RENEW: + case types.TRANSFER: + case types.FINALIZE: + case types.REVOKE: + { if (height === -1) - state.ulocked(path, output.value); + state.ulocked(path, value); else - state.clocked(path, output.value); - break; - } - - case types.REVEAL: { - assert(i < tx.inputs.length); - - const nameHash = covenant.getHash(0); - const prevout = tx.inputs[i].prevout; - - const bb = await this.getBid(nameHash, prevout); - if (!bb) - break; - - if (height === -1) { - state.ulocked(path, -bb.lockup); - state.ulocked(path, output.value); - } else { - state.clocked(path, -bb.lockup); - state.clocked(path, output.value); - } - - break; - } - - case types.REDEEM: { - if (height === -1) - state.ulocked(path, -output.value); - else - state.clocked(path, -output.value); - break; - } - - case types.REGISTER: { - assert(i < tx.inputs.length); - - const prevout = tx.inputs[i].prevout; - - const coin = await this.getCoin(prevout.hash, prevout.index); - assert(coin); - assert(coin.covenant.isReveal() || coin.covenant.isClaim()); - - if (height === -1) { - state.ulocked(path, -coin.value); - state.ulocked(path, output.value); - } else { - state.clocked(path, -coin.value); - state.clocked(path, output.value); - } - + state.clocked(path, value); break; } - case types.FINALIZE: { - if (height === -1) - state.ulocked(path, output.value); - else - state.clocked(path, output.value); + case types.REDEEM: // noop: already unlocked by the BID in the input break; - } } } /** * Unlock balances according to covenants. - * @param {Object} b + * Inserting or confirming: Coins spent by the wallet in TX inputs. + * Removing or undoing: TX outputs. * @param {State} state - * @param {TX} tx - * @param {Number} i + * @param {Coin|Output} coin * @param {Path} path * @param {Number} height */ - async unlockBalances(b, state, tx, i, path, height) { - const output = tx.outputs[i]; - const covenant = output.covenant; + unlockBalances(state, coin, path, height) { + const {value, covenant} = coin; switch (covenant.type) { - case types.CLAIM: - case types.BID: { + case types.CLAIM: // output is locked until REGISTER + case types.BID: // output is locked until REVEAL + case types.REVEAL: // output is locked until REDEEM + case types.REGISTER: // output is now locked or "burned" + case types.UPDATE: // output has been locked since REGISTER + case types.RENEW: + case types.TRANSFER: + case types.FINALIZE: + case types.REVOKE: + { if (height === -1) - state.ulocked(path, -output.value); + state.ulocked(path, -value); else - state.clocked(path, -output.value); + state.clocked(path, -value); break; } - - case types.REVEAL: { - assert(i < tx.inputs.length); - - const nameHash = covenant.getHash(0); - const prevout = tx.inputs[i].prevout; - - const bb = await this.getBid(nameHash, prevout); - if (!bb) - break; - - if (height === -1) { - state.ulocked(path, bb.lockup); - state.ulocked(path, -output.value); - } else { - state.clocked(path, bb.lockup); - state.clocked(path, -output.value); - } - + case types.REDEEM: // noop: already unlocked by the BID in the input break; - } - - case types.REDEEM: { - if (height === -1) - state.ulocked(path, output.value); - else - state.clocked(path, output.value); - break; - } - - case types.REGISTER: { - assert(i < tx.inputs.length); - - const coins = await this.getSpentCoins(tx); - const coin = coins[i]; - assert(coin); - assert(coin.covenant.isReveal() || coin.covenant.isClaim()); - - if (height === -1) { - state.ulocked(path, coin.value); - state.ulocked(path, -output.value); - } else { - state.clocked(path, coin.value); - state.clocked(path, -output.value); - } - - break; - } - - case types.FINALIZE: { - if (height === -1) - state.ulocked(path, -output.value); - else - state.clocked(path, -output.value); - break; - } } } diff --git a/test/wallet-test.js b/test/wallet-test.js index e6108a7db..527424550 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -2356,7 +2356,7 @@ describe('Wallet', function() { uTXCount++; // Check - const senderBal3 = await wallet.getBalance(); + const senderBal3 = await wallet.getBalance(); assert.strictEqual(senderBal3.tx, 7); // One less wallet coin because name UTXO belongs to recip now assert.strictEqual(senderBal3.coin, 3); @@ -3345,7 +3345,7 @@ describe('Wallet', function() { assert.strictEqual(bal.ulocked, value); assert.strictEqual(bal.clocked, value + secondHighest); - // Confirm REGISTER + // Confirm REDEEM const block = { height: wdb.height + 1, hash: Buffer.alloc(32),