From 255851fc8fe6d44fce83e0dc39ef1053192f2e2a Mon Sep 17 00:00:00 2001 From: Mark Tyneway Date: Tue, 15 Oct 2019 22:44:05 -0700 Subject: [PATCH 1/4] records: rrTypesByType --- lib/dns/records.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/lib/dns/records.js b/lib/dns/records.js index 736e45b04..f830188ad 100644 --- a/lib/dns/records.js +++ b/lib/dns/records.js @@ -74,6 +74,35 @@ const types = { ADDR: 21 // TXT }; +/** + * DNS RR Types by Handshake + * record type. + * @constant + */ + +const rrTypesByType = { + [types.INET4]: wire.types.A, + [types.INET6]: wire.types.AAAA, + [types.ONION]: wire.types.TXT, + [types.ONIONNG]: wire.types.TXT, + [types.NAME]: wire.types.CNAME, + [types.CANONICAL]: wire.types.CNAME, + [types.DELEGATE]: wire.types.DNAME, + [types.NS]: wire.types.NS, + [types.SERVICE]: wire.types.SRV, + [types.URI]: wire.types.URI, + [types.EMAIL]: wire.types.RP, + [types.TEXT]: wire.types.TXT, + [types.LOCATION]: wire.types.LOC, + [types.MAGNET]: wire.types.URI, + [types.DS]: wire.types.DS, + [types.TLS]: wire.types.TLSA, + [types.SMIME]: wire.types.SMIMEA, + [types.SSH]: wire.types.SSHFP, + [types.PGP]: wire.types.OPENPGPKEY, + [types.ADDR]: wire.types.URI +}; + /** * Target * @extends {Struct} @@ -1311,6 +1340,7 @@ function isSingle(label) { */ records.types = types; +records.rrTypesByType = rrTypesByType; records.Target = Target; records.Service = Service; From 168c6ae4fe1084fb9c08b9fa5d407f16b13291ac Mon Sep 17 00:00:00 2001 From: Mark Tyneway Date: Tue, 15 Oct 2019 22:44:25 -0700 Subject: [PATCH 2/4] node/rpc: dumpzone experiment --- lib/node/rpc.js | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/lib/node/rpc.js b/lib/node/rpc.js index 2618cce7d..0f008f701 100644 --- a/lib/node/rpc.js +++ b/lib/node/rpc.js @@ -8,6 +8,7 @@ const assert = require('bsert'); const bweb = require('bweb'); +const bns = require('bns'); const {Lock} = require('bmutex'); const IP = require('binet'); const Validator = require('bval'); @@ -36,6 +37,7 @@ const consensus = require('../protocol/consensus'); const pkg = require('../pkg'); const rules = require('../covenants/rules'); const Resource = require('../dns/resource'); +const Records = require('../dns/records'); const NameState = require('../covenants/namestate'); const AirdropProof = require('../primitives/airdropproof'); const {EXP} = consensus; @@ -243,6 +245,8 @@ class RPC extends RPCBase { this.add('sendrawclaim', this.sendRawClaim); this.add('sendrawairdrop', this.sendRawAirdrop); + this.add('dumpzone', this.dumpzone); + // Compat // this.add('getnameinfo', this.getNameInfo); // this.add('getnameresource', this.getNameResource); @@ -2314,6 +2318,46 @@ class RPC extends RPCBase { return items; } + async dumpzone(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'dumpzone'); + + // TODO: clean some of this up, dead code + const network = this.network; + const height = this.chain.height; + const txn = this.chain.db.txn; + const items = []; + + // TODO: + // open up a stream to a file + // write the file to disk + + const iter = txn.iterator(); + + while (await iter.next()) { + const {key, value} = iter; + const ns = NameState.decode(value); + + const resource = Resource.decode(ns.data); + const fqdn = bns.util.fqdn(ns.name.toString('ascii')); + + const rrTypesByType = Records.rrTypesByType; + + for (const [hs, rrtype] of Object.entries(rrTypesByType)) { + const dns = resource.toDNS(fqdn, rrtype); + + for (const rr of dns.records()) { + const entry = rr.toString(); + console.log(entry); + } + } + } + + // see backup wallet for example + // of return type + return items; + } + async getNameInfo(args, help) { if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'getnameinfo "name"'); From 1c89c611bd269f027f512f27648bf0562fd2df53 Mon Sep 17 00:00:00 2001 From: Mark Tyneway Date: Tue, 15 Oct 2019 22:44:54 -0700 Subject: [PATCH 3/4] node/http: get /zonefile --- lib/node/http.js | 57 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/lib/node/http.js b/lib/node/http.js index 380c47745..f43c427aa 100644 --- a/lib/node/http.js +++ b/lib/node/http.js @@ -9,6 +9,7 @@ const assert = require('bsert'); const path = require('path'); const {Server} = require('bweb'); +const bns = require('bns'); const Validator = require('bval'); const {base58} = require('bstring'); const {BloomFilter} = require('bfilter'); @@ -19,6 +20,9 @@ const util = require('../utils/util'); const TX = require('../primitives/tx'); const Claim = require('../primitives/claim'); const Address = require('../primitives/address'); +const Records = require('../dns/records'); +const Resource = require('../dns/resource'); +const NameState = require('../covenants/namestate'); const Network = require('../protocol/network'); const pkg = require('../pkg'); @@ -404,6 +408,59 @@ class HTTP extends Server { res.json(200, { success: true }); }); + this.get('/zonefile', async (req, res) => { + const valid = Validator.fromRequest(req); + + // TODO: add param for zone name, default to '.' + // DNSSEC on/off + // height param (figure out reversing tree) + // goal is that client can call response.join('\n') + // and then write that to disk and have a valid + // zone file + + const txn = this.chain.db.txn; + const iter = txn.iterator(); + + res.setType('json'); + res.write('['); + + // TODO: generate header of zonefile + + while (await iter.next()) { + const {key, value} = iter; + const ns = NameState.decode(value); + + const resource = Resource.decode(ns.data); + const fqdn = bns.util.fqdn(ns.name.toString('ascii')); + + const rrTypesByType = Records.rrTypesByType; + const rrTypes = new Set(Object.values(rrTypesByType)); + + let seen = 0; + for (const rrtype of rrTypes) { + const msg = resource.toDNS(fqdn, rrtype); + + // TODO + // potentially ignore authority field + // it is causing a lot of duplicates + // or handle it as special case + for (const rr of msg.records()) { + const entry = rr.toString(); + + if (seen === 0) + res.write(`"${entry}"`); + else + res.write(`,"${entry}"`); + + seen++; + } + } + } + + res.write(']'); + res.end(); + }); + // Estimate fee this.get('/fee', async (req, res) => { const valid = Validator.fromRequest(req); From 3677c11ff75852d372ef094bdb87ba3524847706 Mon Sep 17 00:00:00 2001 From: Mark Tyneway Date: Tue, 15 Oct 2019 22:46:32 -0700 Subject: [PATCH 4/4] test: experiment with get zonefile --- test/node-http-test.js | 135 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 test/node-http-test.js diff --git a/test/node-http-test.js b/test/node-http-test.js new file mode 100644 index 000000000..6fc11705b --- /dev/null +++ b/test/node-http-test.js @@ -0,0 +1,135 @@ +/** + * + */ + +'use strict'; + +const {NodeClient,WalletClient} = require('hs-client'); +const Network = require('../lib/protocol/network'); +const FullNode = require('../lib/node/fullnode'); +const common = require('./util/common'); +const rules = require('../lib/covenants/rules'); + +const network = Network.get('regtest'); + +const { + treeInterval, + biddingPeriod, + revealPeriod +} = network.names; + +const ports = { + p2p: 14331, + node: 14332, + wallet: 14333 +}; + +const node = new FullNode({ + network: network.type, + apiKey: 'bar', + walletAuth: true, + memory: true, + port: ports.p2p, + httpPort: ports.node, + workers: true, + plugins: [require('../lib/wallet/plugin')], + env: { + 'HSD_WALLET_HTTP_PORT': ports.wallet.toString() + } +}); + +const nclient = new NodeClient({ + port: ports.node, + apiKey: 'bar' +}); + +const wclient = new WalletClient({ + port: ports.wallet, + apiKey: 'bar' +}); + +const wallet = wclient.wallet('primary'); + +let coinbase, name; + +describe('Node RPC Methods', function() { + this.timeout(20000); + + before(async () => { + await node.open(); + await nclient.open(); + await wclient.open(); + + // mine a bunch of blocks + const info = await wallet.createAddress('default'); + coinbase = info.address; + + await mineBlocks(10, coinbase); + + name = rules.grindName(3, 1, network); + await doAuction(name); + }); + + after(async () => { + await nclient.close(); + await wclient.close(); + await node.close(); + }); + + it('should get zonefile', async () => { + const res = await nclient.get('/zonefile') + console.log(res) + }); +}); + +async function doAuction(name) { + await wallet.client.post(`/wallet/${wallet.id}/open`, { + name: name + }); + + await mineBlocks(treeInterval + 1, coinbase); + + await wallet.client.post(`/wallet/${wallet.id}/bid`, { + name: name, + bid: 1000, + lockup: 2000 + }); + + await mineBlocks(biddingPeriod + 1, coinbase); + + await wallet.client.post(`/wallet/${wallet.id}/reveal`, { + name: name + }); + + await mineBlocks(revealPeriod + 1, coinbase); + + await wallet.client.post(`/wallet/${wallet.id}/update`, { + name: name, + data: { + hosts: ['192.168.0.91'], + ns: [ + 'ns1.cloudflare.com@1.2.3.4', + 'ns2.cloudflare.com@1.2.4.4' + ], + ttl: 64 + } + }); + + await mineBlocks(treeInterval, coinbase); +} + +/** + * Mine blocks and take into + * account race conditions + */ + +async function mineBlocks(count, address) { + for (let i = 0; i < count; i++) { + const obj = { complete: false }; + node.once('block', () => { + obj.complete = true; + }); + await nclient.execute('generatetoaddress', [1, address]); + await common.forValue(obj, 'complete', true); + } +}