From e47bd6c4abd37cca64f40967e92683f1fb9408a2 Mon Sep 17 00:00:00 2001 From: Mark Tyneway Date: Mon, 10 Jun 2019 19:21:02 -0700 Subject: [PATCH 1/4] test: wallet auction sockets Add tests for socket events based on auction based transactions being indexed in the wallet txdb. --- test/wallet-http-test.js | 128 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 127 insertions(+), 1 deletion(-) diff --git a/test/wallet-http-test.js b/test/wallet-http-test.js index 9ecfcb80c..f92095bdf 100644 --- a/test/wallet-http-test.js +++ b/test/wallet-http-test.js @@ -11,6 +11,7 @@ 'use strict'; const {NodeClient, WalletClient} = require('hs-client'); +const bsock = require('bsock'); const Network = require('../lib/protocol/network'); const FullNode = require('../lib/node/fullnode'); const MTX = require('../lib/primitives/mtx'); @@ -21,9 +22,9 @@ const Output = require('../lib/primitives/output'); const rules = require('../lib/covenants/rules'); const {types} = rules; const secp256k1 = require('bcrypto/lib/secp256k1'); -const network = Network.get('regtest'); const assert = require('bsert'); const common = require('./util/common'); +const network = Network.get('regtest'); const node = new FullNode({ network: 'regtest', @@ -49,6 +50,7 @@ const wallet2 = wclient.wallet('secondary'); let name, cbAddress; const accountTwo = 'foobar'; +let wsocket, wsocket2; const { treeInterval, @@ -70,9 +72,25 @@ describe('Wallet HTTP', function() { await wclient.createWallet('secondary'); cbAddress = (await wallet.createAddress('default')).address; await wallet.createAccount(accountTwo); + + // wsocket listens on wallet channel + wsocket = bsock.connect(network.walletPort); + wsocket.on('connect', async () => { + await wsocket.call('auth', 'foo'); + await wsocket.call('join', wallet.id); + }); + + // wsocket2 listens on wallet2 channel + wsocket2 = bsock.connect(network.walletPort); + wsocket2.on('connect', async () => { + await wsocket2.call('auth', 'foo'); + await wsocket2.call('join', wallet2.id); + }); }); after(async () => { + await wsocket.destroy(); + await wsocket2.destroy(); await nclient.close(); await wclient.close(); await node.close(); @@ -1092,6 +1110,114 @@ describe('Wallet HTTP', function() { assert.equal(ns.info.name, name); assert.equal(ns.info.state, 'REVOKED'); }); + + it('should emit events for covenants', async () => { + const seen = { + open: false, + bid: false, + finalize: false, + reveal: false, + register: false, + update: false, + renew: false, + transfer: false, + revoke: false, + redeem: false + }; + + // Assert that the data is correct that is coming + // over the websocket. Check to make sure the correct + // covenant type is sent, the corret wallet id is sent + // and that the correct namestate is sent + function assertSocketData(wallet, action, walletid, ns, details) { + const covenants = details.outputs.map(o => o.covenant); + assert.ok(covenants.some(c => c.action === action)); + assert.ok(covenants.some(c => c.type === types[action])); + assert.equal(walletid, wallet.id); + assert.equal(ns.name, name); + seen[action.toLowerCase()] = true; + } + + for (const t of Object.keys(seen)) { + const channel = `${t} covenant`; + const action = t.toUpperCase(); + wsocket.bind(channel, (walletid, ns, details) => { + assertSocketData(wallet, action, walletid, ns, details); + }); + + wsocket2.bind(channel, (walletid, ns, details) => { + assertSocketData(wallet2, action, walletid, ns, details); + }); + } + + await wallet.client.post(`/wallet/${wallet.id}/open`, { + name: name + }); + await mineBlocks(treeInterval + 1, cbAddress); + + await wallet.client.post(`/wallet/${wallet.id}/bid`, { + name: name, + bid: 1000, + lockup: 2000 + }); + await wallet.client.post(`/wallet/${wallet2.id}/bid`, { + name: name, + bid: 500, + lockup: 1500 + }); + await mineBlocks(biddingPeriod + 1, cbAddress); + + await wallet.client.post(`/wallet/${wallet.id}/reveal`, { + name: name + }); + await wallet.client.post(`/wallet/${wallet2.id}/reveal`, { + name: name + }); + await mineBlocks(revealPeriod + 1, cbAddress); + + // first update is REGISTER + await wallet.client.post(`/wallet/${wallet.id}/update`, { + name: name, + data: {text: ['foobar']} + }); + await wallet.client.post(`/wallet/${wallet2.id}/redeem`, { + name: name + }); + await mineBlocks(treeInterval + 1, cbAddress); + + const {receiveAddress} = await wallet.getAccount('default'); + + await wallet.client.post(`/wallet/${wallet.id}/transfer`, { + name, + address: receiveAddress + }); + await mineBlocks(transferLockup + 1, cbAddress); + + await wallet.client.post(`/wallet/${wallet.id}/finalize`, { + name + }); + await mineBlocks(1, cbAddress); + + // second update is UPDATE + await wallet.client.post(`/wallet/${wallet.id}/update`, { + name: name, + data: {text: ['foo']} + }); + await mineBlocks(treeInterval + 1, cbAddress); + + await wallet.client.post(`/wallet/${wallet.id}/renewal`, { + name + }); + await mineBlocks(1, cbAddress); + + await wallet.client.post(`/wallet/${wallet.id}/revoke`, { + name + }); + await mineBlocks(1, cbAddress); + + for (const [event, triggered] of Object.entries(seen)) + assert(triggered, `Covenant type ${event} not seen`); + }); }); async function sleep(time) { From 9722b5c6609cc9c8f7ada628e5cb4725369026e5 Mon Sep 17 00:00:00 2001 From: Mark Tyneway Date: Mon, 10 Jun 2019 19:25:48 -0700 Subject: [PATCH 2/4] wallet: emit covenant related events Add a new method `emitCovenants` that emits an event for each auction related output in a transaction that is indexed in the walletdb. --- lib/wallet/txdb.js | 60 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index 3c45aacc7..8f46e9b82 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -22,7 +22,7 @@ const rules = require('../covenants/rules'); const NameState = require('../covenants/namestate'); const NameUndo = require('../covenants/undo'); const {TXRecord} = records; -const {types} = rules; +const {types, typesByVal} = rules; /* * Constants @@ -79,6 +79,63 @@ class TXDB { this.wallet.emit(event, data, details); } + /** + * Emit events for each type of covenant. + * @private + * @param {TXRecord} wtx + * @param {Details} details + */ + + async emitCovenants(tx, details, view, height) { + const network = this.wdb.network; + if (height < 0) + height = null; + + const {outputs} = tx; + for (const output of outputs) { + const {covenant} = output; + if (!covenant.isName()) + continue; + + // view accumulates state in connectNames + const nameHash = covenant.get(0); + const ns = await view.getNameState(this, nameHash); + + // only look up the name if it isn't already populated + if (EMPTY.equals(ns.name) || !ns.name) { + switch (covenant.type) { + case types.CLAIM: + case types.OPEN: + case types.BID: + case types.FINALIZE: { + ns.name = covenant.get(2); + break; + } + case types.REVEAL: + case types.REDEEM: + case types.REGISTER: + case types.UPDATE: + case types.RENEW: + case types.TRANSFER: + case types.REVOKE: { + const nameState = await this.wallet.getNameState(nameHash); + ns.name = nameState.name; + break; + } + } + } + + if (ns.name) + ns.name = ns.name.toString(); + + const nameState = ns.getJSON(height, network); + const {type} = covenant; + const action = typesByVal[type].toLowerCase(); + + this.emit(`${action} covenant`, nameState, details); + } + } + /** * Get wallet path for output. * @param {Output} output @@ -1064,6 +1121,7 @@ class TXDB { // successfully written to disk. this.emit('tx', tx, details); this.emit('balance', balance); + this.emitCovenants(tx, details, view, height); return details; } From e007f14b1fed40e0fb9f880b99b5a136c5eec347 Mon Sep 17 00:00:00 2001 From: Mark Tyneway Date: Mon, 10 Jun 2019 19:27:55 -0700 Subject: [PATCH 3/4] http: websocket events for auctions Emit events over websocket for auction related events that are emitted by the walletdb. --- lib/wallet/http.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/lib/wallet/http.js b/lib/wallet/http.js index 4c2ca6287..832fc9537 100644 --- a/lib/wallet/http.js +++ b/lib/wallet/http.js @@ -1303,6 +1303,21 @@ class HTTP extends Server { this.to('w:*', event, wallet.id, json); }; + const handleCovenant = (event, wallet, ns, details) => { + const name = `w:${wallet.id}`; + + if (!this.channel(name) && !this.channel('w:*')) + return; + + const json = details.getJSON(this.network, this.wdb.height); + + if (this.channel(name)) + this.to(name, event, wallet.id, ns, json); + + if (this.channel('w:*')) + this.to('w:*', event, wallet.id, ns, json); + }; + this.wdb.on('tx', (wallet, tx, details) => { handleTX('tx', wallet, tx, details); }); @@ -1351,6 +1366,16 @@ class HTTP extends Server { if (this.channel('w:*')) this.to('w:*', 'address', wallet.id, json); }); + + // set up listener for each covenant type + for (const type of Object.values(rules.typesByVal)) + if (type !== 'NONE') { + const action = type.toLowerCase(); + const channel = `${action} covenant`; + this.wdb.on(channel, (wallet, data, details) => { + handleCovenant(channel, wallet, data, details); + }); + } } /** From b80bbc58e158c9defa2f832cd37214166d4d379a Mon Sep 17 00:00:00 2001 From: Mark Tyneway Date: Tue, 13 Aug 2019 10:58:03 +0200 Subject: [PATCH 4/4] txdb: remove redundant code --- lib/wallet/txdb.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index 8f46e9b82..e360fe939 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -125,9 +125,6 @@ class TXDB { } } - if (ns.name) - ns.name = ns.name.toString(); - const nameState = ns.getJSON(height, network); const {type} = covenant; const action = typesByVal[type].toLowerCase();