@steempro/dsteem
Version:
Steem blockchain RPC client library
363 lines (362 loc) • 14 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 });
exports.makeBitMaskFilter = exports.operationOrders = exports.buildWitnessUpdateOp = exports.retryingFetch = exports.copy = exports.iteratorStream = exports.sleep = exports.waitForEvent = void 0;
const node_fetch_1 = require("node-fetch");
const stream_1 = require("stream");
// TODO: Add more errors that should trigger a failover
const timeoutErrors = [
"timeout",
"ENOTFOUND",
"ECONNREFUSED",
"database lock",
"CERT_HAS_EXPIRED",
"EHOSTUNREACH",
"ECONNRESET",
"ERR_TLS_CERT_ALTNAME_INVALID",
"EAI_AGAIN",
];
/**
* 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, consoleOnFailover, 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 node_fetch_1.default(currentAddress, opts);
if (!response.ok) {
if (response.status === 500) {
// Support for Drone
const resJson = yield response.json();
if (resJson.jsonrpc === "2.0") {
return { response: resJson, currentAddress };
}
}
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return { response: yield response.json(), currentAddress };
}
catch (error) {
if (timeout !== 0 && Date.now() - start > timeout) {
if ((!error || !error.code) && Array.isArray(allAddresses)) {
// If error is empty or not code is present, it means rpc is down => switch
currentAddress = failover(currentAddress, allAddresses, currentAddress, consoleOnFailover);
}
else {
const isFailoverError = timeoutErrors.filter((fe) => error && error.code && error.code.includes(fe)).length > 0;
if (isFailoverError &&
Array.isArray(allAddresses) &&
allAddresses.length > 1) {
if (round < failoverThreshold) {
start = Date.now();
tries = -1;
if (failoverThreshold > 0) {
round++;
}
currentAddress = failover(currentAddress, allAddresses, currentAddress, consoleOnFailover);
}
else {
error.message = `[${error.code}] tried ${failoverThreshold} times with ${allAddresses.join(",")}`;
throw error;
}
}
else {
// tslint:disable-next-line: no-console
console.error(`Didn't failover for error ${error.code ? "code" : "message"}: [${error.code || error.message}]`);
throw error;
}
}
}
yield sleep(backoff(tries++));
}
} while (true);
});
}
exports.retryingFetch = retryingFetch;
const failover = (url, urls, currentAddress, consoleOnFailover) => {
const index = urls.indexOf(url);
const targetUrl = urls.length === index + 1 ? urls[0] : urls[index + 1];
if (consoleOnFailover) {
// tslint:disable-next-line: no-console
console.log(`Switched Steem RPC: ${targetUrl} (previous: ${currentAddress})`);
}
return targetUrl;
};
// Hack to be able to generate a valid witness_set_properties op
// Can hopefully be removed when steemd's JSON representation is fixed
const ByteBuffer = require("@steempro/bytebuffer");
const serializer_1 = require("./chain/serializer");
const 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());
};
exports.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 "sbd_interest_rate":
type = serializer_1.Types.UInt16;
break;
case "url":
type = serializer_1.Types.String;
break;
case "sbd_exchange_rate":
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];
};
const JSBI = require("jsbi");
exports.operationOrders = {
vote: 0,
// tslint:disable-next-line: object-literal-sort-keys
comment: 1,
transfer: 2,
transfer_to_vesting: 3,
withdraw_vesting: 4,
limit_order_create: 5,
limit_order_cancel: 6,
feed_publish: 7,
convert: 8,
account_create: 9,
account_update: 10,
witness_update: 11,
account_witness_vote: 12,
account_witness_proxy: 13,
pow: 14,
custom: 15,
report_over_production: 16,
delete_comment: 17,
custom_json: 18,
comment_options: 19,
set_withdraw_vesting_route: 20,
limit_order_create2: 21,
claim_account: 22,
create_claimed_account: 23,
request_account_recovery: 24,
recover_account: 25,
change_recovery_account: 26,
escrow_transfer: 27,
escrow_dispute: 28,
escrow_release: 29,
pow2: 30,
escrow_approve: 31,
transfer_to_savings: 32,
transfer_from_savings: 33,
cancel_transfer_from_savings: 34,
custom_binary: 35,
decline_voting_rights: 36,
reset_account: 37,
set_reset_account: 38,
claim_reward_balance: 39,
delegate_vesting_shares: 40,
account_create_with_delegation: 41,
witness_set_properties: 42,
account_update2: 43,
create_proposal: 44,
update_proposal_votes: 45,
remove_proposal: 46,
update_proposal: 47,
collateralized_convert: 48,
recurrent_transfer: 49,
// virtual ops
fill_convert_request: 50,
author_reward: 51,
curation_reward: 52,
comment_reward: 53,
liquidity_reward: 54,
interest: 55,
fill_vesting_withdraw: 56,
fill_order: 57,
shutdown_witness: 58,
fill_transfer_from_savings: 59,
hardfork: 60,
comment_payout_update: 61,
return_vesting_delegation: 62,
comment_benefactor_reward: 63,
producer_reward: 64,
clear_null_account_balance: 65,
proposal_pay: 66,
sps_fund: 67,
hardfork_steem: 68,
hardfork_steem_restore: 69,
delayed_voting: 70,
consolidate_treasury_balance: 71,
effective_comment_vote: 72,
ineffective_delete_comment: 73,
sps_convert: 74,
expired_account_notification: 75,
changed_recovery_account: 76,
transfer_to_vesting_completed: 77,
pow_reward: 78,
vesting_shares_split: 79,
account_created: 80,
fill_collateralized_convert_request: 81,
system_warning: 82,
fill_recurrent_transfer: 83,
failed_recurrent_transfer: 84,
};
/**
* Make bitmask filter to be used with getAccountHistory call
* @param allowedOperations Array of operations index numbers
*/
function makeBitMaskFilter(allowedOperations) {
return allowedOperations
.reduce(redFunction, [JSBI.BigInt(0), JSBI.BigInt(0)])
.map((value) => JSBI.notEqual(value, JSBI.BigInt(0)) ? value.toString() : null);
}
exports.makeBitMaskFilter = makeBitMaskFilter;
const redFunction = ([low, high], allowedOperation) => {
if (allowedOperation < 64) {
return [
JSBI.bitwiseOr(low, JSBI.leftShift(JSBI.BigInt(1), JSBI.BigInt(allowedOperation))),
high,
];
}
else {
return [
low,
JSBI.bitwiseOr(high, JSBI.leftShift(JSBI.BigInt(1), JSBI.BigInt(allowedOperation - 64))),
];
}
};