@esteemapp/dhive
Version:
Hive blockchain RPC client library
225 lines (224 loc) • 9.45 kB
JavaScript
;
/**
* @file Misc utility functions.
* @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());
});
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
Object.defineProperty(exports, "__esModule", { value: true });
const cross_fetch_1 = require("cross-fetch");
const stream_1 = require("stream");
const timeoutErrors = ['request-timeout', 'ENOTFOUND', 'ECONNREFUSED', 'Unable to acquire database lock'];
/**
* Return a promise that will resove when a specific event is emitted.
*/
function waitForEvent(emitter, eventName) {
return new Promise((resolve, reject) => {
emitter.once(eventName, resolve);
});
}
exports.waitForEvent = waitForEvent;
/**
* Sleep for N milliseconds.
*/
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
exports.sleep = sleep;
/**
* Return a stream that emits iterator values.
*/
function iteratorStream(iterator) {
const stream = new stream_1.PassThrough({ objectMode: true });
const iterate = () => __awaiter(this, void 0, void 0, function* () {
var e_1, _a;
try {
for (var iterator_1 = __asyncValues(iterator), iterator_1_1; iterator_1_1 = yield iterator_1.next(), !iterator_1_1.done;) {
const item = iterator_1_1.value;
if (!stream.write(item)) {
yield waitForEvent(stream, 'drain');
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (iterator_1_1 && !iterator_1_1.done && (_a = iterator_1.return)) yield _a.call(iterator_1);
}
finally { if (e_1) throw e_1.error; }
}
});
iterate()
.then(() => {
stream.end();
})
.catch((error) => {
stream.emit('error', error);
stream.end();
});
return stream;
}
exports.iteratorStream = iteratorStream;
/**
* Return a deep copy of a JSON-serializable object.
*/
function copy(object) {
return JSON.parse(JSON.stringify(object));
}
exports.copy = copy;
/**
* Fetch API wrapper that retries until timeout is reached.
*/
function retryingFetch(currentAddress, allAddresses, opts, timeout, failoverThreshold, backoff, fetchTimeout) {
return __awaiter(this, void 0, void 0, function* () {
let start = Date.now();
let tries = 0;
let round = 0;
do {
try {
if (fetchTimeout) {
opts.timeout = fetchTimeout(tries);
}
const response = yield cross_fetch_1.default(currentAddress, opts);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return { response: yield response.json(), currentAddress };
}
catch (error) {
if (timeout !== 0 && Date.now() - start > timeout) {
if (timeoutErrors.includes(error.code) &&
Array.isArray(allAddresses) &&
allAddresses.length > 1) {
if (round < failoverThreshold) {
start = Date.now();
tries = -1;
if (failoverThreshold > 0) {
round++;
}
currentAddress = failover(currentAddress, allAddresses);
}
else {
if (timeoutErrors.includes(error.code) &&
Array.isArray(allAddresses)) {
error.message = `[${error.code}] tried ${failoverThreshold} times with ${allAddresses.join(',')}`;
throw error;
}
else {
throw error;
}
}
}
else {
throw error;
}
}
yield sleep(backoff(tries++));
}
} while (true);
});
}
exports.retryingFetch = retryingFetch;
const failover = (url, urls) => {
const index = urls.indexOf(url);
return urls.length === index + 1 ? urls[0] : urls[index + 1];
};
// Hack to be able to generate a valid witness_set_properties op
// Can hopefully be removed when hived's JSON representation is fixed
const ByteBuffer = require("bytebuffer");
const serializer_1 = require("./chain/serializer");
function serialize(serializer, data) {
const buffer = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN);
serializer(buffer, data);
buffer.flip();
// `props` values must be hex
return buffer.toString('hex');
// return Buffer.from(buffer.toBuffer());
}
function buildWitnessUpdateOp(owner, props) {
const data = {
extensions: [],
owner,
props: []
};
for (const key of Object.keys(props)) {
let type;
switch (key) {
case 'key':
case 'new_signing_key':
type = serializer_1.Types.PublicKey;
break;
case 'account_subsidy_budget':
case 'account_subsidy_decay':
case 'maximum_block_size':
type = serializer_1.Types.UInt32;
break;
case 'hbd_interest_rate':
case 'sbd_interest_rate': // remove after hf24
type = serializer_1.Types.UInt16;
break;
case 'url':
type = serializer_1.Types.String;
break;
case 'hbd_exchange_rate':
case 'sbd_exchange_rate': // remove after hf24
type = serializer_1.Types.Price;
break;
case 'account_creation_fee':
type = serializer_1.Types.Asset;
break;
default:
throw new Error(`Unknown witness prop: ${key}`);
}
data.props.push([key, serialize(type, props[key])]);
}
data.props.sort((a, b) => a[0].localeCompare(b[0]));
return ['witness_set_properties', data];
}
exports.buildWitnessUpdateOp = buildWitnessUpdateOp;