UNPKG

@esteemapp/dhive

Version:

Hive blockchain RPC client library

223 lines (222 loc) 9.83 kB
"use strict"; /** * @file Hive RPC client implementation. * @author Johan Nordberg <code@johan-nordberg.com> * @license * Copyright (c) 2017 Johan Nordberg. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistribution of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistribution in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * You acknowledge that this software is not designed, licensed or intended for use * in the design, construction, operation or maintenance of any military facility. */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const assert = require("assert"); const verror_1 = require("verror"); const version_1 = require("./version"); const blockchain_1 = require("./helpers/blockchain"); const broadcast_1 = require("./helpers/broadcast"); const database_1 = require("./helpers/database"); const hivemind_1 = require("./helpers/hivemind"); const rc_1 = require("./helpers/rc"); const utils_1 = require("./utils"); const serializer_1 = require("./chain/serializer"); /** * Library version. */ exports.VERSION = version_1.default; /** * Main Hive network chain id. */ exports.DEFAULT_CHAIN_ID = Buffer.from('beeab0de00000000000000000000000000000000000000000000000000000000', 'hex'); /** * Main Hive network address prefix. */ exports.DEFAULT_ADDRESS_PREFIX = 'STM'; /** * RPC Client * ---------- * Can be used in both node.js and the browser. Also see {@link ClientOptions}. */ class Client { /** * @param address The address to the Hive RPC server, * e.g. `https://api.hive.blog`. or [`https://api.hive.blog`, `https://another.api.com`] * @param options Client options. */ constructor(address, options = {}) { exports.rebrandedApiGlobal = options.rebrandedApi || false; serializer_1.updateOperations(); this.currentAddress = Array.isArray(address) ? address[0] : address; this.address = address; this.options = options; this.chainId = options.chainId ? Buffer.from(options.chainId, 'hex') : exports.DEFAULT_CHAIN_ID; assert.equal(this.chainId.length, 32, 'invalid chain id'); this.addressPrefix = options.addressPrefix || exports.DEFAULT_ADDRESS_PREFIX; this.timeout = options.timeout || 60 * 1000; this.backoff = options.backoff || defaultBackoff; this.failoverThreshold = options.failoverThreshold || 3; this.database = new database_1.DatabaseAPI(this); this.broadcast = new broadcast_1.BroadcastAPI(this); this.blockchain = new blockchain_1.Blockchain(this); this.rc = new rc_1.RCAPI(this); this.hivemind = new hivemind_1.HivemindAPI(this); } /** * Create a new client instance configured for the testnet. */ static testnet(options) { let opts = {}; if (options) { opts = utils_1.copy(options); opts.agent = options.agent; } // Testnet details: https://gitlab.syncad.com/hive/hive/-/issues/36 opts.addressPrefix = 'STM'; opts.chainId = 'beeab0de00000000000000000000000000000000000000000000000000000000'; return new Client('https://hive-test-beeabode.roelandp.nl', opts); } /** * Make a RPC call to the server. * * @param api The API to call, e.g. `database_api`. * @param method The API method, e.g. `get_dynamic_global_properties`. * @param params Array of parameters to pass to the method, optional. * */ call(api, method, params = []) { return __awaiter(this, void 0, void 0, function* () { let request; if (api === 'bridge') { request = { id: 0, jsonrpc: '2.0', method: api + '.' + method, params }; } else { request = { id: '0', jsonrpc: '2.0', method: 'call', params: [api, method, params] }; } const body = JSON.stringify(request, (key, value) => { // encode Buffers as hex strings instead of an array of bytes if (typeof value === 'object' && value.type === 'Buffer') { return Buffer.from(value.data).toString('hex'); } return value; }); const opts = { body, cache: 'no-cache', method: 'POST', mode: 'cors' }; // Self is not defined within Node environments // This check is needed because the user agent cannot be set in a browser if (typeof self === undefined) { opts.headers = { 'User-Agent': `dhive/${version_1.default}` }; } if (this.options.agent) { opts.agent = this.options.agent; } let fetchTimeout; if (api !== 'network_broadcast_api' && !method.startsWith('broadcast_transaction')) { // bit of a hack to work around some nodes high error rates // only effective in node.js (until timeout spec lands in browsers) fetchTimeout = (tries) => (tries + 1) * 500; } const { response, currentAddress } = yield utils_1.retryingFetch(this.currentAddress, this.address, opts, this.timeout, this.failoverThreshold, this.backoff, fetchTimeout); // After failover, change the currently active address if (currentAddress !== this.currentAddress) { this.currentAddress = currentAddress; } // resolve FC error messages into something more readable if (response.error) { const formatValue = (value) => { switch (typeof value) { case 'object': return JSON.stringify(value); default: return String(value); } }; const { data } = response.error; let { message } = response.error; if (data && data.stack && data.stack.length > 0) { const top = data.stack[0]; const topData = utils_1.copy(top.data); message = top.format.replace(/\$\{([a-z_]+)\}/gi, (match, key) => { let rv = match; if (topData[key]) { rv = formatValue(topData[key]); delete topData[key]; } return rv; }); const unformattedData = Object.keys(topData) .map((key) => ({ key, value: formatValue(topData[key]) })) .map((item) => `${item.key}=${item.value}`); if (unformattedData.length > 0) { message += ' ' + unformattedData.join(' '); } } throw new verror_1.VError({ info: data, name: 'RPCError' }, message); } assert.equal(response.id, request.id, 'got invalid response id'); return response.result; }); } updateOperations(rebrandedApi) { exports.rebrandedApiGlobal = rebrandedApi || false; serializer_1.updateOperations(); } } exports.Client = Client; /** * Default backoff function. * ```min(tries*10^2, 10 seconds)``` */ const defaultBackoff = (tries) => Math.min(Math.pow(tries * 10, 2), 10 * 1000);