diff --git a/package.json b/package.json index 3644254..de2d9d5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@tovchain/cosms", - "version": "0.7.2", + "version": "0.7.6", "description": "query data form cosmos base networks template", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -48,7 +48,8 @@ "@cosmjs/proto-signing": "^0.29.0", "@cosmjs/stargate": "^0.29.0", "cosmjs-types": "^0.5.1", - "crypto-addr-codec": "^0.1.7" + "crypto-addr-codec": "^0.1.7", + "deepmerge": "^4.2.2" }, "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.1", diff --git a/src/index.ts b/src/index.ts index f4b4604..d8504bf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,12 @@ import Cosm from './lib/cosm'; + +export { BatchHttpClient } from './lib/tendermint-batch-rpc/batchhttpclient'; +export { BatchRpcClient } from './lib/tendermint-batch-rpc/batchrpcclient'; +export { + TendermintBatchClient +} from './lib/tendermint-batch-rpc/tendermintbatchclient'; + +export { BaseProvider } from './lib/providers'; +export { Wallet } from './lib/wallet'; + export default Cosm; diff --git a/src/lib/cosm/test_helper.spec.ts b/src/lib/cosm/test_helper.spec.ts index ffbee83..4840289 100644 --- a/src/lib/cosm/test_helper.spec.ts +++ b/src/lib/cosm/test_helper.spec.ts @@ -3,9 +3,13 @@ import { BaseProvider } from '../providers'; import Cosm from './index'; -const rpcUrl = 'https://testnet.rpc.orai.io'; -// const rpcUrl = "https://public-rpc1.stafihub.io"; -// const rpcUrl = "https://rpc-cosmoshub.keplr.app"; +// const rpcUrl = 'https://testnet.rpc.orai.io'; +const rpcUrl = 'https://rpc.orai.io'; +// const rpcUrl = 'https://public-rpc1.stafihub.io'; +// const rpcUrl = "https://node1.konstellation.tech:26657"; +// const rpcUrl = "https://konstellation-rpc.polkachu.com"; +// const rpcUrl = "https://rpc-archive.sifchain.finance"; +// const rpcUrl = "https://rpc-sifchain.ecostake.com"; // const rpcUrl = "https://rpc-osmosis.keplr.app"; // const rpcUrl = 'https://osmosis-testnet-rpc.allthatnode.com:26657'; let provider; @@ -22,10 +26,10 @@ describe('Cosm test', async () => { describe('Test message', async () => { it('Test helper', async function() { - const start = 8568000; - const end = 8568202; + const end = await provider.batchQueryClient.getHeight(); + const start = end - 100; - let uptime = await cosm.helper.getUptime(start, end); + let uptime = await cosm.helper.getUptimeBatch(start, end,40); console.log(uptime); }); }); diff --git a/src/lib/helpers/index.js.ts b/src/lib/helpers/index.js.ts index 95cdd9e..697bbdb 100644 --- a/src/lib/helpers/index.js.ts +++ b/src/lib/helpers/index.js.ts @@ -1,6 +1,8 @@ import * as responses from '@cosmjs/tendermint-rpc/build/tendermint35/responses'; +import deepmerge from 'deepmerge'; import Cosm from '../cosm'; +import { breakToRanges, mergeWithAdd } from '../utils/range'; export class Helper { private cosm: Cosm; @@ -9,7 +11,7 @@ export class Helper { this.cosm = cosm; } - async getBatchBlock(startBlock, endBlock) { + async getBlocks(startBlock, endBlock) { const tendermint = this.cosm.tendermint; for (let i = startBlock; i < endBlock + 1; i++) { await tendermint.block(i); @@ -24,8 +26,18 @@ export class Helper { return blockResults; } + async getBatchBlock(startBlock, endBlock, batchSize = 1000) { + const ranges = breakToRanges(startBlock, endBlock, batchSize); + let blockResults = {}; + for (const range of ranges) { + let blocks = await this.getBlocks(range[0], range[1]); + blockResults = { ...blockResults, ...blocks }; + } + return blockResults; + } + async getUptime(startBlock, endBlock) { - let blocks = await this.getBatchBlock(startBlock, endBlock); + let blocks = await this.getBlocks(startBlock, endBlock); let upTimeResult = {}; let proposeTimeResult = {}; @@ -36,9 +48,14 @@ export class Helper { proposeTimeResult[proposerAddress] = proposeTime + 1; let signatures = block.block.lastCommit.signatures; for (const signature of signatures) { - let validatorAddress = Cosm.utils.uint8Array.toHex(signature.validatorAddress); - let upTime = upTimeResult[validatorAddress] | 0; - upTimeResult[validatorAddress] = upTime + 1; + try { + let validatorAddress = Cosm.utils.uint8Array.toHex(signature.validatorAddress); + let upTime = upTimeResult[validatorAddress] | 0; + upTimeResult[validatorAddress] = upTime + 1; + } catch (e) { + console.debug(`Warn: block.lastCommit.signatures : signature ${JSON.stringify(signature)}`); + } + } } return { @@ -48,4 +65,24 @@ export class Helper { proposeTime: proposeTimeResult }; } + + async getUptimeBatch(startBlock, endBlock, batchSize = 1000) { + let ranges = breakToRanges(startBlock, endBlock, batchSize); + let upTimeResults = { + startBlock: startBlock, + endBlock: endBlock, + upTime: {}, + proposeTime: {} + }; + + + for (const range of ranges) { + const upTime = await this.getUptime(range[0], range[1]); + upTimeResults.upTime = deepmerge(upTime.upTime,upTimeResults.upTime); + upTimeResults.proposeTime = deepmerge(upTime.proposeTime,upTimeResults.proposeTime); + + } + return upTimeResults; + } } + diff --git a/src/lib/utils/bech32.ts b/src/lib/utils/bech32.ts new file mode 100644 index 0000000..68a8729 --- /dev/null +++ b/src/lib/utils/bech32.ts @@ -0,0 +1,208 @@ +'use strict'; +const ALPHABET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'; + +const ALPHABET_MAP: { [key: string]: number } = {}; +for (let z = 0; z < ALPHABET.length; z++) { + const x = ALPHABET.charAt(z); + ALPHABET_MAP[x] = z; +} + +function polymodStep(pre: number): number { + const b = pre >> 25; + return ( + ((pre & 0x1ffffff) << 5) ^ + (-((b >> 0) & 1) & 0x3b6a57b2) ^ + (-((b >> 1) & 1) & 0x26508e6d) ^ + (-((b >> 2) & 1) & 0x1ea119fa) ^ + (-((b >> 3) & 1) & 0x3d4233dd) ^ + (-((b >> 4) & 1) & 0x2a1462b3) + ); +} + +function prefixChk(prefix: string): number | string { + let chk = 1; + for (let i = 0; i < prefix.length; ++i) { + const c = prefix.charCodeAt(i); + if (c < 33 || c > 126) return 'Invalid prefix (' + prefix + ')'; + + chk = polymodStep(chk) ^ (c >> 5); + } + chk = polymodStep(chk); + + for (let i = 0; i < prefix.length; ++i) { + const v = prefix.charCodeAt(i); + chk = polymodStep(chk) ^ (v & 0x1f); + } + return chk; +} + +function convert(data: ArrayLike, inBits: number, outBits: number, pad: true): number[]; +function convert( + data: ArrayLike, + inBits: number, + outBits: number, + pad: false, +): number[] | string; +function convert( + data: ArrayLike, + inBits: number, + outBits: number, + pad: boolean, +): number[] | string { + let value = 0; + let bits = 0; + const maxV = (1 << outBits) - 1; + + const result: number[] = []; + for (let i = 0; i < data.length; ++i) { + value = (value << inBits) | data[i]; + bits += inBits; + + while (bits >= outBits) { + bits -= outBits; + result.push((value >> bits) & maxV); + } + } + + if (pad) { + if (bits > 0) { + result.push((value << (outBits - bits)) & maxV); + } + } else { + if (bits >= inBits) return 'Excess padding'; + if ((value << (outBits - bits)) & maxV) return 'Non-zero padding'; + } + + return result; +} + +function toWords(bytes: ArrayLike): number[] { + return convert(bytes, 8, 5, true); +} + +function fromWordsUnsafe(words: ArrayLike): number[] | undefined { + const res = convert(words, 5, 8, false); + if (Array.isArray(res)) return res; +} + +function fromWords(words: ArrayLike): number[] { + const res = convert(words, 5, 8, false); + if (Array.isArray(res)) return res; + + throw new Error(res); +} + +function getLibraryFromEncoding(encoding: 'bech32' | 'bech32m'): BechLib { + let ENCODING_CONST: number; + if (encoding === 'bech32') { + ENCODING_CONST = 1; + } else { + ENCODING_CONST = 0x2bc830a3; + } + + function encode(prefix: string, words: ArrayLike, LIMIT?: number): string { + LIMIT = LIMIT || 90; + if (prefix.length + 7 + words.length > LIMIT) throw new TypeError('Exceeds length limit'); + + prefix = prefix.toLowerCase(); + + // determine chk mod + let chk = prefixChk(prefix); + if (typeof chk === 'string') throw new Error(chk); + + let result = prefix + '1'; + for (let i = 0; i < words.length; ++i) { + const x = words[i]; + if (x >> 5 !== 0) throw new Error('Non 5-bit word'); + + chk = polymodStep(chk) ^ x; + result += ALPHABET.charAt(x); + } + + for (let i = 0; i < 6; ++i) { + chk = polymodStep(chk); + } + chk ^= ENCODING_CONST; + + for (let i = 0; i < 6; ++i) { + const v = (chk >> ((5 - i) * 5)) & 0x1f; + result += ALPHABET.charAt(v); + } + + return result; + } + + function __decode(str: string, LIMIT?: number): Decoded | string { + LIMIT = LIMIT || 90; + if (str.length < 8) return str + ' too short'; + if (str.length > LIMIT) return 'Exceeds length limit'; + + // don't allow mixed case + const lowered = str.toLowerCase(); + const uppered = str.toUpperCase(); + if (str !== lowered && str !== uppered) return 'Mixed-case string ' + str; + str = lowered; + + const split = str.lastIndexOf('1'); + if (split === -1) return 'No separator character for ' + str; + if (split === 0) return 'Missing prefix for ' + str; + + const prefix = str.slice(0, split); + const wordChars = str.slice(split + 1); + if (wordChars.length < 6) return 'Data too short'; + + let chk = prefixChk(prefix); + if (typeof chk === 'string') return chk; + + const words = []; + for (let i = 0; i < wordChars.length; ++i) { + const c = wordChars.charAt(i); + const v = ALPHABET_MAP[c]; + if (v === undefined) return 'Unknown character ' + c; + chk = polymodStep(chk) ^ v; + + // not in the checksum? + if (i + 6 >= wordChars.length) continue; + words.push(v); + } + + if (chk !== ENCODING_CONST) return 'Invalid checksum for ' + str; + return { prefix, words }; + } + + function decodeUnsafe(str: string, LIMIT?: number): Decoded | undefined { + const res = __decode(str, LIMIT); + if (typeof res === 'object') return res; + } + + function decode(str: string, LIMIT?: number): Decoded { + const res = __decode(str, LIMIT); + if (typeof res === 'object') return res; + + throw new Error(res); + } + + return { + decodeUnsafe, + decode, + encode, + toWords, + fromWordsUnsafe, + fromWords, + }; +} + +export const bech32 = getLibraryFromEncoding('bech32'); +export const bech32m = getLibraryFromEncoding('bech32m'); +export interface Decoded { + prefix: string; + words: number[]; +} +export interface BechLib { + decodeUnsafe: (str: string, LIMIT?: number | undefined) => Decoded | undefined; + decode: (str: string, LIMIT?: number | undefined) => Decoded; + encode: (prefix: string, words: ArrayLike, LIMIT?: number | undefined) => string; + toWords: typeof toWords; + fromWordsUnsafe: typeof fromWordsUnsafe; + fromWords: typeof fromWords; +} diff --git a/src/lib/utils/bench32helper.ts b/src/lib/utils/bench32helper.ts index cd27156..66ab07a 100644 --- a/src/lib/utils/bench32helper.ts +++ b/src/lib/utils/bench32helper.ts @@ -1,13 +1,11 @@ -import bech32 from 'bech32'; import { isValidChecksumAddress, stripHexPrefix, toChecksumAddress } from 'crypto-addr-codec'; +import { bech32 } from './bech32'; -const { encode, decode, toWords, fromWords } = bech32; - function hexEncoder() { return (data) => toChecksumAddress(data.toString('hex')); } @@ -29,18 +27,18 @@ function hexDecoder() { } function bech32Encoder(prefix) { - return (data) => encode(prefix, toWords(data)); + return (data) => bech32.encode(prefix, bech32.toWords(data)); } function bech32Decoder(currPrefix) { return (data) => { - const { prefix, words } = decode(data); + const { prefix, words } = bech32.decode(data); if (prefix !== currPrefix) { throw Error('Invalid address format'); } - return Buffer.from(fromWords(words)); + return Buffer.from(bech32.fromWords(words)); }; } @@ -77,9 +75,10 @@ function toBech32(prefix, address) { } function fromBytes(prefix, bytes: ArrayLike) { - return bech32.encode(prefix, toWords(bytes)); + return bech32.encode(prefix, bech32.toWords(bytes)); } + export const Bech32Helper = { converter, toHex, diff --git a/src/lib/utils/range.ts b/src/lib/utils/range.ts new file mode 100644 index 0000000..56b7e52 --- /dev/null +++ b/src/lib/utils/range.ts @@ -0,0 +1,36 @@ +import merge from 'deepmerge'; + + +export function breakToRanges(start: number, end: number, step: number) { + let ranges = []; + while (start <= end) { + let endTemp = start + step - 1; + const startTemp = start; + endTemp = Math.min(endTemp, end); + ranges.push([startTemp, endTemp]); + start = endTemp + 1; + } + return ranges; +} + +function customAdd(key) { + + + return (v1, v2) => { + if (typeof v1 == 'object' || typeof v2 == 'object') { + return merge(v2, v1, { + customMerge: customAdd, + isMergeableObject: value => true + }); + } else { + return v1 + v2; + } + }; +} + +export function mergeWithAdd(obj1 = {}, obj2 = {}) { + return merge(obj1, obj2, { + customMerge: customAdd, + isMergeableObject: value => true + }); +}