diff --git a/.npmignore b/.npmignore index ca6705a78..30ec5f154 100644 --- a/.npmignore +++ b/.npmignore @@ -5,12 +5,15 @@ .eslint* .git* .mocharc* +.nyc_output/ .yarnignore bench/ browser/hsd* build/ +coverage/ docker_data/ docs/ +eslint.config.cjs node_modules/ npm-debug.log package-lock.json diff --git a/CHANGELOG.md b/CHANGELOG.md index d119584a5..1b3684a9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # HSD Release Notes & Changelog +## Unreleased + +### Wallet Changes + +#### Wallet HTTP API + - `POST /wallet/:id/zap` returned object has a new property: `zapped: number`, + indicating the number of transactions that were zapped. + +#### Wallet/WalletDB API + - Wallet.zap now returns number of transactions zapped instead of hashes. + + ## v7.0.0 **When upgrading to this version of hsd, you must pass `--wallet-migrate=5` when diff --git a/lib/wallet/http.js b/lib/wallet/http.js index 9b21721a2..60c2b36c5 100644 --- a/lib/wallet/http.js +++ b/lib/wallet/http.js @@ -28,6 +28,9 @@ const HDPublicKey = require('../hd/public'); const {Resource} = require('../dns/resource'); const common = require('./common'); +/** @typedef {import('../types').NetworkType} NetworkType */ +/** @typedef {ReturnType} RequestValidator */ + /** * HTTP * @alias module:wallet.HTTP @@ -459,7 +462,7 @@ class HTTP extends Server { this.post('/wallet/:id/send', async (req, res) => { const valid = Validator.fromRequest(req); - const options = TransactionOptions.fromValidator(valid); + const options = TransactionOptions.fromValidator(valid, this.network); const tx = await req.wallet.send(options); const details = await req.wallet.getDetails(tx.hash()); @@ -474,7 +477,7 @@ class HTTP extends Server { // TODO: Add create TX with locks for used Coins and/or // adds to the pending list. - const options = TransactionOptions.fromValidator(valid); + const options = TransactionOptions.fromValidator(valid, this.network); const tx = await req.wallet.createTX(options); if (sign) @@ -496,12 +499,12 @@ class HTTP extends Server { enforce(raw, 'TX is required.'); - const tx = MTX.decode(raw); - tx.view = await req.wallet.getCoinView(tx); + const mtx = MTX.decode(raw); + mtx.view = await req.wallet.getCoinView(mtx); - await req.wallet.sign(tx, passphrase); + await req.wallet.sign(mtx, passphrase); - res.json(200, tx.getJSON(this.network)); + res.json(200, mtx.getJSON(this.network)); }); // Zap Wallet TXs @@ -510,11 +513,14 @@ class HTTP extends Server { const acct = valid.str('account'); const age = valid.u32('age'); - enforce(age, 'Age is required.'); + enforce(age != null, 'Age is required.'); - await req.wallet.zap(acct, age); + const total = await req.wallet.zap(acct, age); - res.json(200, { success: true }); + res.json(200, { + success: true, + zapped: total + }); }); // Abandon Wallet TX @@ -1072,7 +1078,7 @@ class HTTP extends Server { enforce(name, 'Name is required.'); enforce(broadcast ? sign : true, 'Must sign when broadcasting.'); - const options = TransactionOptions.fromValidator(valid); + const options = TransactionOptions.fromValidator(valid, this.network); if (broadcast) { // TODO: Add abort signal to close when request closes. @@ -1109,7 +1115,7 @@ class HTTP extends Server { enforce(lockup != null, 'Lockup is required.'); enforce(broadcast ? sign : true, 'Must sign when broadcasting.'); - const options = TransactionOptions.fromValidator(valid); + const options = TransactionOptions.fromValidator(valid, this.network); if (broadcast) { // TODO: Add abort signal to close when request closes. @@ -1148,7 +1154,7 @@ class HTTP extends Server { enforce(broadcastBid != null, 'broadcastBid is required.'); enforce(broadcastBid ? sign : true, 'Must sign when broadcasting.'); - const options = TransactionOptions.fromValidator(valid); + const options = TransactionOptions.fromValidator(valid, this.network); const auctionTXs = await req.wallet.createAuctionTXs( name, bid, @@ -1189,7 +1195,7 @@ class HTTP extends Server { enforce(broadcast ? sign : true, 'Must sign when broadcasting.'); - const options = TransactionOptions.fromValidator(valid); + const options = TransactionOptions.fromValidator(valid, this.network); if (broadcast) { let tx; @@ -1235,7 +1241,7 @@ class HTTP extends Server { enforce(broadcast ? sign : true, 'Must sign when broadcasting.'); - const options = TransactionOptions.fromValidator(valid); + const options = TransactionOptions.fromValidator(valid, this.network); if (broadcast) { let tx; @@ -1291,7 +1297,7 @@ class HTTP extends Server { return res.json(400); } - const options = TransactionOptions.fromValidator(valid); + const options = TransactionOptions.fromValidator(valid, this.network); if (broadcast) { // TODO: Add abort signal to close when request closes. @@ -1324,7 +1330,7 @@ class HTTP extends Server { enforce(broadcast ? sign : true, 'Must sign when broadcasting.'); enforce(name, 'Must pass name.'); - const options = TransactionOptions.fromValidator(valid); + const options = TransactionOptions.fromValidator(valid, this.network); if (broadcast) { // TODO: Add abort signal to close when request closes. @@ -1358,7 +1364,7 @@ class HTTP extends Server { enforce(address, 'Must pass address.'); const addr = Address.fromString(address, this.network); - const options = TransactionOptions.fromValidator(valid); + const options = TransactionOptions.fromValidator(valid, this.network); if (broadcast) { // TODO: Add abort signal to close when request closes. @@ -1391,7 +1397,7 @@ class HTTP extends Server { enforce(broadcast ? sign : true, 'Must sign when broadcasting.'); enforce(name, 'Must pass name.'); - const options = TransactionOptions.fromValidator(valid); + const options = TransactionOptions.fromValidator(valid, this.network); if (broadcast) { // TODO: Add abort signal to close when request closes. @@ -1424,7 +1430,7 @@ class HTTP extends Server { enforce(broadcast ? sign : true, 'Must sign when broadcasting.'); enforce(name, 'Must pass name.'); - const options = TransactionOptions.fromValidator(valid); + const options = TransactionOptions.fromValidator(valid, this.network); if (broadcast) { // TODO: Add abort signal to close when request closes. @@ -1455,7 +1461,7 @@ class HTTP extends Server { enforce(broadcast ? sign : true, 'Must sign when broadcasting.'); enforce(name, 'Must pass name.'); - const options = TransactionOptions.fromValidator(valid); + const options = TransactionOptions.fromValidator(valid, this.network); if (broadcast) { // TODO: Add abort signal to close when request closes. @@ -1571,7 +1577,6 @@ class HTTP extends Server { /** * Handle new websocket. - * @private * @param {WebSocket} socket */ @@ -1823,22 +1828,24 @@ class TransactionOptions { * TransactionOptions * @alias module:http.TransactionOptions * @constructor - * @param {Validator} valid + * @param {RequestValidator} [valid] + * @param {(NetworkType|Network)?} [network] */ - constructor(valid) { + constructor(valid, network) { if (valid) - return this.fromValidator(valid); + return this.fromValidator(valid, network); } /** * Inject properties from Validator. * @private - * @param {Validator} valid + * @param {RequestValidator} valid + * @param {(NetworkType|Network)?} [network] * @returns {TransactionOptions} */ - fromValidator(valid) { + fromValidator(valid, network) { assert(valid); this.rate = valid.u64('rate'); @@ -1863,10 +1870,11 @@ class TransactionOptions { for (const output of outputs) { const valid = new Validator(output); - let addr = valid.str('address'); + const addrstr = valid.str('address'); + let addr; - if (addr) - addr = Address.fromString(addr, this.network); + if (addrstr) + addr = Address.fromString(addrstr, network); let covenant = valid.obj('covenant'); @@ -1884,15 +1892,16 @@ class TransactionOptions { return this; } - /* + /** * Instantiate transaction options * from Validator. - * @param {Validator} valid + * @param {RequestValidator} [valid] + * @param {(NetworkType|Network)?} [network] * @returns {TransactionOptions} */ - static fromValidator(valid) { - return new this().fromValidator(valid); + static fromValidator(valid, network) { + return new this().fromValidator(valid, network); } } diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index 761a8975e..9ca75cedd 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -4028,7 +4028,7 @@ class TXDB { * Zap pending transactions older than `age`. * @param {Number} acct * @param {Number} age - Age delta. - * @returns {Promise} - zapped tx hashes. + * @returns {Promise} - zapped tx hashes. */ async zap(acct, age) { @@ -4043,7 +4043,7 @@ class TXDB { let txs = await this.listUnconfirmedByTime(acct, options); - const hashes = []; + let zapped = 0; while (txs.length) { for (const wtx of txs) { @@ -4052,13 +4052,13 @@ class TXDB { await this.remove(wtx.hash); - hashes.push(wtx.hash); + zapped++; } txs = await this.listUnconfirmedByTime(acct, options); } - return hashes; + return zapped; } /** diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index 32f85a70f..264e5ecc3 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -5032,7 +5032,7 @@ class Wallet extends EventEmitter { * Zap stale TXs from wallet. * @param {(Number|String)?} acct * @param {Number} age - Age threshold (unix time, default=72 hours). - * @returns {Promise} + * @returns {Promise} */ async zap(acct, age) { @@ -5049,7 +5049,7 @@ class Wallet extends EventEmitter { * @private * @param {(Number|String)?} acct * @param {Number} age - * @returns {Promise} + * @returns {Promise} */ async _zap(acct, age) { diff --git a/test/wallet-http-test.js b/test/wallet-http-test.js index 5d3f83740..8a3e93fc4 100644 --- a/test/wallet-http-test.js +++ b/test/wallet-http-test.js @@ -448,6 +448,61 @@ describe('Wallet HTTP', function() { }); }); + describe('Zap TXs', function() { + const TEST_WALLET = 'test'; + const DEFAULT = 'default'; + const ALT = 'alt'; + + let testWallet; + + const resetPending = async () => { + await wallet.zap(null, 0); + nodeCtx.mempool.reset(); + }; + + before(async () => { + await beforeAll(); + + await wclient.createWallet(TEST_WALLET); + testWallet = wclient.wallet(TEST_WALLET); + + await testWallet.createAccount(ALT); + + await nodeCtx.mineBlocks(10, cbAddress); + }); + + afterEach(resetPending); + + after(afterAll); + + it('should zap all txs (wallet)', async () => { + for (const account of [DEFAULT, ALT]) { + const {address} = await testWallet.createAddress(account); + + for (let i = 0; i < 3; i++) + await wallet.send({outputs: [{address, value: 1e4}]}); + } + + const result = await testWallet.zap(null, 0); + assert.strictEqual(result.zapped, 6); + }); + + it('should zap all txs (account)', async () => { + for (const account of [DEFAULT, ALT]) { + const {address} = await testWallet.createAddress(account); + + for (let i = 0; i < 3; i++) + await wallet.send({outputs: [{address, value: 1e4}]}); + } + + const resultDefault = await testWallet.zap(DEFAULT, 0); + assert.strictEqual(resultDefault.zapped, 3); + + const resultAlt = await testWallet.zap(ALT, 0); + assert.strictEqual(resultAlt.zapped, 3); + }); + }); + describe('Create account (Integration)', function() { before(beforeAll); after(afterAll); @@ -525,18 +580,23 @@ describe('Wallet HTTP', function() { it('should allow covenants with create tx', async () => { const {address} = await wallet.createChange('default'); - const output = openOutput(name, address); + const output = openOutput(name, address, network); - const tx = await wallet.createTX({outputs: [output]}); + const tx = await wallet.createTX({ + outputs: [output.getJSON(network)] + }); assert.equal(tx.outputs[0].covenant.type, types.OPEN); }); it('should allow covenants with send tx', async () => { const {address} = await wallet.createChange('default'); - const output = openOutput(name, address); + const output = openOutput(name, address, network); + + const tx = await wallet.send({ + outputs: [output.getJSON(network)] + }); - const tx = await wallet.send({outputs: [output]});; assert.equal(tx.outputs[0].covenant.type, types.OPEN); }); @@ -2703,12 +2763,12 @@ describe('Wallet HTTP', function() { }); // create an OPEN output -function openOutput(name, address) { +function openOutput(name, address, network) { const nameHash = rules.hashName(name); const rawName = Buffer.from(name, 'ascii'); const output = new Output(); - output.address = Address.fromString(address); + output.address = Address.fromString(address, network); output.value = 0; output.covenant.setOpen(nameHash, rawName); diff --git a/test/wallet-test.js b/test/wallet-test.js index d971e6038..e1dab5944 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -3989,7 +3989,7 @@ describe('Wallet', function() { // Age becomes: 5 - 4 = 1. So, zap will zap all txs with age 1 // - so first 2 txs. const zapped = await wallet.zap(-1, time - 1); - assert.strictEqual(zapped.length, 2); + assert.strictEqual(zapped, 2); const txsAfterZap = await wallet.listUnconfirmed(-1, { limit: 20, @@ -4015,7 +4015,7 @@ describe('Wallet', function() { // two transactions from default (calculation above.) const zapped = await wallet.zap(DEFAULT, time - 3); - assert.strictEqual(zapped.length, 2); + assert.strictEqual(zapped, 2); const txsAfterZap = await wallet.listUnconfirmed(DEFAULT, { limit: 20,