parallel requests for median aproach

This commit is contained in:
Alexey 2020-10-17 02:07:39 +03:00
parent 8d86bfc0fc
commit 402732a84b
6 changed files with 78 additions and 122 deletions

View File

@ -41,7 +41,7 @@
"tslint": "^6.1.2",
"tslint-config-prettier": "^1.18.0",
"tslint-plugin-prettier": "^2.3.0",
"typescript": "^3.9.3"
"typescript": "^4.0.3"
},
"dependencies": {
"axios": "^0.19.2",

View File

@ -1,22 +1,24 @@
import axios from 'axios';
import config from './config';
import { GasPrice, OffChainOracle, OnChainOracle, ConstructorArgs, GasPriceKey } from './types';
import { GasPrice, OffChainOracle, OnChainOracle, Config, GasPriceKey } from './types';
import BigNumber from 'bignumber.js';
export class GasPriceOracle {
lastGasPrice: GasPrice;
defaultRpc = 'https://api.mycryptoapi.com/eth';
offChainOracles = { ...config.offChainOracles };
onChainOracles = { ...config.onChainOracles };
configuration: Config = {
defaultRpc: 'https://api.mycryptoapi.com/eth',
timeout: 10000,
};
constructor(options: ConstructorArgs) {
if (options && options.defaultRpc) {
this.defaultRpc = options.defaultRpc;
constructor(options: Config) {
if (options) {
Object.assign(this.configuration, options);
}
}
async fetchGasPricesOffChain(): Promise<GasPrice> {
for (let oracle of Object.values(this.offChainOracles)) {
async askOracle(oracle: OffChainOracle): Promise<GasPrice> {
const {
name,
url,
@ -26,8 +28,7 @@ export class GasPriceOracle {
lowPropertyName,
denominator,
} = oracle;
try {
const response = await axios.get(url, { timeout: 10000 });
const response = await axios.get(url, { timeout: this.configuration.timeout });
if (response.status === 200) {
const gas = response.data;
if (Number(gas[fastPropertyName]) === 0) {
@ -43,47 +44,34 @@ export class GasPriceOracle {
} else {
throw new Error(`Fetch gasPrice from ${name} oracle failed. Trying another one...`);
}
}
async fetchGasPricesOffChain(): Promise<GasPrice> {
for (let oracle of Object.values(this.offChainOracles)) {
try {
return await this.askOracle(oracle);
} catch (e) {
console.error(e.message);
console.info(e.message);
continue;
}
}
throw new Error('All oracles are down. Probably a network error.');
}
async fetchMedianGasPriceOffChain(): Promise<GasPrice> {
const allGasPrices: GasPrice[] = [];
for (let oracle of Object.values(this.offChainOracles)) {
const {
name,
url,
instantPropertyName,
fastPropertyName,
standardPropertyName,
lowPropertyName,
denominator,
} = oracle;
try {
const response = await axios.get(url, { timeout: 10000 });
// todo parallel requests
if (response.status === 200) {
const gas = response.data;
if (Number(gas[fastPropertyName]) === 0) {
throw new Error(`${name} oracle provides corrupted values`);
}
const gasPrices: GasPrice = {
instant: parseFloat(gas[instantPropertyName]) / denominator,
fast: parseFloat(gas[fastPropertyName]) / denominator,
standard: parseFloat(gas[standardPropertyName]) / denominator,
low: parseFloat(gas[lowPropertyName]) / denominator,
};
allGasPrices.push(gasPrices);
} else {
throw new Error(`Fetch gasPrice from ${name} oracle failed. Trying another one...`);
}
} catch (e) {
console.error(e.message);
async fetchMedianGasPriceOffChain() {
const promises: Promise<GasPrice>[] = [];
for (let oracle of Object.values(this.offChainOracles) as Array<OffChainOracle>) {
promises.push(this.askOracle(oracle));
}
const settledPromises = await Promise.allSettled(promises);
const allGasPrices = settledPromises.reduce((acc: GasPrice[], result) => {
if (result.status === 'fulfilled') {
acc.push(result.value);
return acc;
}
return acc;
}, []);
if (allGasPrices.length === 0) {
throw new Error('All oracles are down. Probably a network error.');
}
@ -124,9 +112,8 @@ export class GasPriceOracle {
async fetchGasPricesOnChain(): Promise<number> {
for (let oracle of Object.values(this.onChainOracles)) {
const { name, callData, contract, denominator } = oracle;
let { rpc } = oracle;
rpc = rpc ? rpc : this.defaultRpc;
const { name, callData, contract, denominator, rpc } = oracle;
const rpcUrl = rpc || this.configuration.defaultRpc;
const body = {
jsonrpc: '2.0',
id: 1337,
@ -134,7 +121,7 @@ export class GasPriceOracle {
params: [{ data: callData, to: contract }, 'latest'],
};
try {
const response = await axios.post(rpc, body, { timeout: 10000 });
const response = await axios.post(rpcUrl!, body, { timeout: this.configuration.timeout });
if (response.status === 200) {
const { result } = response.data;
let fastGasPrice = new BigNumber(result);

View File

@ -22,6 +22,7 @@ export type GasPrice = {
export type GasPriceKey = 'instant' | 'fast' | 'standard' | 'low';
export interface ConstructorArgs {
export interface Config {
defaultRpc?: string;
timeout?: number;
}

View File

@ -24,6 +24,19 @@ beforeEach('beforeEach', function () {
oracle = new GasPriceOracle();
});
describe('constructor', function () {
it('should set default values', async function () {
oracle.configuration.defaultRpc.should.be.equal('https://api.mycryptoapi.com/eth');
oracle.configuration.timeout.should.be.equal(10000);
});
it('should set passed values', async function () {
const newOracle = new GasPriceOracle({ timeout: 1337 });
newOracle.configuration.defaultRpc.should.be.equal('https://api.mycryptoapi.com/eth');
newOracle.configuration.timeout.should.be.equal(1337);
});
});
describe('fetchGasPricesOffChain', function () {
it('should work', async function () {
const gas: GasPrice = await oracle.fetchGasPricesOffChain();
@ -61,7 +74,7 @@ describe('fetchGasPricesOnChain', function () {
it('should work with custom rpc', async function () {
const rpc = 'https://ethereum-rpc.trustwalletapp.com';
const oracle = new GasPriceOracle({ defaultRpc: rpc });
oracle.defaultRpc.should.be.equal(rpc);
oracle.configuration.defaultRpc.should.be.equal(rpc);
const gas: number = await oracle.fetchGasPricesOnChain();
gas.should.be.a('number');

View File

@ -6,7 +6,8 @@
"lib": [
"es2017",
"esnext.asynciterable",
"es2019"
"es2019",
"ES2020.Promise"
] /* Specify library files to be included in the compilation. */,
"outDir": "./lib" /* Redirect output structure to the directory. */,
"strict": true /* Enable all strict type-checking options. */,

View File

@ -269,14 +269,6 @@ diff@^4.0.1:
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
doctrine@0.7.2:
version "0.7.2"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-0.7.2.tgz#7cb860359ba3be90e040b26b729ce4bfa654c523"
integrity sha1-fLhgNZujvpDgQLJrcpzkv6ZUxSM=
dependencies:
esutils "^1.1.6"
isarray "0.0.1"
emoji-regex@^7.0.1:
version "7.0.3"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
@ -344,11 +336,6 @@ esprima@^4.0.0:
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
esutils@^1.1.6:
version "1.1.6"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-1.1.6.tgz#c01ccaa9ae4b897c6d0c3e210ae52f3c7a844375"
integrity sha1-wBzKqa5LiXxtDD4hCuUvPHqEQ3U=
fast-diff@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
@ -541,11 +528,6 @@ is-symbol@^1.0.2:
dependencies:
has-symbols "^1.0.1"
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
@ -919,11 +901,6 @@ ts-node@^8.10.1:
source-map-support "^0.5.17"
yn "3.1.1"
tslib@1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8"
integrity sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ==
tslib@^1.13.0, tslib@^1.7.1, tslib@^1.8.1:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
@ -934,22 +911,6 @@ tslint-config-prettier@^1.18.0:
resolved "https://registry.yarnpkg.com/tslint-config-prettier/-/tslint-config-prettier-1.18.0.tgz#75f140bde947d35d8f0d238e0ebf809d64592c37"
integrity sha512-xPw9PgNPLG3iKRxmK7DWr+Ea/SzrvfHtjFt5LBl61gk2UBG/DB9kCXRjv+xyIU1rUtnayLeMUVJBcMX8Z17nDg==
tslint-config-standard@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/tslint-config-standard/-/tslint-config-standard-9.0.0.tgz#349a94819d93d5f8d803e3c71cb58ef38eff88e0"
integrity sha512-CAw9J743RnPMemQV/XQ4YyNreC+A1NItACfkm+cBedrOkz6CQfwlnbKn8anUXBfoa4Zo4tjAhblRbsMNcSLfSw==
dependencies:
tslint-eslint-rules "^5.3.1"
tslint-eslint-rules@^5.3.1:
version "5.4.0"
resolved "https://registry.yarnpkg.com/tslint-eslint-rules/-/tslint-eslint-rules-5.4.0.tgz#e488cc9181bf193fe5cd7bfca213a7695f1737b5"
integrity sha512-WlSXE+J2vY/VPgIcqQuijMQiel+UtmXS+4nvK4ZzlDiqBfXse8FAvkNnTcYhnQyOTW5KFM+uRRGXxYhFpuBc6w==
dependencies:
doctrine "0.7.2"
tslib "1.9.0"
tsutils "^3.0.0"
tslint-plugin-prettier@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/tslint-plugin-prettier/-/tslint-plugin-prettier-2.3.0.tgz#73fe71bf9f03842ac48c104122ca9b1de012ecf4"
@ -985,22 +946,15 @@ tsutils@^2.29.0:
dependencies:
tslib "^1.8.1"
tsutils@^3.0.0:
version "3.17.1"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"
integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==
dependencies:
tslib "^1.8.1"
type-detect@^4.0.0, type-detect@^4.0.5:
version "4.0.8"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
typescript@^3.9.3:
version "3.9.7"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa"
integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==
typescript@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.3.tgz#153bbd468ef07725c1df9c77e8b453f8d36abba5"
integrity sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==
which-module@^2.0.0:
version "2.0.0"