From b9e8e3149b022ab89bf3a011f3e810f89d5da143 Mon Sep 17 00:00:00 2001 From: Bennett Hoffman Date: Fri, 30 Apr 2021 12:35:56 -0400 Subject: [PATCH 1/2] Configuation options for JSON-RPC eth provider --- lib/ethereum.js | 50 +++++++++++++++++++++++++++++++++++++++-------- lib/handover.js | 26 ++++++++++++++++++++---- package-lock.json | 6 ++++++ package.json | 1 + 4 files changed, 71 insertions(+), 12 deletions(-) diff --git a/lib/ethereum.js b/lib/ethereum.js index 848b0f3..4ad67e0 100644 --- a/lib/ethereum.js +++ b/lib/ethereum.js @@ -3,7 +3,7 @@ const ethers = require('ethers'); const {encoding} = require('bns'); -const ENS_ADDRESS = '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e'; +const MAINNET_ENS_ADDRESS = '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e'; const ENS_ABI = [ 'function setOwner(bytes32 node, address owner) external @500000', 'function setSubnodeOwner(bytes32 node, bytes32 label, address owner) external @500000', @@ -26,14 +26,48 @@ const RESOLVER_ABI = [ 'function hasDNSRecords(bytes32 node, bytes32 name) view returns (bool)' ]; +/** + * Ethereum configuration options + * + * @typedef {{ + * provider: 'infura'| 'jsonrpc', + * jsonRpcUrl?: string | ethers.ethers.utils.ConnectionInfo, + * network?: ethers.ethers.providers.Networkish, + * localhostEnsAddress?: string, + * projectId?: string, + * projectSecret?: string, + * }} Options + */ + class Ethereum { + /** + * @param {Options} options + */ constructor(options) { this.keccak256 = ethers.utils.keccak256; this.namehash = ethers.utils.namehash; - this.infura = new ethers.providers.InfuraProvider('homestead', options); - - this.ensRegistry = new ethers.Contract(ENS_ADDRESS, ENS_ABI, this.infura); + /** @type { ethers.providers.Provider & ethers.providers.EnsProvider } */ + let provider; + /** @type { string } */ + let ensAddress; + + switch (options.provider) { + case 'infura': + provider = new ethers.providers.InfuraProvider('homestead', options); + ensAddress = MAINNET_ENS_ADDRESS; + break; + case 'jsonrpc': + provider = new ethers.providers.JsonRpcProvider(options.jsonRpcUrl) + ensAddress = options.localhostEnsAddress; + break; + default: + throw new Error("no provider specified for handover"); + } + + this.provider = provider; + + this.ensRegistry = new ethers.Contract(ensAddress, ENS_ABI, this.provider); this.ensResolver = null; } @@ -48,20 +82,20 @@ class Ethereum { // TODO: cache these async getResolverFromRegistry(name, registry) { const resolverAddr = await registry.resolver(this.namehash(name)); - return new ethers.Contract(resolverAddr, RESOLVER_ABI, this.infura); + return new ethers.Contract(resolverAddr, RESOLVER_ABI, this.provider); } getAbstractEnsRegistry(address) { - return new ethers.Contract(address, ENS_ABI, this.infura); + return new ethers.Contract(address, ENS_ABI, this.provider); } async resolveEnsAddress(name) { - return this.infura.resolveName(name); + return this.provider.resolveName(name); } // https://eips.ethereum.org/EIPS/eip-634 async resolveEnsText(name, key) { - const nameResolver = await this.infura.getResolver(name); + const nameResolver = await this.provider.getResolver(name); if (!nameResolver) return null; diff --git a/lib/handover.js b/lib/handover.js index 5adb7c6..3764ea3 100644 --- a/lib/handover.js +++ b/lib/handover.js @@ -6,21 +6,39 @@ 'use strict'; const {wire, util} = require('bns'); +const { RRSIGRecord } = require('bns/lib/wire'); const {BufferReader} = require('bufio'); const Ethereum = require('./ethereum'); const plugin = exports; +/** + * @typedef { import("hsd/lib/node").FullNode } FullNode + * @typedef { import("bcfg/lib/config")} Config + */ + class Plugin { + /** + * @param { FullNode } node + */ constructor(node) { this.ready = false; this.node = node; this.ns = node.ns; this.logger = node.logger.context('handover'); + /** @type { Config } */ + const config = node.config; + + // For now, just set all the possible options from config, missing ones + // aren't a problem for the unused providers. this.ethereum = new Ethereum({ - projectId: node.config.str('handover-infura-projectid'), - projectSecret: node.config.str('handover-infura-projectsecret') + provider: config.str('handover-provider'), + projectId: config.str('handover-infura-projectid'), + projectSecret: config.str('handover-infura-projectsecret'), + jsonRpcUrl: config.str('handover-jsonrpc-url'), + localhostEnsAddress: config.str('handover-jsonrpc-ens-address'), + network: config.str('handover-network'), }); // Plugin can not operate if node doesn't have DNS resolvers @@ -76,7 +94,7 @@ class Plugin { if (!res.authority.length) return res; - // Check NS records for referals to TLDs `.eth` and `._eth` + // Check NS records for referals to TLDs `.eth`, `._eth`, and `._eth_aliased` for (const rr of res.authority) { if (rr.type !== wire.types.NS) continue; @@ -105,7 +123,7 @@ class Plugin { // Look up an alternate (forked) ENS contract by the Ethereum // address specified in the NS record, and query it for // the user's original request - if (rr.data.ns.slice(-6) === '._eth.') { + if (rr.data.ns.endsWith('._eth.')) { // If the recursive is being minimal, don't look up the name. // Send the SOA back and get the full query from the recursive . if (labels.length < 2) { diff --git a/package-lock.json b/package-lock.json index ee3dab0..84d8c9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -380,6 +380,12 @@ "@ethersproject/strings": "^5.0.8" } }, + "@types/node": { + "version": "14.14.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.43.tgz", + "integrity": "sha512-3pwDJjp1PWacPTpH0LcfhgjvurQvrZFBrC6xxjaUEZ7ifUtT32jtjPxEMMblpqd2Mvx+k8haqQJLQxolyGN/cQ==", + "dev": true + }, "aes-js": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", diff --git a/package.json b/package.json index 812aa81..320bd0e 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "bns": "~0.14.0" }, "devDependencies": { + "@types/node": "^14.14.41", "bmocha": "^2.1.5" } } From 2503bde9de4f90fa4f4b12468065aab107c3adbe Mon Sep 17 00:00:00 2001 From: Bennett Hoffman Date: Fri, 30 Apr 2021 12:51:31 -0400 Subject: [PATCH 2/2] README updates for jsonrpc config --- README.md | 47 +++++++++++++++++++++++++++++++++++++++++++---- lib/handover.js | 3 +-- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 5c1f6b6..6e9c52a 100644 --- a/README.md +++ b/README.md @@ -16,20 +16,22 @@ npm install imperviousinc/handover ``` Your Infura credentials can be passed to the plugin in the same way(s) as all -other `hsd` configuraiton parameters: +other `hsd` configuration parameters: Command line: ``` hsd \ --plugins=handover \ + --handover-provider=infura --handover-infura-projectid=<...> \ --handover-infura-projectsecret=<...> -``` +``` Environment variables: ``` +export HSD_HANDOVER_PROVIDER=infura export HSD_HANDOVER_INFURA_PROJECTID=<...> export HSD_HANDOVER_INFURA_PROJECTSECRET=<...> hsd --plugins handover @@ -40,6 +42,7 @@ Configuration file: `~/.hsd/hsd.conf`: ``` +handover-provider: infura handover-infura-projectid: <...> handover-infura-projectsecret: <...> plugins: handover @@ -98,6 +101,42 @@ $ node '096365727469666965640662616461737300000100010000ea600004b8495201' ``` +## Using a local Ethereum provider + +To use a local Etherum node over JSON-RPC instead of Infura, use these configuration options instead. + +* `handover-jsonrpc-ens-address` is an ethereum address for the ENS registry to use when resolving '.eth' requests. +* `handover-jsonrpc-url` (optional) specify the jsonrpc connection url. If not provided, will use the ethersjs default. + +Command line: + +``` +hsd \ + --plugins=handover \ + --handover-provider=jsonrpc \ + --handover-jsonrpc-url=<...> \ + --handover-jsonrpc-ens-address=<...> +``` + +Environment variables: + +``` +export HSD_HANDOVER_INFURA_PROJECTID=<...> +export HSD_HANDOVER_INFURA_PROJECTSECRET=<...> +hsd --plugins handover +``` + +Configuration file: + +`~/.hsd/hsd.conf`: + +``` +handover-provider: jsonrpc +handover-jsonrpc-url: <...> +handover-jsonrpc-ens-address: <...> +plugins: handover +``` + ## Explanation When `hsd` is run with this plugin, a middleware function is added to the HNS root @@ -122,8 +161,8 @@ request with the full query string (i.e. `certified.badass`). There's still a lot "TODO": -Local Ethereum provider: Currently the plugin relies on the Infura API. However, -it is trivial to add an option to use a local Ethereum full node instead. +More config options: Support running against live testnets, without a '.ens' resolver +(to simplify testing), etc. Cache: The Ethereum interface should cache "resolver" and "registry" contract objects instead of requesting them from the Ethereum provider on each query. diff --git a/lib/handover.js b/lib/handover.js index 3764ea3..f3e7546 100644 --- a/lib/handover.js +++ b/lib/handover.js @@ -6,7 +6,6 @@ 'use strict'; const {wire, util} = require('bns'); -const { RRSIGRecord } = require('bns/lib/wire'); const {BufferReader} = require('bufio'); const Ethereum = require('./ethereum'); @@ -94,7 +93,7 @@ class Plugin { if (!res.authority.length) return res; - // Check NS records for referals to TLDs `.eth`, `._eth`, and `._eth_aliased` + // Check NS records for referals to TLDs `.eth` and `._eth` for (const rr of res.authority) { if (rr.type !== wire.types.NS) continue;