From 72c249a0c434661c79b1de6d7789142b05eec8bf Mon Sep 17 00:00:00 2001 From: clearloop Date: Wed, 9 Sep 2020 15:03:26 +0800 Subject: [PATCH] Complete receipt API (#73) * perf(proposal): update the genesis confirm part of proposal cmd * perf(util): clean util * perf(types): remove outdated types * refactor(listener): move listeners to one folder * chore(types): update receipt types * perf(proposal): fix the genesis lastconfirmed block * perf(listener): add tx type to the callback of ethereum listener * perf(eth): clean db files of ethereum listener * perf(cfg): merge configs * refactor(cmd): use run command for default command, remove proposal cmd * refactor(listener): invoke blockNumber into ethereum listener callback * feat(listener): complete listener queue * chore(api): unly delay exs * refactor(api): use array as tuple in redeem api * feat(redeem): complete redeem process * chore(static): update types.json of darwinia * perf(cmd): optimize the log of ethereum listener * chore(config): update the config of ethereum listener * perf(badge): add npm badge --- README.md | 1 + config/darwiniadev.json | 19 --- config/default.json | 20 --- package.json | 95 ++++++----- src/__test__/eth.ts | 25 +++ src/api/api.ts | 52 +++--- src/api/index.ts | 7 +- src/api/shadow.ts | 17 +- src/api/{types/block.ts => types.ts} | 35 +++- src/api/types/extrinsic.ts | 50 ------ src/cmd/index.ts | 7 +- src/cmd/proposal.ts | 197 ----------------------- src/cmd/run.ts | 36 +++++ src/listener/BlockInDB.ts | 30 ---- src/listener/BlockchainState.ts | 16 -- src/listener/LogInDB.ts | 38 ----- src/listener/Starter.ts | 22 --- src/listener/cache.ts | 30 ++++ src/listener/eth/BlockchainState.ts | 16 ++ src/listener/{ => eth}/Config.ts | 1 - src/listener/eth/DB.ts | 61 +++++++ src/listener/{ => eth}/EventParser.ts | 66 ++++---- src/listener/eth/index.ts | 19 +++ src/listener/{ => eth}/types.ts | 7 +- src/listener/guard.ts | 50 ++++++ src/listener/index.ts | 4 + src/listener/relay.ts | 75 +++++++++ src/listener/test.ts | 26 --- src/util/block.ts | 162 ------------------- src/util/cfg.ts | 54 +++---- src/{listener/Utils.ts => util/delay.ts} | 6 +- src/util/download.ts | 30 +--- src/util/index.ts | 5 +- src/util/static/ethereum_listener.json | 14 +- src/util/static/types.json | 14 +- 35 files changed, 519 insertions(+), 788 deletions(-) delete mode 100644 config/darwiniadev.json delete mode 100644 config/default.json create mode 100644 src/__test__/eth.ts rename src/api/{types/block.ts => types.ts} (56%) delete mode 100644 src/api/types/extrinsic.ts delete mode 100644 src/cmd/proposal.ts create mode 100644 src/cmd/run.ts delete mode 100644 src/listener/BlockInDB.ts delete mode 100644 src/listener/BlockchainState.ts delete mode 100644 src/listener/LogInDB.ts delete mode 100644 src/listener/Starter.ts create mode 100644 src/listener/cache.ts create mode 100644 src/listener/eth/BlockchainState.ts rename src/listener/{ => eth}/Config.ts (99%) create mode 100644 src/listener/eth/DB.ts rename src/listener/{ => eth}/EventParser.ts (61%) create mode 100644 src/listener/eth/index.ts rename src/listener/{ => eth}/types.ts (78%) create mode 100644 src/listener/guard.ts create mode 100644 src/listener/index.ts create mode 100644 src/listener/relay.ts delete mode 100644 src/listener/test.ts delete mode 100644 src/util/block.ts rename src/{listener/Utils.ts => util/delay.ts} (53%) diff --git a/README.md b/README.md index 293ffc2..07c5100 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # @darwinia/dj [![dj][workflow-badge]][github] +[![dj](https://img.shields.io/npm/v/@darwinia/dj)](https://www.npmjs.com/package/@darwinia/dj) [![LICENSE](https://img.shields.io/crates/l/darwinia-shadow.svg)](https://choosealicense.com/licenses/gpl-3.0/) ## **Introduction** diff --git a/config/darwiniadev.json b/config/darwiniadev.json deleted file mode 100644 index 0b74a4e..0000000 --- a/config/darwiniadev.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "RPC_SERVER": "http://darwinia-dev-environment:8545", - "CONTRACT": { - "RING": { - "address": "0x07d9cEB75403892798cF3ab77A9422549ba647C3" - }, - "KTON": { - "address": "0x7C2f9AB7Cb17cA2e745b4893873906a0531Abb5F" - }, - "BANK": { - "address": "0x14dD0a609C77D09114898138b2eA3459D325D1B0", - "burnAndRedeemTopics": "0xe77bf2fa8a25e63c1e5e29e1b2fcb6586d673931e020c4e3ffede453b830fb12" - }, - "ISSUING": { - "address": "0xE445275B93A94337Da05e4BCA7F383924228247a", - "burnAndRedeemTopics": "0xc9dcda609937876978d7e0aa29857cb187aea06ad9e843fd23fd32108da73f10" - } - } -} \ No newline at end of file diff --git a/config/default.json b/config/default.json deleted file mode 100644 index b2bb2fa..0000000 --- a/config/default.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "RPC_SERVER": "https://ropsten.infura.io/v3/xxxxxxxxxxxxx", - "CONTRACT": { - "RING": { - "address": "0xb52FBE2B925ab79a821b261C82c5Ba0814AAA5e0", - "burnAndRedeemTopics": "0xc9dcda609937876978d7e0aa29857cb187aea06ad9e843fd23fd32108da73f10" - }, - "KTON": { - "address": "0x1994100c58753793D52c6f457f189aa3ce9cEe94", - "burnAndRedeemTopics": "0xc9dcda609937876978d7e0aa29857cb187aea06ad9e843fd23fd32108da73f10" - }, - "BANK": { - "address": "0x6EF538314829EfA8386Fc43386cB13B4e0A67D1e", - "burnAndRedeemTopics": "0xe77bf2fa8a25e63c1e5e29e1b2fcb6586d673931e020c4e3ffede453b830fb12" - }, - "ISSUING": { - "address": "0x49262B932E439271d05634c32978294C7Ea15d0C" - } - } -} \ No newline at end of file diff --git a/package.json b/package.json index fa1ae3a..0718d89 100644 --- a/package.json +++ b/package.json @@ -1,50 +1,49 @@ { - "private": false, - "name": "@darwinia/dj", - "version": "", - "description": "Darwinia bridge relayer tool", - "homepage": "https://github.com/darwinia-network/dj", - "repository": { - "type": "git", - "url": "https://github.com/darwinia-network/dj" - }, - "author": "darwinia-network ", - "license": "GPL-3.0", - "main": "lib/index.js", - "bin": { - "dj": "lib/index.js" - }, - "files": ["lib/**/*"], - "dependencies": { - "@polkadot/api": "1.30.0-beta.0", - "@polkadot/keyring": "3.3.1", - "@polkadot/util-crypto": "3.3.1", - "axios": "^0.19.2", - "ethereumjs-util": "^7.0.4", - "got": "^11.5.2", - "javascript-stringify": "^2.0.1", - "progress": "^2.0.3", - "prompts": "^2.3.2", - "tar": "^6.0.5", - "yargs": "^15.4.1", - "web3": "^1.2.11", - "config": "^3.3.1" - }, - "devDependencies": { - "@commitlint/cli": "^8.3.5", - "@commitlint/config-conventional": "^8.3.4", - "@polkadot/types": "1.30.0-beta.0", - "@types/node": "^13.11.1", - "@types/progress": "^2.0.3", - "@types/prompts": "^2.0.8", - "@types/tar": "^4.0.3", - "@types/yargs": "^15.0.4", - "husky": "^4.2.5", - "tslint": "^6.1.1", - "typescript": "^3.8.3" - }, - "scripts": { - "build": "tsc --strict", - "lint": "tsc --noEmit --strict && tslint --project ./tsconfig.json" - } + "private": false, + "name": "@darwinia/dj", + "version": "", + "description": "Darwinia bridge relayer tool", + "homepage": "https://github.com/darwinia-network/dj", + "repository": { + "type": "git", + "url": "https://github.com/darwinia-network/dj" + }, + "author": "darwinia-network ", + "license": "GPL-3.0", + "main": "lib/index.js", + "bin": { + "dj": "lib/index.js" + }, + "files": ["lib/**/*"], + "dependencies": { + "@polkadot/api": "1.30.0-beta.0", + "@polkadot/keyring": "3.3.1", + "@polkadot/util-crypto": "3.3.1", + "axios": "^0.19.2", + "got": "^11.5.2", + "javascript-stringify": "^2.0.1", + "progress": "^2.0.3", + "prompts": "^2.3.2", + "tar": "^6.0.5", + "yargs": "^15.4.1", + "web3": "^1.2.11", + "config": "^3.3.1" + }, + "devDependencies": { + "@commitlint/cli": "^8.3.5", + "@commitlint/config-conventional": "^8.3.4", + "@polkadot/types": "1.30.0-beta.0", + "@types/node": "^13.11.1", + "@types/progress": "^2.0.3", + "@types/prompts": "^2.0.8", + "@types/tar": "^4.0.3", + "@types/yargs": "^15.0.4", + "husky": "^4.2.5", + "tslint": "^6.1.1", + "typescript": "^3.8.3" + }, + "scripts": { + "build": "tsc --strict", + "lint": "tsc --noEmit --strict && tslint --project ./tsconfig.json" + } } diff --git a/src/__test__/eth.ts b/src/__test__/eth.ts new file mode 100644 index 0000000..6d981e1 --- /dev/null +++ b/src/__test__/eth.ts @@ -0,0 +1,25 @@ +import { ethereum } from "../listener"; + +ethereum({ + "RPC_SERVER": "https://ropsten.infura.io/v3/0bfb9acbb13c426097aabb1d81a9d016", + "START_BLOCK_NUMBER": 8609800, + "CONTRACT": { + "RING": { + "address": "0xb52FBE2B925ab79a821b261C82c5Ba0814AAA5e0", + "burnAndRedeemTopics": "0xc9dcda609937876978d7e0aa29857cb187aea06ad9e843fd23fd32108da73f10" + }, + "KTON": { + "address": "0x1994100c58753793D52c6f457f189aa3ce9cEe94", + "burnAndRedeemTopics": "0xc9dcda609937876978d7e0aa29857cb187aea06ad9e843fd23fd32108da73f10" + }, + "BANK": { + "address": "0x6EF538314829EfA8386Fc43386cB13B4e0A67D1e", + "burnAndRedeemTopics": "0xe77bf2fa8a25e63c1e5e29e1b2fcb6586d673931e020c4e3ffede453b830fb12" + }, + "ISSUING": { + "address": "0x49262B932E439271d05634c32978294C7Ea15d0C" + } + } +}, (tx: any) => { + console.log('test::callback:', tx) +}); diff --git a/src/api/api.ts b/src/api/api.ts index ce7e2c6..82c3f8b 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -1,5 +1,5 @@ /* tslint:disable:variable-name */ -import { log, IDarwiniaEthBlock } from "../util"; +import { log, delay } from "../util"; import { ApiPromise, SubmittableResult, WsProvider } from "@polkadot/api"; import { SubmittableExtrinsic } from "@polkadot/api/types"; import Keyring from "@polkadot/keyring"; @@ -7,7 +7,10 @@ import { KeyringPair } from "@polkadot/keyring/types"; import { DispatchError, EventRecord } from "@polkadot/types/interfaces/types"; import { cryptoWaitReady } from "@polkadot/util-crypto"; import { SignedBlock } from "@polkadot/types/interfaces"; -import { IEthereumHeaderThingWithProof } from "./types/block"; +import { + IEthereumHeaderThingWithProof, + IReceiptWithProof, +} from "./types"; export interface IErrorDoc { name: string; @@ -15,12 +18,6 @@ export interface IErrorDoc { documentation: string[]; } -export interface IReceipt { - index: string; - proof: string; - header_hash: string; -} - /** * Extrinsic Result * @@ -148,13 +145,13 @@ export class API { /** * Get last confirm block */ - public async lastConfirm(): Promise { + public async lastConfirm(): Promise { const res = await this._.query.ethereumRelay.lastConfirmedHeaderInfo(); if (res.toJSON() === null) { - return null; + return 0; } - return (res.toJSON() as any)[0]; + return (res.toJSON() as any)[0] as number; } /** @@ -194,6 +191,8 @@ export class API { } else { return new ExResult(false, "", ""); } + log.event(`Approve block ${block}`); + await delay(3000); return await this.blockFinalized(ex, true); } @@ -209,6 +208,8 @@ export class API { } else { return new ExResult(false, "", ""); } + log.event(`Reject block ${block}`); + await delay(3000); return await this.blockFinalized(ex); } @@ -218,7 +219,9 @@ export class API { * @param {IEthHeaderThing} headerThings - Eth Header Things */ public async submitProposal(headerThings: IEthereumHeaderThingWithProof[]): Promise { + log.event(`Submit proposal contains block ${headerThings[headerThings.length - 1].header.number}`); const ex = this._.tx.ethereumRelay.submitProposal(headerThings); + await delay(3000); return await this.blockFinalized(ex); } @@ -227,23 +230,14 @@ export class API { * * @param {DarwiniaEthBlock} block - darwinia style eth block */ - public async redeem(receipt: IReceipt): Promise { - const ex: SubmittableExtrinsic<"promise"> = this._.tx.ethRelay.redeem({ - Ring: receipt, - }); - return await this.blockFinalized(ex); - } - - /** - * reset darwinia header - * - * @param {DarwiniaEthBlock} block - darwinia style eth block - */ - public async reset(block: IDarwiniaEthBlock): Promise { - const ex: SubmittableExtrinsic<"promise"> = this._.tx.ethRelay.resetGenesisHeader( - block, block.difficulty, - ); - + public async redeem(act: string, proof: IReceiptWithProof): Promise { + log.event(`Redeem tx in block ${proof.header.number}`); + const ex: SubmittableExtrinsic<"promise"> = this._.tx.ethereumBacking.redeem(act, [ + proof.header, + proof.receipt_proof, + proof.mmr_proof, + ]); + await delay(3000); return await this.blockFinalized(ex); } @@ -255,6 +249,7 @@ export class API { */ public async transfer(addr: string, amount: number): Promise { const ex = this._.tx.balances.transfer(addr, amount); + await delay(3000); return await this.blockFinalized(ex); } @@ -315,7 +310,6 @@ export class API { }); } } else if (status.isInvalid) { - console.log(res) log.warn("Invalid Extrinsic"); reject(res); } else if (status.isRetracted) { diff --git a/src/api/index.ts b/src/api/index.ts index eea00ee..c03c00d 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,8 +1,11 @@ import { API, ExResult } from "./api"; import { autoAPI } from "./auto"; import { ShadowAPI } from "./shadow"; -import { IEthereumHeaderThing, IEthereumHeaderThingWithProof } from "./types/block"; +import { IEthereumHeaderThing, IEthereumHeaderThingWithProof } from "./types"; export { - API, autoAPI, ExResult, ShadowAPI, IEthereumHeaderThing, IEthereumHeaderThingWithProof, + API, autoAPI, + ExResult, ShadowAPI, + IEthereumHeaderThing, + IEthereumHeaderThingWithProof, } diff --git a/src/api/shadow.ts b/src/api/shadow.ts index 2988e31..550b069 100644 --- a/src/api/shadow.ts +++ b/src/api/shadow.ts @@ -1,13 +1,12 @@ /* tslint:disable:variable-name */ import axios, { AxiosResponse } from "axios"; -import { - log, Block, IDarwiniaEthBlock, -} from "../util"; +import { log } from "../util"; import { IReceiptWithProof, IEthereumHeaderThing, IEthereumHeaderThingWithProof, -} from "./types/block"; + IDarwiniaEthBlock, +} from "./types"; /** * Shadow APIs @@ -29,11 +28,11 @@ export class ShadowAPI { */ public async getBlock(block: number | string): Promise { let r: AxiosResponse; - r = await axios.get(this.api + "/header/" + block); + r = await axios.get(this.api + "/eth/header/" + block); // Trace the back data - log.trace(JSON.stringify(r.data, null, 2)); - return Block.from(r.data); + log.trace(JSON.stringify(r.data.header, null, 2)); + return r.data.header; } /** @@ -56,9 +55,9 @@ export class ShadowAPI { * * @param {number} block - block number */ - async getReceipt(tx: string, last: number): Promise { + async getReceipt(tx: string, lastConfirmed: number): Promise { const r: AxiosResponse = await axios.get( - this.api + "/eth/receipt/" + tx + "?last=" + last + this.api + "/eth/receipt/" + tx + "/" + lastConfirmed ); // Trace the back data diff --git a/src/api/types/block.ts b/src/api/types.ts similarity index 56% rename from src/api/types/block.ts rename to src/api/types.ts index 552f18f..0a7ad6c 100644 --- a/src/api/types/block.ts +++ b/src/api/types.ts @@ -1,4 +1,28 @@ -import { IDarwiniaEthBlock } from "../../util"; +/// Transaction stuffs +export interface ITx { + tx: string, + ty: string, + relayedBlock: number, +} + +/// Darwinia Block +export interface IDarwiniaEthBlock { + parent_hash: string; + timestamp: number; + number: number; + author: string; + transactions_root?: string; + uncles_hash: string; + extra_data: string; + state_root: string; + receipts_root?: string; + log_bloom: string; + gas_used: number; + gas_limit: number; + difficulty: number; + seal: string[]; + hash: string; +} /// EthashProof Interface export interface IDoubleNodeWithMerkleProof { @@ -6,10 +30,17 @@ export interface IDoubleNodeWithMerkleProof { proof: string[], } +/// Receipt interface +export interface IReceipt { + index: string; + proof: string; + header_hash: string; +} + /// Receipt Proof Interface export interface IReceiptWithProof { header: IDarwiniaEthBlock, - receipt_proof: string, + receipt_proof: IReceipt, mmr_proof: string[], } diff --git a/src/api/types/extrinsic.ts b/src/api/types/extrinsic.ts deleted file mode 100644 index c04d274..0000000 --- a/src/api/types/extrinsic.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* tslint:disable:variable-name */ -export interface ExtrinsicParams { - name: string; - type: string; - value: string; - valueRaw: string; -} - -export interface ExtrinsicTransfer { - from: string; - to: string; - module: string; - amount: string; - hash: string; - block_timestamp: number; - block_num: number; - extrinsic_index: string; - success: boolean; - fee: string; -} - -export interface ExtrinsicEvent { - event_index: string; - block_num: number; - extrinsic_idx: number; - module_id: string; - params: string; - extrinsic_hash: string; - event_idx: string; - finalized: boolean; -} - -export interface Extrinsic { - block_timestamp: number; - block_num: number; - extrinsic_index: string; - call_module_function: string; - call_module: string; - account_id: string; - signature: string; - nonce: number; - extrinsic_hash: string; - success: boolean; - params: ExtrinsicParams[]; - transfer: ExtrinsicTransfer; - event: ExtrinsicEvent[]; - fee: string; - error: any; - finalized: boolean; -} diff --git a/src/cmd/index.ts b/src/cmd/index.ts index af78b73..a615d61 100755 --- a/src/cmd/index.ts +++ b/src/cmd/index.ts @@ -3,8 +3,8 @@ import { whereisPj } from "../util"; import cmdBalance from "./balance"; import cmdConfig from "./config"; -import cmdProposal from "./proposal"; import cmdTransfer from "./transfer"; +import { run } from "./run"; // main export default async function exec() { @@ -17,17 +17,16 @@ export default async function exec() { // parser const _ = yargs - .usage("dj ") + .usage("dj ") .help("help").alias("help", "h") .version("version", pj.version).alias("version", "V") .command(cmdBalance) .command(cmdConfig) - .command(cmdProposal) .command(cmdTransfer) .argv; // show help if no input if (process.argv.length < 3) { - yargs.showHelp(); + run(); } } diff --git a/src/cmd/proposal.ts b/src/cmd/proposal.ts deleted file mode 100644 index 1fb5356..0000000 --- a/src/cmd/proposal.ts +++ /dev/null @@ -1,197 +0,0 @@ -import yargs from "yargs"; -import { autoAPI, ShadowAPI, API, ExResult } from "../api"; -import { log, Config } from "../util"; -import path from "path"; -import fs from "fs"; -import { DispatchError } from "@polkadot/types/interfaces/types"; -import { IEthereumHeaderThingWithProof } from "../api/types/block"; - - -const cache = path.resolve((new Config()).path.root, "cache/blocks"); - -// Init Cache -function initCache() { - if (fs.existsSync(cache)) { - fs.rmdirSync(cache, { recursive: true }); - } - - fs.mkdirSync(cache, { recursive: true }); -} - -// Get block from cache -function getBlock(block: number): IEthereumHeaderThingWithProof | null { - const f = path.resolve(cache, `${block}.block`); - if (fs.existsSync(f)) { - return JSON.parse(fs.readFileSync(f).toString()); - } else { - return null; - } -} - -// Get block from cache -function setBlock(block: number, headerThing: IEthereumHeaderThingWithProof) { - fs.writeFileSync(path.resolve(cache, `${block}.block`), JSON.stringify(headerThing)); -} - -// Proposal guard -async function guard(api: API, shadow: ShadowAPI) { - let perms = 4; - if ((await api._.query.sudo.key()).toJSON().indexOf(api.account.address) > -1) { - perms = 7; - } else if (((await api._.query.council.members()).toJSON() as string[]).indexOf(api.account.address) > -1) { - perms = 5; - } else { - return; - } - - // start listening - let lock = false; - const handled: number[] = []; - setInterval(async () => { - if (lock) { return; } - const headers = (await api._.query.ethereumRelayerGame.pendingHeaders()).toJSON() as string[][]; - if (headers.length === 0) { - return; - } - - lock = true; - for (const h of headers) { - const blockNumber = Number.parseInt(h[1], 10); - if (handled.indexOf(blockNumber) > -1) { - break; - } - - const block = (await shadow.getHeaderThing(blockNumber)) as any; - if (JSON.stringify(block) === JSON.stringify(h[2])) { - const res: ExResult = await api.approveBlock(blockNumber, perms); - if (res.isOk) { - log.event(`Approved block ${blockNumber}`) - } else { - log.err(res.toString()) - } - } else { - const res = await api.rejectBlock(h[1], perms); - if (res.isOk) { - log.event(`Rejected block ${blockNumber}`) - } else { - log.err(res) - log.err(res.toString()) - } - } - handled.push(blockNumber); - } - - lock = false; - }, 10000); -} - -/// block 19: Uncle -/// diff: -/// block -/// ethash_proof -/// mmr_root -/// mmr_proof -/// -/// block 1-18: Normal ----- [19, 18] -/// diff: -/// mmr_proof - -/// chain-0 chain-1 -/// --------------------------- -/// real [19] mock [19] -/// real [19,18] mock [19,18] - -// Listen and submit proposals -function startListener(api: API, shadow: ShadowAPI) { - // Subscribe to system events via storage - api._.query.system.events((events) => { - events.forEach(async (record) => { - const { event, phase } = record; - const types = event.typeDef; - - if (event.method === "GameOver") { - log.ok("Gameover"); - } - - // Show what we are busy with - if (event.method === "NewRound") { - log.trace(`\t${event.section}:${event.method}:: (phase=${phase.toString()})`); - log.trace(`\t\t${event.meta.documentation.toString()}`); - - // Samples - const lastLeaf = Math.max(...(event.data[1].toJSON() as number[])); - const members: number[] = event.data[1].toJSON() as number[]; - - // Get proposals - let newMember: number = 0; - let proposals: IEthereumHeaderThingWithProof[] = []; - members.forEach((i: number) => { - const block = getBlock(i); - if (block) { - proposals.push(block); - } else { - newMember = i; - } - }) - - const newProposal = await shadow.getProposal([newMember], newMember, lastLeaf); - setBlock(newMember, Object.assign(JSON.parse(JSON.stringify(newProposal)), { - ethash_proof: [], - mmr_root: "", - mmr_proof: [], - })); - proposals = proposals.concat(newProposal); - - // Submit new proposals - await api.submitProposal(proposals); - - // Loop through each of the parameters, displaying the type and data - event.data.forEach((data, index) => { - log.trace(`\t\t\t${types[index].type}: ${data.toString()}`); - }); - } - - if (event.data[0] && (event.data[0] as DispatchError).isModule) { - log.err(api._.registry.findMetaError( - (event.data[0] as DispatchError).asModule.toU8a(), - )); - } - }); - }); -} - -// The proposal API -async function handler(args: yargs.Arguments) { - initCache(); - - const conf = new Config(); - const api = await autoAPI(); - const block = (args.block as number); - const lastConfirmedBlock = await api.lastConfirm(); - const shadow = new ShadowAPI(conf.shadow); - - // Start guard - // conf. - guard(api, shadow); - - // The target block - const lastLeaf = block > 1 ? block - 1 : 0; - const proposal = await shadow.getProposal( - lastConfirmedBlock - ? [lastConfirmedBlock] - : [], block, lastLeaf - ); - log.trace(await api.submitProposal([proposal])); - - // Start proposal linstener - startListener(api, shadow); - // addEventListener(conf.ethereumListener, console.log) -} - -const cmdProposal: yargs.CommandModule = { - command: "proposal ", - describe: "Submit a relay proposal to darwinia", - handler, -} - -export default cmdProposal; diff --git a/src/cmd/run.ts b/src/cmd/run.ts new file mode 100644 index 0000000..90a0a9a --- /dev/null +++ b/src/cmd/run.ts @@ -0,0 +1,36 @@ +import { autoAPI, ShadowAPI } from "../api"; +import { ITx } from "../api/types"; +import { log, Config } from "../util"; +import * as Listener from "../listener" + +const QUEUE: ITx[] = []; + +// The proposal API +export async function run() { + Listener.Cache.initCache(); + + const conf = new Config(); + const api = await autoAPI(); + const shadow = new ShadowAPI(conf.shadow); + + // Start proposal linstener + Listener.guard(api, shadow); + Listener.relay(api, shadow, QUEUE); + Listener.ethereum( + conf.ethereumListener, + async (tx: string, ty: string, blockNumber: number) => { + log.trace(`Find darwinia ${ty} tx ${tx} in block ${blockNumber}`); + const lastConfirm = await api.lastConfirm(); + const relayedBlock = blockNumber + 1; + await api.submitProposal([ + await shadow.getProposal([lastConfirm], relayedBlock, blockNumber), + ]); + + QUEUE.push({ + tx, + relayedBlock, + ty: ty === "bank" ? "Deposit" : "Token", + }); + } + ); +} diff --git a/src/listener/BlockInDB.ts b/src/listener/BlockInDB.ts deleted file mode 100644 index 1dcd288..0000000 --- a/src/listener/BlockInDB.ts +++ /dev/null @@ -1,30 +0,0 @@ - - -export interface Blocks { - lastBlockNumber: number, - parsedEventBlockNumber: number -} - -export class BlockInDB { - private lastBlockNumber: number = 0; - private parsedEventBlockNumber: number = 0; - - setLastBlockNumber(num: number): void { - this.lastBlockNumber = num; - } - - setParsedEventBlockNumber(num: number): void { - this.parsedEventBlockNumber = num; - } - - getBlockNumber(): Blocks { - return { - lastBlockNumber: this.lastBlockNumber, - parsedEventBlockNumber: this.parsedEventBlockNumber - }; - } -} - -const blockInDB = new BlockInDB(); - -export { blockInDB }; diff --git a/src/listener/BlockchainState.ts b/src/listener/BlockchainState.ts deleted file mode 100644 index 66610d3..0000000 --- a/src/listener/BlockchainState.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { localConfig as Config } from "./Config"; -import { blockInDB } from "./BlockInDB"; - -export class BlockchainState { - getState(): Promise { - return BlockchainState.getBlockState().then(([blockInChain, latestBlockInDB]) => { - blockInDB.setLastBlockNumber(blockInChain); - }) - } - - static getBlockState(): Promise { - const latestBlockOnChain = Config.web3.eth.getBlockNumber(); - const latestBlockInDB = blockInDB.getBlockNumber(); - return Promise.all([latestBlockOnChain, latestBlockInDB]); - } -} \ No newline at end of file diff --git a/src/listener/LogInDB.ts b/src/listener/LogInDB.ts deleted file mode 100644 index f46653d..0000000 --- a/src/listener/LogInDB.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { LogType, Log } from "./types"; - -export class LogInDB { - private ringQueue: Log[] = []; - private ktonQueue: Log[] = []; - private bankQueue: Log[] = []; - // @ts-nocheck - private callback: (tx: string) => void = () => undefined; - - getQueue(type: LogType): Log[] { - switch (type) { - case 'ring': - return this.ringQueue; - case 'kton': - return this.ktonQueue; - case 'bank': - return this.bankQueue; - } - } - // getTx(type: LogType, acount: number): Log[] { - - // } - - afterTx(type: LogType, logs: Log[]) { - this.getQueue(type).push(...logs); - logs.map((log) => { - this.callback(log.transactionHash); - }) - } - - setCallback(callback: (tx: string) => void) { - this.callback = callback; - } -} - -const logInDB = new LogInDB(); - -export { logInDB }; diff --git a/src/listener/Starter.ts b/src/listener/Starter.ts deleted file mode 100644 index 2e95c27..0000000 --- a/src/listener/Starter.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { BlockchainState } from "./BlockchainState"; -import { EventParser } from "./EventParser"; -import { logInDB } from "./LogInDB"; -import { localConfig } from "./Config"; -import { blockInDB } from "./BlockInDB"; - -const blockchainState = new BlockchainState(); -const eventParser = new EventParser(); - -export default class { - start(config: any, callback: (tx: string) => void) { - localConfig.setConfig(config); - - blockInDB.setParsedEventBlockNumber(localConfig.info.START_BLOCK_NUMBER); - - logInDB.setCallback(callback); - - blockchainState.getState().then(() => { - eventParser.start(blockchainState); - }) - } -} \ No newline at end of file diff --git a/src/listener/cache.ts b/src/listener/cache.ts new file mode 100644 index 0000000..2e478ba --- /dev/null +++ b/src/listener/cache.ts @@ -0,0 +1,30 @@ +import { Config } from "../util"; +import path from "path"; +import fs from "fs"; +import { IEthereumHeaderThingWithProof } from "../api/types"; + +const cache = path.resolve((new Config()).path.root, "cache/blocks"); + +// Init Cache +export function initCache() { + if (fs.existsSync(cache)) { + fs.rmdirSync(cache, { recursive: true }); + } + + fs.mkdirSync(cache, { recursive: true }); +} + +// Get block from cache +export function getBlock(block: number): IEthereumHeaderThingWithProof | null { + const f = path.resolve(cache, `${block}.block`); + if (fs.existsSync(f)) { + return JSON.parse(fs.readFileSync(f).toString()); + } else { + return null; + } +} + +// Get block from cache +export function setBlock(block: number, headerThing: IEthereumHeaderThingWithProof) { + fs.writeFileSync(path.resolve(cache, `${block}.block`), JSON.stringify(headerThing)); +} diff --git a/src/listener/eth/BlockchainState.ts b/src/listener/eth/BlockchainState.ts new file mode 100644 index 0000000..2b4375a --- /dev/null +++ b/src/listener/eth/BlockchainState.ts @@ -0,0 +1,16 @@ +import { localConfig as Config } from "./Config"; +import { blockInDB } from "./DB"; + +export class BlockchainState { + getState(): Promise { + return BlockchainState.getBlockState().then(([blockInChain, latestBlockInDB]) => { + blockInDB.setLastBlockNumber(blockInChain); + }) + } + + static getBlockState(): Promise { + const latestBlockOnChain = Config.web3.eth.getBlockNumber(); + const latestBlockInDB = blockInDB.getBlockNumber(); + return Promise.all([latestBlockOnChain, latestBlockInDB]); + } +} diff --git a/src/listener/Config.ts b/src/listener/eth/Config.ts similarity index 99% rename from src/listener/Config.ts rename to src/listener/eth/Config.ts index 44ddbdf..8b76976 100644 --- a/src/listener/Config.ts +++ b/src/listener/eth/Config.ts @@ -15,5 +15,4 @@ export class Config { } const localConfig = new Config(); - export { localConfig } diff --git a/src/listener/eth/DB.ts b/src/listener/eth/DB.ts new file mode 100644 index 0000000..42a3dc4 --- /dev/null +++ b/src/listener/eth/DB.ts @@ -0,0 +1,61 @@ +export interface Blocks { + lastBlockNumber: number, + parsedEventBlockNumber: number +} + +export class BlockInDB { + private lastBlockNumber: number = 0; + private parsedEventBlockNumber: number = 0; + + setLastBlockNumber(num: number): void { + this.lastBlockNumber = num; + } + + setParsedEventBlockNumber(num: number): void { + this.parsedEventBlockNumber = num; + } + + getBlockNumber(): Blocks { + return { + lastBlockNumber: this.lastBlockNumber, + parsedEventBlockNumber: this.parsedEventBlockNumber + }; + } +} + +import { LogType, Log } from "./types"; + +export class LogInDB { + private ringQueue: Log[] = []; + private ktonQueue: Log[] = []; + private bankQueue: Log[] = []; + // @ts-nocheck + private callback: (tx: string, type: LogType, blockNumber: number) => void = () => undefined; + + getQueue(type: LogType): Log[] { + switch (type) { + case 'ring': + return this.ringQueue; + case 'kton': + return this.ktonQueue; + case 'bank': + return this.bankQueue; + } + } + + afterTx(type: LogType, logs: Log[], blockNumber: number) { + this.getQueue(type).push(...logs); + logs.map((log) => { + this.callback(log.transactionHash, type, blockNumber); + }) + } + + setCallback(callback: (tx: string, type: LogType, blockNumber: number) => void) { + this.callback = callback; + } +} + +const blockInDB = new BlockInDB(); +const logInDB = new LogInDB(); + +export { blockInDB, logInDB }; diff --git a/src/listener/EventParser.ts b/src/listener/eth/EventParser.ts similarity index 61% rename from src/listener/EventParser.ts rename to src/listener/eth/EventParser.ts index 92a3bbe..8f54d62 100644 --- a/src/listener/EventParser.ts +++ b/src/listener/eth/EventParser.ts @@ -1,11 +1,8 @@ import web3 from "web3"; - -import { Blocks, blockInDB } from "./BlockInDB"; -import { logInDB } from "./LogInDB"; +import { Blocks, blockInDB, logInDB } from "./DB"; import { localConfig as Config } from "./Config"; -import { setDelay } from "./Utils"; +import { delay, log } from "../../util"; import { BlockchainState } from "./BlockchainState"; - import { LogsOptions, Log, CoundBeNullLogs } from "./types"; export class EventParser { @@ -14,16 +11,18 @@ export class EventParser { private blockchainState: BlockchainState | null = null; start(blockchainState: BlockchainState | null) { - if (!this.blockchainState) { this.blockchainState = blockchainState; } this.blockchainState?.getState().then(() => { const blockNumber: Blocks = blockInDB.getBlockNumber(); - console.log('EventParser::starter', blockNumber); + log.trace(`EventParser::starter ${JSON.stringify(blockNumber)}`); if (blockNumber.lastBlockNumber - blockNumber.parsedEventBlockNumber > this.delayBlockNumber) { - this.startParseNextStepLogs(blockNumber.lastBlockNumber, blockNumber.parsedEventBlockNumber); + this.startParseNextStepLogs( + blockNumber.lastBlockNumber, + blockNumber.parsedEventBlockNumber, + ); } else { this.scheduleParsing(); } @@ -32,14 +31,13 @@ export class EventParser { startParseNextStepLogs(lastBlock: number, current: number) { let next: number = 0; - if (lastBlock - current > this.step) { next = current + this.step; } else { next = lastBlock; } - console.log(`EventParser::startParseNextStepLogs: parse block: [${current} - ${next})`); + log.trace(`EventParser::startParseNextStepLogs: parse block: [${current} - ${next})`); const issuingLogOptions: LogsOptions = { fromBlock: current, toBlock: next - 1, @@ -54,65 +52,71 @@ export class EventParser { topics: [Config.contracts.BANK.burnAndRedeemTopics] }; - const logs = Promise.all([this.fetchPastLogs(issuingLogOptions), this.fetchPastLogs(bankLogOptions)]); + const logs = Promise.all([ + this.fetchPastLogs(issuingLogOptions), + this.fetchPastLogs(bankLogOptions), + ]); logs.then(([ringLog, bankLog]) => { if (ringLog === null || bankLog === null) { this.scheduleParsing(); return; } - ringLog.map(log => { - if (log.topics.includes(web3.utils.padLeft(Config.contracts.RING.address.toLowerCase(), 64))) { - logInDB.afterTx('ring', [log]); + ringLog.map(l => { + if (l.topics.includes( + web3.utils.padLeft(Config.contracts.RING.address.toLowerCase(), 64) + )) { + logInDB.afterTx('ring', [l], l.blockNumber); } - if (log.topics.includes(web3.utils.padLeft(Config.contracts.KTON.address.toLowerCase(), 64))) { - logInDB.afterTx('kton', [log]); + if (l.topics.includes( + web3.utils.padLeft(Config.contracts.KTON.address.toLowerCase(), 64) + )) { + logInDB.afterTx('kton', [l], l.blockNumber); } }) - bankLog.map((log: Log): void => { - if (log.topics.includes( + bankLog.map((l: Log): void => { + if (l.topics.includes( web3.utils.padLeft(Config.contracts.BANK.burnAndRedeemTopics.toLowerCase(), 64) )) { - logInDB.afterTx('bank', [log]); + logInDB.afterTx('bank', [l], l.blockNumber); } }) - // console.log('ring', logInDB.getQueue('ring').map((item) => item.transactionHash)) - // console.log('kton', logInDB.getQueue('kton').map((item) => item.transactionHash)) - // console.log('bank', logInDB.getQueue('bank').map((item) => item.transactionHash)) blockInDB.setParsedEventBlockNumber(next); const blockNumber: Blocks = blockInDB.getBlockNumber(); if (blockNumber.lastBlockNumber - blockNumber.parsedEventBlockNumber > this.delayBlockNumber) { - setDelay(5000).then(() => { - this.startParseNextStepLogs(blockNumber.lastBlockNumber, blockNumber.parsedEventBlockNumber) + delay(5000).then(() => { + this.startParseNextStepLogs( + blockNumber.lastBlockNumber, + blockNumber.parsedEventBlockNumber, + ) }) } else { this.scheduleParsing(); } }).catch((err: any) => { - console.error(`startParseNextStepLogs: ${err}`); + log.err(`startParseNextStepLogs: ${err}`); this.scheduleParsing(); }); } fetchPastLogs(options: LogsOptions): Promise { return new Promise((resolve, reject) => { - Config.web3.eth.getPastLogs(options) - .then((log: any) => { - resolve(log); + .then((l: any) => { + resolve(l); }).catch((err: any) => { - console.error(`fetchPastLogs: ${err}`); + log.err(`fetchPastLogs: ${err}`); reject(null); }); }) } scheduleParsing() { - setDelay(30000).then(() => { + delay(30000).then(() => { this.start(this.blockchainState); }) } -} \ No newline at end of file +} diff --git a/src/listener/eth/index.ts b/src/listener/eth/index.ts new file mode 100644 index 0000000..c89a3ca --- /dev/null +++ b/src/listener/eth/index.ts @@ -0,0 +1,19 @@ +import { BlockchainState } from "./BlockchainState"; +import { EventParser } from "./EventParser"; +import { blockInDB, logInDB } from "./DB"; +import { localConfig } from "./Config"; +import { LogType } from "./types"; + +const blockchainState = new BlockchainState(); +const eventParser = new EventParser(); + +export function listen(config: any, callback: ( + tx: string, type: LogType, blockNumber: number, +) => void) { + localConfig.setConfig(config); + blockInDB.setParsedEventBlockNumber(localConfig.info.START_BLOCK_NUMBER); + logInDB.setCallback(callback); + blockchainState.getState().then(() => { + eventParser.start(blockchainState); + }) +} diff --git a/src/listener/types.ts b/src/listener/eth/types.ts similarity index 78% rename from src/listener/types.ts rename to src/listener/eth/types.ts index fc6e08e..c8a0bb2 100644 --- a/src/listener/types.ts +++ b/src/listener/eth/types.ts @@ -1,9 +1,7 @@ import { BlockNumber, Log } from "web3-core"; -export * from 'web3-core'; - +export { Log } from 'web3-core'; export type Topics = (string | string[] | null)[]; - export interface LogsOptions { fromBlock: BlockNumber; toBlock: BlockNumber; @@ -12,5 +10,4 @@ export interface LogsOptions { } export type CoundBeNullLogs = null | Log[]; - -export type LogType = 'ring' | 'kton' | 'bank'; \ No newline at end of file +export type LogType = 'ring' | 'kton' | 'bank'; diff --git a/src/listener/guard.ts b/src/listener/guard.ts new file mode 100644 index 0000000..4e5fa3a --- /dev/null +++ b/src/listener/guard.ts @@ -0,0 +1,50 @@ +import { ShadowAPI, API, ExResult } from "../api"; +import { log } from "../util"; + +// Proposal guard +export async function guard(api: API, shadow: ShadowAPI) { + let perms = 4; + if ((await api._.query.sudo.key()).toJSON().indexOf(api.account.address) > -1) { + perms = 7; + } else if (((await api._.query.council.members()).toJSON() as string[]).indexOf(api.account.address) > -1) { + perms = 5; + } else { + return; + } + + // start listening + let lock = false; + const handled: number[] = []; + setInterval(async () => { + if (lock) { return; } + const headers = (await api._.query.ethereumRelayerGame.pendingHeaders()).toJSON() as string[][]; + if (headers.length === 0) { + return; + } + + lock = true; + for (const h of headers) { + const blockNumber = Number.parseInt(h[1], 10); + if (handled.indexOf(blockNumber) > -1) { + break; + } + + const block = (await shadow.getHeaderThing(blockNumber)) as any; + if (JSON.stringify(block) === JSON.stringify(h[2])) { + const res: ExResult = await api.approveBlock(blockNumber, perms); + if (!res.isOk) { + log.err(res.toString()) + } + } else { + const res = await api.rejectBlock(h[1], perms); + if (!res.isOk) { + log.err(res) + log.err(res.toString()) + } + } + handled.push(blockNumber); + } + + lock = false; + }, 10000); +} diff --git a/src/listener/index.ts b/src/listener/index.ts new file mode 100644 index 0000000..3c56c7d --- /dev/null +++ b/src/listener/index.ts @@ -0,0 +1,4 @@ +export { guard } from "./guard"; +export * as Cache from "./cache"; +export { relay } from "./relay"; +export { listen as ethereum } from "./eth"; diff --git a/src/listener/relay.ts b/src/listener/relay.ts new file mode 100644 index 0000000..ede160c --- /dev/null +++ b/src/listener/relay.ts @@ -0,0 +1,75 @@ +import { ShadowAPI, API } from "../api"; +import { log } from "../util"; +import { DispatchError } from "@polkadot/types/interfaces/types"; +import { IEthereumHeaderThingWithProof, ITx } from "../api/types"; +import { Cache } from "./" + +// Listen and submit proposals +export function relay(api: API, shadow: ShadowAPI, queue: ITx[]) { + // Subscribe to system events via storage + api._.query.system.events((events: any) => { + events.forEach(async (record: any) => { + const { event, phase } = record; + const types = event.typeDef; + + if (event.method === "GameOver") { + log.trace("Gameover"); + } + + if (event.method === "PendingHeaderApproved") { + log.trace(`\t${event.section}:${event.method}:: (phase=${phase.toString()})`); + log.trace(`\t\t${event.meta.documentation.toString()}`); + const lastConfirmed = await api.lastConfirm(); + queue.filter((tx) => tx.relayedBlock === lastConfirmed).forEach(async (tx) => { + await api.redeem(tx.ty, await shadow.getReceipt(tx.tx, lastConfirmed)); + }); + + queue = queue.filter((tx) => tx.relayedBlock !== lastConfirmed); + } + + // Show what we are busy with + if (event.method === "NewRound") { + log.trace(`\t${event.section}:${event.method}:: (phase=${phase.toString()})`); + log.trace(`\t\t${event.meta.documentation.toString()}`); + + // Samples + const lastLeaf = Math.max(...(event.data[1].toJSON() as number[])); + const members: number[] = event.data[1].toJSON() as number[]; + + // Get proposals + let newMember: number = 0; + let proposals: IEthereumHeaderThingWithProof[] = []; + members.forEach((i: number) => { + const block = Cache.getBlock(i); + if (block) { + proposals.push(block); + } else { + newMember = i; + } + }) + + const newProposal = await shadow.getProposal([newMember], newMember, lastLeaf); + Cache.setBlock(newMember, Object.assign(JSON.parse(JSON.stringify(newProposal)), { + ethash_proof: [], + mmr_root: "", + mmr_proof: [], + })); + proposals = proposals.concat(newProposal); + + // Submit new proposals + await api.submitProposal(proposals); + + // Loop through each of the parameters, displaying the type and data + event.data.forEach((data: any, index: any) => { + log.trace(`\t\t\t${types[index].type}: ${data.toString()}`); + }); + } + + if (event.data[0] && (event.data[0] as DispatchError).isModule) { + log.err(api._.registry.findMetaError( + (event.data[0] as DispatchError).asModule.toU8a(), + )); + } + }); + }); +} diff --git a/src/listener/test.ts b/src/listener/test.ts deleted file mode 100644 index 60b2ee7..0000000 --- a/src/listener/test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import EventStarter from "./Starter"; - -const eventStarter = new EventStarter(); -eventStarter.start({ - "RPC_SERVER": "https://ropsten.infura.io/v3/xx", - "START_BLOCK_NUMBER": 8609800, - "CONTRACT": { - "RING": { - "address": "0xb52FBE2B925ab79a821b261C82c5Ba0814AAA5e0", - "burnAndRedeemTopics": "0xc9dcda609937876978d7e0aa29857cb187aea06ad9e843fd23fd32108da73f10" - }, - "KTON": { - "address": "0x1994100c58753793D52c6f457f189aa3ce9cEe94", - "burnAndRedeemTopics": "0xc9dcda609937876978d7e0aa29857cb187aea06ad9e843fd23fd32108da73f10" - }, - "BANK": { - "address": "0x6EF538314829EfA8386Fc43386cB13B4e0A67D1e", - "burnAndRedeemTopics": "0xe77bf2fa8a25e63c1e5e29e1b2fcb6586d673931e020c4e3ffede453b830fb12" - }, - "ISSUING": { - "address": "0x49262B932E439271d05634c32978294C7Ea15d0C" - } - } -}, (tx: any) => { - console.log('test::callback:',tx) -}); \ No newline at end of file diff --git a/src/util/block.ts b/src/util/block.ts deleted file mode 100644 index 3f876f7..0000000 --- a/src/util/block.ts +++ /dev/null @@ -1,162 +0,0 @@ -/* tslint:disable:variable-name */ -import { bufferToU8a, u8aToHex } from "@polkadot/util"; -import { rlp } from "ethereumjs-util"; - -export interface IEthBlock { - mixHash?: string; - nonce: string; - parentHash: string; - timestamp: number; - number: number; - miner: string; - totalDifficulty: number; - transactionsRoot?: string; - sha3Uncles: string; - extraData: string; - stateRoot: string; - receiptsRoot?: string; - transactions: string[]; - uncles: string[]; - logsBloom: string; - gasUsed: number; - gasLimit: number; - difficulty: number; - seal?: string; - size?: number; - hash: string; -} - -export interface IDarwiniaEthBlock { - parent_hash: string; - timestamp: number; - number: number; - author: string; - transactions_root?: string; - uncles_hash: string; - extra_data: string; - state_root: string; - receipts_root?: string; - log_bloom: string; - gas_used: number; - gas_limit: number; - difficulty: number; - seal: string[]; - hash: string; -} - -/** - * Block in Darwinia - */ -export class Block { - /** - * Parse EthBlock to DarwiniaEthBlock - * - * @param {IEthBlock} block - Ethereum block - */ - public static parse(block: IEthBlock): IDarwiniaEthBlock { - return Block.from(block).toJson(); - } - - /** - * Generate Darwinia style Ethereum block from raw Ethereum block - * - * @param {IEthBlock} block - Ethereum block - */ - public static from(block: IEthBlock): Block { - let mh = block.mixHash; - if (mh === undefined) { - mh = ""; - } - - const mixh = bufferToU8a(rlp.encode(mh)); - const nonce = bufferToU8a(rlp.encode(block.nonce)); - const seal = [u8aToHex(mixh), u8aToHex(nonce)]; - - const darwiniaBlock = new Block(); - darwiniaBlock.author = block.miner; - darwiniaBlock.difficulty = block.difficulty; - darwiniaBlock.extra_data = block.extraData; - darwiniaBlock.gas_limit = block.gasLimit; - darwiniaBlock.gas_used = block.gasUsed; - darwiniaBlock.hash = block.hash; - darwiniaBlock.log_bloom = block.logsBloom; - darwiniaBlock.number = block.number; - darwiniaBlock.parent_hash = block.parentHash; - darwiniaBlock.receipts_root = block.receiptsRoot; - darwiniaBlock.seal = seal; - darwiniaBlock.state_root = block.stateRoot; - darwiniaBlock.timestamp = block.timestamp; - darwiniaBlock.transactions_root = block.transactionsRoot; - darwiniaBlock.uncles_hash = block.sha3Uncles; - - return darwiniaBlock; - } - - public parent_hash: string; - public timestamp: number; - public number: number; - public author: string; - public transactions_root?: string; - public uncles_hash: string; - public extra_data: string; - public state_root: string; - public receipts_root?: string; - public log_bloom: string; - public gas_used: number; - public gas_limit: number; - public difficulty: number; - public seal: string[]; - public hash: string; - - constructor() { - this.parent_hash = ""; - this.timestamp = 0; - this.number = 0; - this.author = ""; - this.transactions_root = ""; - this.uncles_hash = ""; - this.extra_data = ""; - this.state_root = ""; - this.receipts_root = ""; - this.log_bloom = ""; - this.gas_used = 0; - this.gas_limit = 0; - this.difficulty = 0; - this.seal = []; - this.hash = ""; - } - - /** - * convert darwinia block class to json - * - * @return {DarwiniaEthBlock} block - darwinia eth block in json - */ - public toJson(): IDarwiniaEthBlock { - return { - parent_hash: this.parent_hash, - timestamp: this.timestamp, - number: this.number, - author: this.author, - transactions_root: this.transactions_root, - uncles_hash: this.uncles_hash, - extra_data: this.extra_data, - state_root: this.state_root, - receipts_root: this.receipts_root, - log_bloom: this.log_bloom, - gas_used: this.gas_used, - gas_limit: this.gas_limit, - difficulty: this.difficulty, - seal: this.seal, - hash: this.hash, - }; - } - - /** - * stringify and pretty darwinia block - * - * @return {String} block - darwinia block in string - */ - public toString(): string { - return JSON.stringify(this.toJson(), null, 2); - } -} diff --git a/src/util/cfg.ts b/src/util/cfg.ts index ac80caf..d4da2df 100644 --- a/src/util/cfg.ts +++ b/src/util/cfg.ts @@ -47,6 +47,20 @@ export class Config { } } + /// Load and merge config from file + static load(p: string, defaultConfig: Record): Record { + let json: Record = defaultConfig; + if (!fs.existsSync(p)) { + fs.writeFileSync(p, JSON.stringify(json, null, 2)); + } else { + const cur = Object.assign(json, JSON.parse(fs.readFileSync(p, "utf8"))); + fs.writeFileSync(p, JSON.stringify(cur, null, 2)); + json = cur; + } + + return json + } + public node: string; public path: IConfigPath; public shadow: string; @@ -61,7 +75,7 @@ export class Config { const types = path.resolve(root, "types.json"); const ethereumListener = path.resolve(root, "ethereum_listener.json"); - // init pathes + // Init pathes this.path = { conf, root, @@ -69,47 +83,17 @@ export class Config { ethereumListener, }; - // check root + // Check root if (!fs.existsSync(root)) { fs.mkdirSync(root, { recursive: true }); } - // load config.json - let cj: IConfig = rawCj; - if (!fs.existsSync(conf)) { - fs.writeFileSync(conf, JSON.stringify(cj, null, 2)); - } else { - const curConfig = JSON.parse(fs.readFileSync(conf, "utf8")); - const mergeConfig = Object.assign(rawCj, curConfig); - if (mergeConfig !== curConfig) { - fs.writeFileSync(conf, JSON.stringify(mergeConfig, null, 2)); - } - - // assign cj - cj = mergeConfig; - } - - // load types.json - let tj: Record = rawTj; - if (!fs.existsSync(types)) { - fs.writeFileSync(types, JSON.stringify(tj, null, 2)); - } else { - tj = JSON.parse(fs.readFileSync(types, "utf8")); - } - - // Load ethereumListener.json - let ej: Record = rawEj; - if (!fs.existsSync(ethereumListener)) { - fs.writeFileSync(ethereumListener, JSON.stringify(ej, null, 2)); - } else { - ej = JSON.parse(fs.readFileSync(ethereumListener, "utf8")); - } - + const cj = Config.load(conf, rawCj); this.node = cj.node; this.seed = cj.seed; this.shadow = cj.shadow; - this.types = tj; - this.ethereumListener = ej; + this.types = Config.load(types, rawTj); + this.ethereumListener = Config.load(ethereumListener, rawEj); // Warn config Config.warn(this); diff --git a/src/listener/Utils.ts b/src/util/delay.ts similarity index 53% rename from src/listener/Utils.ts rename to src/util/delay.ts index dc1c1ab..3933924 100644 --- a/src/listener/Utils.ts +++ b/src/util/delay.ts @@ -1,6 +1,6 @@ - -export function setDelay(t: number): Promise { +/// Async delay +export function delay(t: number): Promise { return new Promise((resolve) => { setTimeout(resolve, t); }); -} \ No newline at end of file +} diff --git a/src/util/download.ts b/src/util/download.ts index 949e7d1..440f246 100644 --- a/src/util/download.ts +++ b/src/util/download.ts @@ -3,12 +3,6 @@ import fs from "fs"; import got from "got"; import path from "path"; import Progress from "progress"; -import stream from "stream"; -import tar from "tar"; -import { promisify } from "util"; - -// constants -const pipline = promisify(stream.pipeline); /** * Download the get resp to file @@ -21,7 +15,6 @@ export async function download( dir: string, url: string, file: string, - tarFile = false, ): Promise { fs.mkdirSync(dir, { recursive: true }); const bar = new Progress(`[ ${chalk.cyan("wait")} ] [:bar] :rate/bps :etas`, { @@ -32,22 +25,11 @@ export async function download( }); let prePercent = 0; - if (tarFile) { - await pipline( - got.stream(url) - .on("downloadProgress", (progress) => { - bar.tick(progress.percent - prePercent); - prePercent = progress.percent; - }), - tar.x({ cwd: dir, strip: 1 }) - ); - } else { - const res = await got(url) - .on("downloadProgress", (progress) => { - bar.tick(progress.percent - prePercent); - prePercent = progress.percent; - }); + const res = await got(url) + .on("downloadProgress", (progress) => { + bar.tick(progress.percent - prePercent); + prePercent = progress.percent; + }); - fs.writeFileSync(path.resolve(dir, file), res.body); - } + fs.writeFileSync(path.resolve(dir, file), res.body); } diff --git a/src/util/index.ts b/src/util/index.ts index b6a4baf..df92e0b 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -1,17 +1,16 @@ -import { Block, IEthBlock, IDarwiniaEthBlock } from "./block"; import { Config, TYPES_URL } from "./cfg"; import { download } from "./download"; import { log } from "./log"; import { whereisPj } from "./pj"; +import { delay } from "./delay"; import chalk from "chalk"; // exports export { - Block, Config, + delay, chalk, download, - IDarwiniaEthBlock, IEthBlock, log, TYPES_URL, whereisPj, diff --git a/src/util/static/ethereum_listener.json b/src/util/static/ethereum_listener.json index 0b74a4e..754b68a 100644 --- a/src/util/static/ethereum_listener.json +++ b/src/util/static/ethereum_listener.json @@ -1,19 +1,21 @@ { - "RPC_SERVER": "http://darwinia-dev-environment:8545", + "RPC_SERVER": "https://ropsten.infura.io/v3/0bfb9acbb13c426097aabb1d81a9d016", + "START_BLOCK_NUMBER": 8647036, "CONTRACT": { "RING": { - "address": "0x07d9cEB75403892798cF3ab77A9422549ba647C3" + "address": "0xb52FBE2B925ab79a821b261C82c5Ba0814AAA5e0", + "burnAndRedeemTopics": "0xc9dcda609937876978d7e0aa29857cb187aea06ad9e843fd23fd32108da73f10" }, "KTON": { - "address": "0x7C2f9AB7Cb17cA2e745b4893873906a0531Abb5F" + "address": "0x1994100c58753793D52c6f457f189aa3ce9cEe94", + "burnAndRedeemTopics": "0xc9dcda609937876978d7e0aa29857cb187aea06ad9e843fd23fd32108da73f10" }, "BANK": { - "address": "0x14dD0a609C77D09114898138b2eA3459D325D1B0", + "address": "0x6EF538314829EfA8386Fc43386cB13B4e0A67D1e", "burnAndRedeemTopics": "0xe77bf2fa8a25e63c1e5e29e1b2fcb6586d673931e020c4e3ffede453b830fb12" }, "ISSUING": { - "address": "0xE445275B93A94337Da05e4BCA7F383924228247a", - "burnAndRedeemTopics": "0xc9dcda609937876978d7e0aa29857cb187aea06ad9e843fd23fd32108da73f10" + "address": "0x49262B932E439271d05634c32978294C7Ea15d0C" } } } \ No newline at end of file diff --git a/src/util/static/types.json b/src/util/static/types.json index 1cafae9..1e675a2 100644 --- a/src/util/static/types.json +++ b/src/util/static/types.json @@ -175,8 +175,7 @@ }, "RedeemFor": { "_enum": { - "Ring": null, - "Kton": null, + "Token": null, "Deposit": null } }, @@ -185,6 +184,7 @@ "proof": "Bytes", "header_hash": "H256" }, + "EthereumReceiptProofThing": "(EthereumHeader, EthereumReceiptProof, MMRProof)", "MMRProof": "Vec", "__[pallet.claims]__": {}, "OtherSignature": { @@ -208,14 +208,16 @@ }, "__[pallet.relayer-game]__": {}, "Round": "u64", - "TcHeaderThingWithProof": "Vec", + "__[these things should be eventually refactored]__": {}, + "TcHeaderThingWithProof": "EthereumHeaderThingWithProof", "TcHeaderThing": "EthereumHeaderThing", "TcBlockNumber": "u64", - "TcHeaderHash": "Vec", + "TcHeaderHash": "H256", + "__[derotcafer yllautneve eb dluohs sgniht eseht]__": {}, "GameId": "TcBlockNumber", "RelayProposalT": { "relayer": "AccountId", - "bonded_samples": "Vec<(Balance, TcHeaderThing)>", + "bonded_proposal": "Vec<(Balance, TcHeaderThing)>", "extend_from_header_hash": "Option" }, "__[node.rpc]__": {}, @@ -225,4 +227,4 @@ "StakingRuntimeDispatchInfo": { "power": "Power" } -} +} \ No newline at end of file