diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..294b4c3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +## [1.0.0](https://github.com/seegno/bitcoin-core/tree/1.0.0) (2016-03-06) +**Merged pull requests:** + +- Add a better changelog sed [\#10](https://github.com/seegno/bitcoin-core/pull/10) ([ruimarinho](https://github.com/ruimarinho)) +- Use async/await on tests [\#9](https://github.com/seegno/bitcoin-core/pull/9) ([ruimarinho](https://github.com/ruimarinho)) +- Fix Docker hosts in CI environment [\#8](https://github.com/seegno/bitcoin-core/pull/8) ([ruimarinho](https://github.com/ruimarinho)) +- Minor process improvements [\#7](https://github.com/seegno/bitcoin-core/pull/7) ([ruimarinho](https://github.com/ruimarinho)) +- Call `done\(\)` when testing callbacks [\#6](https://github.com/seegno/bitcoin-core/pull/6) ([ruimarinho](https://github.com/ruimarinho)) +- Allow extra ips in docker-compose.yml [\#5](https://github.com/seegno/bitcoin-core/pull/5) ([ruimarinho](https://github.com/ruimarinho)) +- Add support for 0.12 [\#4](https://github.com/seegno/bitcoin-core/pull/4) ([ruimarinho](https://github.com/ruimarinho)) +- Add new 0.12 version methods [\#3](https://github.com/seegno/bitcoin-core/pull/3) ([pedrobranco](https://github.com/pedrobranco)) diff --git a/dist/src/errors/rpc-error.js b/dist/src/errors/rpc-error.js new file mode 100755 index 0000000..0665d7f --- /dev/null +++ b/dist/src/errors/rpc-error.js @@ -0,0 +1,57 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _http = require('http'); + +var _standardError = require('./standard-error'); + +var _standardError2 = _interopRequireDefault(_standardError); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/** + * Export `RpcError` class. + */ + +/** + * Module dependencies. + */ + +class RpcError extends _standardError2.default { + constructor(code, msg, props) { + if (typeof code != 'number') { + throw new TypeError(`Non-numeric HTTP code`); + } + + if (typeof msg == 'object' && msg !== null) { + props = msg; + msg = null; + } + + super(msg || _http.STATUS_CODES[code], props); + + this.code = code; + } + + get status() { + return this.code; + } + + set status(value) { + Object.defineProperty(this, 'status', { + configurable: true, + enumerable: true, + value, + writable: true + }); + } + + toString() { + return `${ this.name }: ${ this.code } ${ this.message }`; + } +} +exports.default = RpcError; +module.exports = exports['default']; \ No newline at end of file diff --git a/dist/src/errors/standard-error.js b/dist/src/errors/standard-error.js new file mode 100755 index 0000000..aa589c5 --- /dev/null +++ b/dist/src/errors/standard-error.js @@ -0,0 +1,23 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _standardError = require('standard-error'); + +var _standardError2 = _interopRequireDefault(_standardError); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/** + * Export `StandardError` class. + */ + +class StandardError extends _standardError2.default {} +exports.default = StandardError; +/** + * Module dependencies. + */ + +module.exports = exports['default']; \ No newline at end of file diff --git a/dist/src/index.js b/dist/src/index.js new file mode 100755 index 0000000..03755e0 --- /dev/null +++ b/dist/src/index.js @@ -0,0 +1,342 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); +/** + * Module dependencies. + */ + +var _lodash = require('lodash'); + +var _lodash2 = _interopRequireDefault(_lodash); + +var _parser = require('./parser'); + +var _parser2 = _interopRequireDefault(_parser); + +var _requester = require('./requester'); + +var _requester2 = _interopRequireDefault(_requester); + +var _bluebird = require('bluebird'); + +var _bluebird2 = _interopRequireDefault(_bluebird); + +var _methods = require('./methods'); + +var _methods2 = _interopRequireDefault(_methods); + +var _request = require('request'); + +var _request2 = _interopRequireDefault(_request); + +var _semver = require('semver'); + +var _semver2 = _interopRequireDefault(_semver); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/** + * Source arguments to find out if a callback has been passed. + */ + +function source() { + for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + const last = _lodash2.default.last(args); + + let callback; + + if (_lodash2.default.isFunction(last)) { + callback = last; + args = _lodash2.default.dropRight(args); + } + + return [args, callback]; +} + +/** + * List of networks and their default port mapping. + */ + +const networks = { + mainnet: 8332, + regtest: 18332, + testnet: 18332 +}; + +/** + * Constructor. + */ + +class Client { + constructor() { + var _ref = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + let agentOptions = _ref.agentOptions; + var _ref$headers = _ref.headers; + let headers = _ref$headers === undefined ? false : _ref$headers; + var _ref$host = _ref.host; + let host = _ref$host === undefined ? 'localhost' : _ref$host; + var _ref$network = _ref.network; + let network = _ref$network === undefined ? 'mainnet' : _ref$network; + let password = _ref.password; + let port = _ref.port; + var _ref$ssl = _ref.ssl; + let ssl = _ref$ssl === undefined ? false : _ref$ssl; + var _ref$timeout = _ref.timeout; + let timeout = _ref$timeout === undefined ? 30000 : _ref$timeout; + let username = _ref.username; + let version = _ref.version; + + if (!_lodash2.default.has(networks, network)) { + throw new Error(`Invalid network name "${ network }"`, { network }); + } + + this.agentOptions = agentOptions; + this.auth = (password || username) && { pass: password, user: username }; + this.headers = headers; + this.host = host; + this.password = password; + this.port = port || networks[network]; + this.timeout = timeout; + this.ssl = { + enabled: _lodash2.default.get(ssl, 'enabled', ssl), + strict: _lodash2.default.get(ssl, 'strict', _lodash2.default.get(ssl, 'enabled', ssl)) + }; + + // Find unsupported methods according to version. + let unsupported = []; + + if (version) { + unsupported = _lodash2.default.chain(_methods2.default).pickBy(range => !_semver2.default.satisfies(version, range)).keys().invokeMap(String.prototype.toLowerCase).value(); + } + + this.request = _bluebird2.default.promisifyAll(_request2.default.defaults({ + agentOptions: this.agentOptions, + baseUrl: `${ this.ssl.enabled ? 'https' : 'http' }://${ this.host }:${ this.port }`, + json: true, + strictSSL: this.ssl.strict, + timeout: this.timeout + }), { multiArgs: true }); + this.requester = new _requester2.default({ unsupported, version }); + this.parser = new _parser2.default({ headers: this.headers }); + } + + /** + * Execute `rpc` command. + */ + + command() { + let body; + let callback; + + for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + args[_key2] = arguments[_key2]; + } + + let parameters = _lodash2.default.tail(args); + const input = _lodash2.default.head(args); + const lastArg = _lodash2.default.last(args); + + if (_lodash2.default.isFunction(lastArg)) { + callback = lastArg; + parameters = _lodash2.default.dropRight(parameters); + } + + return _bluebird2.default.try(() => { + if (Array.isArray(input)) { + body = input.map((method, index) => this.requester.prepare({ method: method.method, parameters: method.parameters, suffix: index })); + } else { + body = this.requester.prepare({ method: input, parameters }); + } + + return this.request.postAsync({ + auth: _lodash2.default.pickBy(this.auth, _lodash2.default.identity), + body, + uri: '/' + }).bind(this).then(this.parser.rpc); + }).asCallback(callback); + } + + /** + * Given a transaction hash, returns a transaction in binary, hex-encoded binary, or JSON formats. + */ + + getTransactionByHash() { + var _source = source.apply(undefined, arguments); + + var _source2 = _slicedToArray(_source, 2); + + var _source2$ = _slicedToArray(_source2[0], 2); + + const hash = _source2$[0]; + var _source2$$ = _source2$[1]; + _source2$$ = _source2$$ === undefined ? {} : _source2$$; + var _source2$$$extension = _source2$$.extension; + const extension = _source2$$$extension === undefined ? 'json' : _source2$$$extension; + const callback = _source2[1]; + + + return _bluebird2.default.try(() => { + return this.request.getAsync(`/rest/tx/${ hash }.${ extension }`).bind(this).then(this.parser.rest); + }).asCallback(callback); + } + + /** + * Given a block hash, returns a block, in binary, hex-encoded binary or JSON formats. + * With `summary` set to `false`, the JSON response will only contain the transaction + * hash instead of the complete transaction details. The option only affects the JSON response. + */ + + getBlockByHash() { + var _source3 = source.apply(undefined, arguments); + + var _source4 = _slicedToArray(_source3, 2); + + var _source4$ = _slicedToArray(_source4[0], 2); + + const hash = _source4$[0]; + var _source4$$ = _source4$[1]; + _source4$$ = _source4$$ === undefined ? {} : _source4$$; + var _source4$$$summary = _source4$$.summary; + const summary = _source4$$$summary === undefined ? false : _source4$$$summary; + var _source4$$$extension = _source4$$.extension; + const extension = _source4$$$extension === undefined ? 'json' : _source4$$$extension; + const callback = _source4[1]; + + + return _bluebird2.default.try(() => { + return this.request.getAsync(`/rest/block${ summary ? '/notxdetails/' : '/' }${ hash }.${ extension }`).bind(this).then(this.parser.rest); + }).asCallback(callback); + } + + /** + * Given a block hash, returns amount of blockheaders in upward direction. + */ + + getBlockHeadersByHash() { + var _source5 = source.apply(undefined, arguments); + + var _source6 = _slicedToArray(_source5, 2); + + var _source6$ = _slicedToArray(_source6[0], 3); + + const hash = _source6$[0]; + const count = _source6$[1]; + var _source6$$ = _source6$[2]; + _source6$$ = _source6$$ === undefined ? {} : _source6$$; + var _source6$$$extension = _source6$$.extension; + const extension = _source6$$$extension === undefined ? 'json' : _source6$$$extension; + const callback = _source6[1]; + + + return _bluebird2.default.try(() => { + if (!_lodash2.default.includes(['bin', 'hex'], extension)) { + throw new Error(`Extension "${ extension }" is not supported`); + } + + return this.request.getAsync(`/rest/headers/${ count }/${ hash }.${ extension }`).bind(this).then(this.parser.rest); + }).asCallback(callback); + } + + /** + * Returns various state info regarding block chain processing. + * Only supports JSON as output format. + */ + + getBlockchainInformation() { + var _source7 = source.apply(undefined, arguments); + + var _source8 = _slicedToArray(_source7, 2); + + const callback = _source8[1]; + + + return this.request.getAsync(`/rest/chaininfo.json`).bind(this).then(this.parser.rest).asCallback(callback); + } + + /** + * Query unspent transaction outputs for a given set of outpoints. + * See BIP64 for input and output serialisation: + * - https://github.com/bitcoin/bips/blob/master/bip-0064.mediawiki + */ + + getUnspentTransactionOutputs() { + var _source9 = source.apply(undefined, arguments); + + var _source10 = _slicedToArray(_source9, 2); + + var _source10$ = _slicedToArray(_source10[0], 2); + + const outpoints = _source10$[0]; + var _source10$$ = _source10$[1]; + _source10$$ = _source10$$ === undefined ? {} : _source10$$; + var _source10$$$extension = _source10$$.extension; + const extension = _source10$$$extension === undefined ? 'json' : _source10$$$extension; + const callback = _source10[1]; + + + const sets = _lodash2.default.flatten([outpoints]).map(outpoint => { + return `${ outpoint.id }-${ outpoint.index }`; + }).join('/'); + + return this.request.getAsync(`/rest/getutxos/checkmempool/${ sets }.${ extension }`).bind(this).then(this.parser.rest).asCallback(callback); + } + + /** + * Returns transactions in the transaction memory pool. + * Only supports JSON as output format. + */ + + getMemoryPoolContent() { + var _source11 = source.apply(undefined, arguments); + + var _source12 = _slicedToArray(_source11, 2); + + const callback = _source12[1]; + + + return this.request.getAsync('/rest/mempool/contents.json').bind(this).then(this.parser.rest).asCallback(callback); + } + + /** + * Returns various information about the transaction memory pool. + * Only supports JSON as output format. + * + * - size: the number of transactions in the transaction memory pool. + * - bytes: size of the transaction memory pool in bytes. + * - usage: total transaction memory pool memory usage. + */ + + getMemoryPoolInformation() { + var _source13 = source.apply(undefined, arguments); + + var _source14 = _slicedToArray(_source13, 2); + + const callback = _source14[1]; + + + return this.request.getAsync('/rest/mempool/info.json').bind(this).then(this.parser.rest).asCallback(callback); + } +} + +/** + * Add all known RPC methods. + */ + +_lodash2.default.forOwn(_methods2.default, (range, method) => { + Client.prototype[method] = _lodash2.default.partial(Client.prototype.command, method.toLowerCase()); +}); + +/** + * Export Client class. + */ + +exports.default = Client; +module.exports = exports['default']; \ No newline at end of file diff --git a/dist/src/methods.js b/dist/src/methods.js new file mode 100755 index 0000000..3f38c68 --- /dev/null +++ b/dist/src/methods.js @@ -0,0 +1,108 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +/* eslint-disable no-inline-comments */ + +/** + * Export available rpc methods. + */ + +exports.default = { + abandonTransaction: '>=0.12.0', + addMultiSigAddress: '>=0.1.0', + addNode: '>=0.8.0', + backupWallet: '>=0.3.12', + clearBanned: '>=0.12.0', + createMultiSig: '>=0.1.0', + createRawTransaction: '>=0.7.0', + decodeRawTransaction: '>=0.7.0', + decodeScript: '>=0.9.0', + disconnectNode: '>=0.12.0', + dumpPrivKey: '>=0.6.0', + dumpWallet: '>=0.9.0', + encryptWallet: '>=0.1.0', + estimateFee: '>=0.10.0', + estimatePriority: '>=0.10.0', + estimateSmartFee: '>=0.12.0', + estimateSmartPriority: '>=0.12.0', + fundRawTransaction: '>=0.12.0', + generate: '>=0.11.0', + getAccount: '>=0.1.0', + getAccountAddress: '>=0.3.18', + getAddedNodeInfo: '>=0.8.0', + getAddressesByAccount: '>=0.1.0', + getBalance: '>=0.3.18', + getBestBlockHash: '>=0.9.0', + getBlock: '>=0.6.0', + getBlockCount: '>=0.1.0', + getBlockHash: '>=0.6.0', + getBlockHeader: '>=0.12.0', + getBlockTemplate: '>=0.7.0', + getBlockchainInfo: '>=0.9.2', + getChainTips: '>=0.10.0', + getConnectionCount: '>=0.1.0', + getDifficulty: '>=0.1.0', + getGenerate: '>=0.1.0', + getHashesPerSec: '<0.10.0', + getInfo: '>=0.1.0', + getMempoolInfo: '>=0.10.0', + getMiningInfo: '>=0.6.0', + getNetTotals: '>=0.1.0', + getNetworkHashPs: '>=0.9.0', + getNetworkInfo: '>=0.9.2', + getNewAddress: '>=0.1.0', + getPeerInfo: '>=0.7.0', + getRawChangeAddress: '>=0.9.0', + getRawMemPool: '>=0.7.0', + getRawTransaction: '>=0.7.0', + getReceivedByAccount: '>=0.1.0', + getReceivedByAddress: '>=0.1.0', + getTransaction: '>=0.1.0', + getTxOut: '>=0.7.0', + getTxOutProof: '>=0.11.0', + getTxOutSetInfo: '>=0.7.0', + getUnconfirmedBalance: '>=0.9.0', + getWalletInfo: '>=0.9.2', + getWork: '<0.10.0', + help: '>=0.1.0', + importAddress: '>=0.10.0', + importPrivKey: '>=0.6.0', + importPubKey: '>=0.12.0', + importWallet: '>=0.9.0', + keypoolRefill: '>=0.1.0', + listAccounts: '>=0.1.0', + listAddressGroupings: '>=0.7.0', + listBanned: '>=0.12.0', + listLockUnspent: '>=0.8.0', + listReceivedByAccount: '>=0.1.0', + listReceivedByAddress: '>=0.1.0', + listSinceBlock: '>=0.5.0', + listTransactions: '>=0.3.18', + listUnspent: '>=0.7.0', + lockUnspent: '>=0.8.0', + move: '>=0.3.18', + ping: '>=0.9.0', + prioritiseTransaction: '>=0.10.0', + sendFrom: '>=0.3.18', + sendMany: '>=0.3.21', + sendRawTransaction: '>=0.7.0', + sendToAddress: '>=0.1.0', + setAccount: '>=0.1.0', + setBan: '>=0.12.0', + setGenerate: '>=0.1.0', + setTxFee: '>=0.3.22', + signMessage: '>=0.5.0', + signRawTransaction: '>=0.7.0', + stop: '>=0.1.0', + submitBlock: '>=0.7.0', + validateAddress: '>=0.3.14', + verifyChain: '>=0.9.0', + verifyMessage: '>=0.5.0', + verifyTxOutProof: '>0.11.0', + walletLock: '>=0.1.0', + walletPassphrase: '>=0.1.0', + walletPassphraseChange: '>=0.1.0' +}; +module.exports = exports['default']; \ No newline at end of file diff --git a/dist/src/parser.js b/dist/src/parser.js new file mode 100644 index 0000000..5ceec84 --- /dev/null +++ b/dist/src/parser.js @@ -0,0 +1,118 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); +/** + * Module dependencies. + */ + +var _lodash = require('lodash'); + +var _lodash2 = _interopRequireDefault(_lodash); + +var _rpcError = require('./errors/rpc-error'); + +var _rpcError2 = _interopRequireDefault(_rpcError); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/** + * Get response result and errors. + */ + +function get(body) { + var _ref = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var _ref$headers = _ref.headers; + let headers = _ref$headers === undefined ? false : _ref$headers; + let response = _ref.response; + + if (!body) { + throw new _rpcError2.default(response.statusCode, response.statusMessage); + } + + if (body.error !== null) { + throw new _rpcError2.default(_lodash2.default.get(body, 'error.code', -32603), _lodash2.default.get(body, 'error.message', 'An error occurred while processing the RPC call to bitcoind')); + } + + if (!_lodash2.default.has(body, 'result')) { + throw new _rpcError2.default(-32700, 'Missing `result` on the RPC call result'); + } + + if (headers) { + return [body.result, response.headers]; + } + + return body.result; +} + +/** + * Export Parser class. + */ + +class Parser { + constructor() { + var _ref2 = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + let headers = _ref2.headers; + + this.headers = headers; + } + + /** + * Parse rpc response. + */ + + rpc(_ref3) { + var _ref4 = _slicedToArray(_ref3, 2); + + let response = _ref4[0]; + let body = _ref4[1]; + + // Body contains HTML (e.g. 401 Unauthorized). + if (typeof body === 'string' && response.statusCode !== 200) { + throw new _rpcError2.default(response.statusCode); + } + + if (!Array.isArray(body)) { + return get(body, { headers: this.headers, response }); + } + + // Batch response parsing where each response may or may not be successful. + const batch = body.map(response => { + try { + return get(response, { headers: false, response }); + } catch (e) { + return e; + } + }); + + if (this.headers) { + return [batch, response.headers]; + } + + return batch; + } + + rest(_ref5) { + var _ref6 = _slicedToArray(_ref5, 2); + + let response = _ref6[0]; + let body = _ref6[1]; + + if (body.error) { + throw new _rpcError2.default(body.error.code, body.error.message); + } + + if (this.headers) { + return [body, response.headers]; + } + + return body; + } +} +exports.default = Parser; +module.exports = exports['default']; \ No newline at end of file diff --git a/dist/src/requester.js b/dist/src/requester.js new file mode 100644 index 0000000..c9dd169 --- /dev/null +++ b/dist/src/requester.js @@ -0,0 +1,53 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _lodash = require('lodash'); + +/** + * Export Requester class. + */ + +class Requester { + constructor() { + var _ref = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + var _ref$unsupported = _ref.unsupported; + let unsupported = _ref$unsupported === undefined ? [] : _ref$unsupported; + let version = _ref.version; + + this.unsupported = unsupported; + this.version = version; + } + + /** + * Prepare rpc request. + */ + + prepare(_ref2) { + let method = _ref2.method; + var _ref2$parameters = _ref2.parameters; + let parameters = _ref2$parameters === undefined ? [] : _ref2$parameters; + let suffix = _ref2.suffix; + + method = method.toLowerCase(); + + if (this.version && (0, _lodash.includes)(this.unsupported, method)) { + throw new Error(`Method "${ method }" is not supported by version "${ this.version }"`); + } + + return { + id: `${ Date.now() }${ suffix !== undefined ? `-${ suffix }` : '' }`, + method, + params: parameters + }; + } +} +exports.default = Requester; +/** + * Module dependencies. + */ + +module.exports = exports['default']; \ No newline at end of file diff --git a/package.json b/package.json index 2cdd2fe..0f7edeb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bitcoin-core", - "version": "0.0.0", + "version": "1.0.0", "description": "A modern Bitcoin Core REST and RPC client.", "author": { "name": "Rui Marinho",