From e26beee44c5cb05744e7225d349f4493fcf9ec08 Mon Sep 17 00:00:00 2001 From: softsimon Date: Tue, 15 Mar 2022 14:44:31 +0100 Subject: [PATCH] Merge node-bitcoin into the project --- backend/package-lock.json | 14 -- backend/package.json | 1 - backend/src/api/bitcoin/bitcoin-client.ts | 2 +- .../src/api/bitcoin/bitcoin-second-client.ts | 2 +- backend/src/rpc-api/commands.ts | 92 ++++++++++ backend/src/rpc-api/index.ts | 61 +++++++ backend/src/rpc-api/jsonrpc.ts | 162 ++++++++++++++++++ 7 files changed, 317 insertions(+), 17 deletions(-) create mode 100644 backend/src/rpc-api/commands.ts create mode 100644 backend/src/rpc-api/index.ts create mode 100644 backend/src/rpc-api/jsonrpc.ts diff --git a/backend/package-lock.json b/backend/package-lock.json index f2ef1abb3..e427bd8ab 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -9,7 +9,6 @@ "version": "2.4.0-dev", "license": "GNU Affero General Public License v3.0", "dependencies": { - "@mempool/bitcoin": "^3.0.3", "@mempool/electrum-client": "^1.1.7", "@types/ws": "8.2.2", "axios": "0.24.0", @@ -56,14 +55,6 @@ "js-tokens": "^4.0.0" } }, - "node_modules/@mempool/bitcoin": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@mempool/bitcoin/-/bitcoin-3.0.3.tgz", - "integrity": "sha512-10UdbwchnevlebDTN+Xhv75AEhDmTMy9UgWHlqx5MG2mheFG6+eqmtHsdxeYnv3IAtTtlRfA6fY0RbV/x4TNFQ==", - "engines": { - "node": ">= 0.10.0" - } - }, "node_modules/@mempool/electrum-client": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/@mempool/electrum-client/-/electrum-client-1.1.8.tgz", @@ -1515,11 +1506,6 @@ "js-tokens": "^4.0.0" } }, - "@mempool/bitcoin": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@mempool/bitcoin/-/bitcoin-3.0.3.tgz", - "integrity": "sha512-10UdbwchnevlebDTN+Xhv75AEhDmTMy9UgWHlqx5MG2mheFG6+eqmtHsdxeYnv3IAtTtlRfA6fY0RbV/x4TNFQ==" - }, "@mempool/electrum-client": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/@mempool/electrum-client/-/electrum-client-1.1.8.tgz", diff --git a/backend/package.json b/backend/package.json index e72d163b2..472b1af0b 100644 --- a/backend/package.json +++ b/backend/package.json @@ -28,7 +28,6 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { - "@mempool/bitcoin": "^3.0.3", "@mempool/electrum-client": "^1.1.7", "@types/ws": "8.2.2", "axios": "0.24.0", diff --git a/backend/src/api/bitcoin/bitcoin-client.ts b/backend/src/api/bitcoin/bitcoin-client.ts index f4aa93bf2..85a410496 100644 --- a/backend/src/api/bitcoin/bitcoin-client.ts +++ b/backend/src/api/bitcoin/bitcoin-client.ts @@ -1,5 +1,5 @@ import config from '../../config'; -import * as bitcoin from '@mempool/bitcoin'; +const bitcoin = require('../../rpc-api/index'); import { BitcoinRpcCredentials } from './bitcoin-api-abstract-factory'; const nodeRpcCredentials: BitcoinRpcCredentials = { diff --git a/backend/src/api/bitcoin/bitcoin-second-client.ts b/backend/src/api/bitcoin/bitcoin-second-client.ts index 6fc00c7c6..39633a317 100644 --- a/backend/src/api/bitcoin/bitcoin-second-client.ts +++ b/backend/src/api/bitcoin/bitcoin-second-client.ts @@ -1,5 +1,5 @@ import config from '../../config'; -import * as bitcoin from '@mempool/bitcoin'; +const bitcoin = require('../../rpc-api/index'); import { BitcoinRpcCredentials } from './bitcoin-api-abstract-factory'; const nodeRpcCredentials: BitcoinRpcCredentials = { diff --git a/backend/src/rpc-api/commands.ts b/backend/src/rpc-api/commands.ts new file mode 100644 index 000000000..ea9bd7bf0 --- /dev/null +++ b/backend/src/rpc-api/commands.ts @@ -0,0 +1,92 @@ +module.exports = { + addMultiSigAddress: 'addmultisigaddress', + addNode: 'addnode', // bitcoind v0.8.0+ + backupWallet: 'backupwallet', + createMultiSig: 'createmultisig', + createRawTransaction: 'createrawtransaction', // bitcoind v0.7.0+ + decodeRawTransaction: 'decoderawtransaction', // bitcoind v0.7.0+ + decodeScript: 'decodescript', + dumpPrivKey: 'dumpprivkey', + dumpWallet: 'dumpwallet', // bitcoind v0.9.0+ + encryptWallet: 'encryptwallet', + estimateFee: 'estimatefee', // bitcoind v0.10.0x + estimatePriority: 'estimatepriority', // bitcoind v0.10.0+ + generate: 'generate', // bitcoind v0.11.0+ + getAccount: 'getaccount', + getAccountAddress: 'getaccountaddress', + getAddedNodeInfo: 'getaddednodeinfo', // bitcoind v0.8.0+ + getAddressesByAccount: 'getaddressesbyaccount', + getBalance: 'getbalance', + getBestBlockHash: 'getbestblockhash', // bitcoind v0.9.0+ + getBlock: 'getblock', + getBlockStats: 'getblockstats', + getBlockFilter: 'getblockfilter', + getBlockchainInfo: 'getblockchaininfo', // bitcoind v0.9.2+ + getBlockCount: 'getblockcount', + getBlockHash: 'getblockhash', + getBlockHeader: 'getblockheader', + getBlockTemplate: 'getblocktemplate', // bitcoind v0.7.0+ + getChainTips: 'getchaintips', // bitcoind v0.10.0+ + getChainTxStats: 'getchaintxstats', + getConnectionCount: 'getconnectioncount', + getDifficulty: 'getdifficulty', + getGenerate: 'getgenerate', + getInfo: 'getinfo', + getMempoolAncestors: 'getmempoolancestors', + getMempoolDescendants: 'getmempooldescendants', + getMempoolEntry: 'getmempoolentry', + getMempoolInfo: 'getmempoolinfo', // bitcoind v0.10+ + getMiningInfo: 'getmininginfo', + getNetTotals: 'getnettotals', + getNetworkInfo: 'getnetworkinfo', // bitcoind v0.9.2+ + getNetworkHashPs: 'getnetworkhashps', // bitcoind v0.9.0+ + getNewAddress: 'getnewaddress', + getPeerInfo: 'getpeerinfo', // bitcoind v0.7.0+ + getRawChangeAddress: 'getrawchangeaddress', // bitcoin v0.9+ + getRawMemPool: 'getrawmempool', // bitcoind v0.7.0+ + getRawTransaction: 'getrawtransaction', // bitcoind v0.7.0+ + getReceivedByAccount: 'getreceivedbyaccount', + getReceivedByAddress: 'getreceivedbyaddress', + getTransaction: 'gettransaction', + getTxOut: 'gettxout', // bitcoind v0.7.0+ + getTxOutProof: 'gettxoutproof', // bitcoind v0.11.0+ + getTxOutSetInfo: 'gettxoutsetinfo', // bitcoind v0.7.0+ + getUnconfirmedBalance: 'getunconfirmedbalance', // bitcoind v0.9.0+ + getWalletInfo: 'getwalletinfo', // bitcoind v0.9.2+ + help: 'help', + importAddress: 'importaddress', // bitcoind v0.10.0+ + importPrivKey: 'importprivkey', + importWallet: 'importwallet', // bitcoind v0.9.0+ + keypoolRefill: 'keypoolrefill', + keyPoolRefill: 'keypoolrefill', + listAccounts: 'listaccounts', + listAddressGroupings: 'listaddressgroupings', // bitcoind v0.7.0+ + listLockUnspent: 'listlockunspent', // bitcoind v0.8.0+ + listReceivedByAccount: 'listreceivedbyaccount', + listReceivedByAddress: 'listreceivedbyaddress', + listSinceBlock: 'listsinceblock', + listTransactions: 'listtransactions', + listUnspent: 'listunspent', // bitcoind v0.7.0+ + lockUnspent: 'lockunspent', // bitcoind v0.8.0+ + move: 'move', + ping: 'ping', // bitcoind v0.9.0+ + prioritiseTransaction: 'prioritisetransaction', // bitcoind v0.10.0+ + sendFrom: 'sendfrom', + sendMany: 'sendmany', + sendRawTransaction: 'sendrawtransaction', // bitcoind v0.7.0+ + sendToAddress: 'sendtoaddress', + setAccount: 'setaccount', + setGenerate: 'setgenerate', + setTxFee: 'settxfee', + signMessage: 'signmessage', + signRawTransaction: 'signrawtransaction', // bitcoind v0.7.0+ + stop: 'stop', + submitBlock: 'submitblock', // bitcoind v0.7.0+ + validateAddress: 'validateaddress', + verifyChain: 'verifychain', // bitcoind v0.9.0+ + verifyMessage: 'verifymessage', + verifyTxOutProof: 'verifytxoutproof', // bitcoind v0.11.0+ + walletLock: 'walletlock', + walletPassphrase: 'walletpassphrase', + walletPassphraseChange: 'walletpassphrasechange' +} diff --git a/backend/src/rpc-api/index.ts b/backend/src/rpc-api/index.ts new file mode 100644 index 000000000..131e1a048 --- /dev/null +++ b/backend/src/rpc-api/index.ts @@ -0,0 +1,61 @@ +var commands = require('./commands') +var rpc = require('./jsonrpc') + +// ===----------------------------------------------------------------------===// +// JsonRPC +// ===----------------------------------------------------------------------===// +function Client (opts) { + // @ts-ignore + this.rpc = new rpc.JsonRPC(opts) +} + +// ===----------------------------------------------------------------------===// +// cmd +// ===----------------------------------------------------------------------===// +Client.prototype.cmd = function () { + var args = [].slice.call(arguments) + var cmd = args.shift() + + callRpc(cmd, args, this.rpc) +} + +// ===----------------------------------------------------------------------===// +// callRpc +// ===----------------------------------------------------------------------===// +function callRpc (cmd, args, rpc) { + var fn = args[args.length - 1] + + // If the last argument is a callback, pop it from the args list + if (typeof fn === 'function') { + args.pop() + } else { + fn = function () {} + } + + return rpc.call(cmd, args, function () { + var args = [].slice.call(arguments) + // @ts-ignore + args.unshift(null) + // @ts-ignore + fn.apply(this, args) + }, function (err) { + fn(err) + }) +} + +// ===----------------------------------------------------------------------===// +// Initialize wrappers +// ===----------------------------------------------------------------------===// +(function () { + for (var protoFn in commands) { + (function (protoFn) { + Client.prototype[protoFn] = function () { + var args = [].slice.call(arguments) + return callRpc(commands[protoFn], args, this.rpc) + } + })(protoFn) + } +})() + +// Export! +module.exports.Client = Client; diff --git a/backend/src/rpc-api/jsonrpc.ts b/backend/src/rpc-api/jsonrpc.ts new file mode 100644 index 000000000..4f7a38baa --- /dev/null +++ b/backend/src/rpc-api/jsonrpc.ts @@ -0,0 +1,162 @@ +var http = require('http') +var https = require('https') + +var JsonRPC = function (opts) { + // @ts-ignore + this.opts = opts || {} + // @ts-ignore + this.http = this.opts.ssl ? https : http +} + +JsonRPC.prototype.call = function (method, params) { + return new Promise((resolve, reject) => { + var time = Date.now() + var requestJSON + + if (Array.isArray(method)) { + // multiple rpc batch call + requestJSON = [] + method.forEach(function (batchCall, i) { + requestJSON.push({ + id: time + '-' + i, + method: batchCall.method, + params: batchCall.params + }) + }) + } else { + // single rpc call + requestJSON = { + id: time, + method: method, + params: params + } + } + + // First we encode the request into JSON + requestJSON = JSON.stringify(requestJSON) + + // prepare request options + var requestOptions = { + host: this.opts.host || 'localhost', + port: this.opts.port || 8332, + method: 'POST', + path: '/', + headers: { + 'Host': this.opts.host || 'localhost', + 'Content-Length': requestJSON.length + }, + agent: false, + rejectUnauthorized: this.opts.ssl && this.opts.sslStrict !== false + } + + if (this.opts.ssl && this.opts.sslCa) { + // @ts-ignore + requestOptions.ca = this.opts.sslCa + } + + // use HTTP auth if user and password set + if (this.opts.user && this.opts.pass) { + // @ts-ignore + requestOptions.auth = this.opts.user + ':' + this.opts.pass + } + + // Now we'll make a request to the server + var cbCalled = false + var request = this.http.request(requestOptions) + + // start request timeout timer + var reqTimeout = setTimeout(function () { + if (cbCalled) return + cbCalled = true + request.abort() + var err = new Error('ETIMEDOUT') + // @ts-ignore + err.code = 'ETIMEDOUT' + reject(err) + }, this.opts.timeout || 30000) + + // set additional timeout on socket in case of remote freeze after sending headers + request.setTimeout(this.opts.timeout || 30000, function () { + if (cbCalled) return + cbCalled = true + request.abort() + var err = new Error('ESOCKETTIMEDOUT') + // @ts-ignore + err.code = 'ESOCKETTIMEDOUT' + reject(err) + }) + + request.on('error', function (err) { + if (cbCalled) return + cbCalled = true + clearTimeout(reqTimeout) + reject(err) + }) + + request.on('response', function (response) { + clearTimeout(reqTimeout) + + // We need to buffer the response chunks in a nonblocking way. + var buffer = '' + response.on('data', function (chunk) { + buffer = buffer + chunk + }) + // When all the responses are finished, we decode the JSON and + // depending on whether it's got a result or an error, we call + // emitSuccess or emitError on the promise. + response.on('end', function () { + var err + + if (cbCalled) return + cbCalled = true + + try { + var decoded = JSON.parse(buffer) + } catch (e) { + if (response.statusCode !== 200) { + err = new Error('Invalid params, response status code: ' + response.statusCode) + err.code = -32602 + reject(err) + } else { + err = new Error('Problem parsing JSON response from server') + err.code = -32603 + reject(err) + } + return + } + + if (!Array.isArray(decoded)) { + decoded = [decoded] + } + + // iterate over each response, normally there will be just one + // unless a batch rpc call response is being processed + decoded.forEach(function (decodedResponse, i) { + if (decodedResponse.hasOwnProperty('error') && decodedResponse.error != null) { + if (reject) { + err = new Error(decodedResponse.error.message || '') + if (decodedResponse.error.code) { + err.code = decodedResponse.error.code + } + reject(err) + } + } else if (decodedResponse.hasOwnProperty('result')) { + // @ts-ignore + resolve(decodedResponse.result, response.headers) + } else { + if (reject) { + err = new Error(decodedResponse.error.message || '') + if (decodedResponse.error.code) { + err.code = decodedResponse.error.code + } + reject(err) + } + } + }) + }) + }) + request.end(requestJSON); + }); +} + +module.exports.JsonRPC = JsonRPC