From 3ab72c072c33f95b2b63e2bea95f09a656077ab9 Mon Sep 17 00:00:00 2001 From: emperorhan Date: Thu, 20 Jun 2019 16:17:48 +0900 Subject: [PATCH 1/3] feat: ignore vscode --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6ee232d..11ddbdb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules/ .DS_Store package-lock.json +.vscode \ No newline at end of file From 9e8ecc8d88abdf73696075bc51266620aeba8483 Mon Sep 17 00:00:00 2001 From: emperorhan Date: Thu, 20 Jun 2019 16:19:26 +0900 Subject: [PATCH 2/3] feat: implement eos action watcher basic frame --- server/package.json | 8 +- server/src/config/watcher.ts | 7 + .../actionHandler/ObjectActionHandler.ts | 116 +++++++++++ .../actionHandler/handlerVersions/v1/index.ts | 190 ++++++++++++++++++ .../eos/watcher/test/config/watcher.ts | 7 + .../blockchain/eos/watcher/types/types.d.ts | 78 +++++++ .../service/blockchain/eos/watcher/watcher.ts | 32 +++ 7 files changed, 437 insertions(+), 1 deletion(-) create mode 100644 server/src/config/watcher.ts create mode 100644 server/src/port/adapter/service/blockchain/eos/watcher/actionHandler/ObjectActionHandler.ts create mode 100644 server/src/port/adapter/service/blockchain/eos/watcher/actionHandler/handlerVersions/v1/index.ts create mode 100644 server/src/port/adapter/service/blockchain/eos/watcher/test/config/watcher.ts create mode 100644 server/src/port/adapter/service/blockchain/eos/watcher/types/types.d.ts create mode 100644 server/src/port/adapter/service/blockchain/eos/watcher/watcher.ts diff --git a/server/package.json b/server/package.json index 6af20bd..4ba1269 100644 --- a/server/package.json +++ b/server/package.json @@ -25,6 +25,11 @@ "@nestjs/platform-express": "^6.0.0", "@nestjs/typeorm": "^6.1.1", "class-validator": "^0.9.1", + "demux": "^4.0.0", + "demux-eos": "4.0.1", + "dotenv": "^8.0.0", + "eosjs": "^20.0.0", + "massive": "^5.11.0", "nestjs-config": "^1.4.0", "nestjs-typeorm-paginate": "^0.1.7", "reflect-metadata": "^0.1.12", @@ -50,7 +55,8 @@ "ts-node": "^7.0.1", "tsconfig-paths": "^3.7.0", "tslint": "^5.12.1", - "typescript": "^3.5.1" + "typescript": "^3.5.1", + "tsc-watch": "^2.2.1" }, "jest": { "moduleFileExtensions": [ diff --git a/server/src/config/watcher.ts b/server/src/config/watcher.ts new file mode 100644 index 0000000..fcf58ff --- /dev/null +++ b/server/src/config/watcher.ts @@ -0,0 +1,7 @@ +export default { + endPoint: 'http://211.195.229.86:8888', + irreversible: 'false', + startAt: 3136735, + stopAt: 0, + contract: 'ghostghost12', +}; diff --git a/server/src/port/adapter/service/blockchain/eos/watcher/actionHandler/ObjectActionHandler.ts b/server/src/port/adapter/service/blockchain/eos/watcher/actionHandler/ObjectActionHandler.ts new file mode 100644 index 0000000..40ccad4 --- /dev/null +++ b/server/src/port/adapter/service/blockchain/eos/watcher/actionHandler/ObjectActionHandler.ts @@ -0,0 +1,116 @@ +import { + AbstractActionHandler, + IndexState, + Block, + NextBlock, + VersionedAction, +} from 'demux'; +import * as State from '../types/types'; + +export class ObjectActionHandler extends AbstractActionHandler { + constructor([handleVersion]: any, stopAt: number) { + super([handleVersion]); + this.stopAt = stopAt; + this.state.indexState = { + blockNumber: 0, + blockHash: '', + isReplay: false, + handlerVersionName: 'v1', + }; + } + public stopService = (blockNumber: number) => { + // Function stop the service when meet the stopAt block number + if (blockNumber >= this.stopAt) { + // console.log('\n####################\n# STOP AT: ', blockNumber); + // console.log('####################\n'); + process.exit(1); + } + } + + public state: any; + private hashHistory: { [key: number]: string } = { 0: '' }; + private stopAt: number; + + get _handlerVersionName() { + return this.handlerVersionName; + } + + // tslint:disable-next-line + public async handleWithState(handle: (state: any) => void) { + try { + await handle(this.state); + } catch (err) { + throw new Error('handle state err'); + } + } + + public async rollbackTo(blockNumber: number) { + this.setLastProcessedBlockNumber(blockNumber); + this.setLastProcessedBlockHash(this.hashHistory[blockNumber]); + this.state.indexState = { + ...this.state.indexState, + blockNumber, + blockHash: this.hashHistory[blockNumber], + }; + } + + public setLastProcessedBlockHash(hash: string) { + this.lastProcessedBlockHash = hash; + } + + public setLastProcessedBlockNumber(num: number) { + this.lastProcessedBlockNumber = num; + } + + public async _applyUpdaters( + state: any, + block: Block, + context: any, + isReplay: boolean, + ): Promise { + return this.applyUpdaters(state, block, context, isReplay); + } + + public _runEffects( + versionedActions: VersionedAction[], + context: any, + nextBlock: NextBlock, + ) { + this.runEffects(versionedActions, context, nextBlock); + } + + protected async loadIndexState(): Promise { + return this.state.indexState; + } + + public async handleBlock( + nextBlock: NextBlock, + isReplay: boolean, + ): Promise { + const { blockNumber, blockHash } = nextBlock.block.blockInfo; + this.hashHistory[blockNumber] = blockHash; + return super.handleBlock(nextBlock, isReplay); + } + + protected async updateIndexState( + state: any, + block: Block, + isReplay: boolean, + handlerVersionName: string, + ) { + // console.log("Processing block: ", block.blockInfo.blockNumber); + const { blockNumber, blockHash } = block.blockInfo; + + state.indexState = { + blockNumber, + blockHash, + isReplay, + handlerVersionName, + }; + if (this.stopAt) { + this.stopService(blockNumber); + } + } + + protected async setup(): Promise {} +} diff --git a/server/src/port/adapter/service/blockchain/eos/watcher/actionHandler/handlerVersions/v1/index.ts b/server/src/port/adapter/service/blockchain/eos/watcher/actionHandler/handlerVersions/v1/index.ts new file mode 100644 index 0000000..52aed86 --- /dev/null +++ b/server/src/port/adapter/service/blockchain/eos/watcher/actionHandler/handlerVersions/v1/index.ts @@ -0,0 +1,190 @@ +import * as State from '../../../types/types'; +import { BlockInfo } from 'demux'; + +const account = process.env.CONTRACT; + +const parseTokenString = ( + tokenString: string, +): { amount: number; symbol: string } => { + const [amountString, symbol] = tokenString.split(' '); + const amount = parseFloat(amountString); + return { amount, symbol }; +}; + +const updateCreateEggData = ( + state: State.CreateEggState, + payload: any, + blockInfo: BlockInfo, + context: any, +): void => { + state.gene = payload.data.gene; + state.owner = payload.data.owner; + state.trx_id = payload.transactionId; + state.indexState.blockNumber = blockInfo.blockNumber; + state.indexState.blockHash = blockInfo.blockHash; + + // database save + + context.stateCopy = JSON.parse(JSON.stringify(state)); +}; + +const updateSendData = ( + state: State.SendState, + payload: any, + blockInfo: BlockInfo, + context: any, +): void => { + state.from = payload.data.from; + state.to = payload.data.to; + state.gene = payload.data.gene; + state.trx_id = payload.transactionId; + state.indexState.blockNumber = blockInfo.blockNumber; + state.indexState.blockHash = blockInfo.blockHash; + + // database save + + context.stateCopy = JSON.parse(JSON.stringify(state)); +}; + +const updateLevelUpData = ( + state: State.LevelUpState, + payload: any, + blockInfo: BlockInfo, + context: any, +): void => { + state.owner = payload.data.owner; + state.gene = payload.data.gene; + state.level = payload.data.level; + state.trx_id = payload.transactionId; + state.indexState.blockNumber = blockInfo.blockNumber; + state.indexState.blockHash = blockInfo.blockHash; + + // database save + + context.stateCopy = JSON.parse(JSON.stringify(state)); +}; + +const updateAuctionData = ( + state: State.AuctionState, + payload: any, + blockInfo: BlockInfo, + context: any, +): void => { + const { amount, symbol } = parseTokenString(payload.data.min_price); + state.auctioneer = payload.data.auctioneer; + state.gene = payload.data.gene; + state.minPrice = amount; + state.deadline = payload.data.sec + (Date.now() / 1000); + state.trx_id = payload.transactionId; + state.indexState.blockNumber = blockInfo.blockNumber; + state.indexState.blockHash = blockInfo.blockHash; + + // database save + + context.stateCopy = JSON.parse(JSON.stringify(state)); +}; + +const updateBidData = ( + state: State.BidState, + payload: any, + blockInfo: BlockInfo, + context: any, +): void => { + const { amount, symbol } = parseTokenString(payload.data.bid); + state.bidder = payload.data.bidder; + state.gene = payload.data.gene; + state.bid = amount; + state.trx_id = payload.transactionId; + state.indexState.blockNumber = blockInfo.blockNumber; + state.indexState.blockHash = blockInfo.blockHash; + + // database save + + context.stateCopy = JSON.parse(JSON.stringify(state)); +}; + +const updateClaimData = ( + state: State.ClaimState, + payload: any, + blockInfo: BlockInfo, + context: any, +): void => { + state.requester = payload.data.requester; + state.gene = payload.data.gene; + state.trx_id = payload.transactionId; + state.indexState.blockNumber = blockInfo.blockNumber; + state.indexState.blockHash = blockInfo.blockHash; + + // database save + + context.stateCopy = JSON.parse(JSON.stringify(state)); +}; + +const updaters = [ + { + actionType: `${account}::createegg`, + apply: updateCreateEggData, + }, + { + actionType: `${account}::send`, + apply: updateSendData, + }, + { + actionType: `${account}::levelup`, + apply: updateLevelUpData, + }, + { + actionType: `${account}::auction`, + apply: updateAuctionData, + }, + { + actionType: `${account}::bid`, + apply: updateBidData, + }, + { + actionType: `${account}::claim`, + apply: updateClaimData, + }, +]; + +const logUpdate = (payload: any, blockInfo: BlockInfo, context: any): void => { + // console.info( + // 'State updated:\n', + // JSON.stringify(context.stateCopy, null, 2), + // ); +}; + +const effects = [ + { + actionType: `${account}::createegg`, + run: logUpdate, + }, + { + actionType: `${account}::send`, + run: logUpdate, + }, + { + actionType: `${account}::levelup`, + run: logUpdate, + }, + { + actionType: `${account}::auction`, + run: logUpdate, + }, + { + actionType: `${account}::bid`, + run: logUpdate, + }, + { + actionType: `${account}::claim`, + run: logUpdate, + }, +]; + +const handlerVersion = { + versionName: 'v1', + updaters, + effects, +}; + +export default handlerVersion; diff --git a/server/src/port/adapter/service/blockchain/eos/watcher/test/config/watcher.ts b/server/src/port/adapter/service/blockchain/eos/watcher/test/config/watcher.ts new file mode 100644 index 0000000..fcf58ff --- /dev/null +++ b/server/src/port/adapter/service/blockchain/eos/watcher/test/config/watcher.ts @@ -0,0 +1,7 @@ +export default { + endPoint: 'http://211.195.229.86:8888', + irreversible: 'false', + startAt: 3136735, + stopAt: 0, + contract: 'ghostghost12', +}; diff --git a/server/src/port/adapter/service/blockchain/eos/watcher/types/types.d.ts b/server/src/port/adapter/service/blockchain/eos/watcher/types/types.d.ts new file mode 100644 index 0000000..2552892 --- /dev/null +++ b/server/src/port/adapter/service/blockchain/eos/watcher/types/types.d.ts @@ -0,0 +1,78 @@ +export interface CreateEggState { + gene: string; + owner: string; + trx_id: string; + indexState: { + blockNumber: number; + blockHash: string; + isReplay: boolean; + handlerVersionName: string; + }; +} +export interface SendState { + from: string; + to: string; + gene: string; + trx_id: string; + indexState: { + blockNumber: number; + blockHash: string; + isReplay: boolean; + handlerVersionName: string; + }; +} +export interface LevelUpState { + owner: string; + gene: string; + level: number; + trx_id: string; + indexState: { + blockNumber: number; + blockHash: string; + isReplay: boolean; + handlerVersionName: string; + }; +} +export interface AuctionState { + auctioneer: string; + gene: string; + minPrice: number; + deadline: number; + trx_id: string; + indexState: { + blockNumber: number; + blockHash: string; + isReplay: boolean; + handlerVersionName: string; + }; +} +export interface BidState { + bidder: string; + gene: string; + bid: number; + trx_id: string; + indexState: { + blockNumber: number; + blockHash: string; + isReplay: boolean; + handlerVersionName: string; + }; +} +export interface ClaimState { + requester: string; + gene: string; + trx_id: string; + indexState: { + blockNumber: number; + blockHash: string; + isReplay: boolean; + handlerVersionName: string; + }; +} +export interface NodeosActionReaderOptions extends ActionReaderOptions { + nodeosEndpoint?: string; +} +export interface ActionReaderOptions { + startAtBlock?: number; + onlyIrreversible?: boolean; +} diff --git a/server/src/port/adapter/service/blockchain/eos/watcher/watcher.ts b/server/src/port/adapter/service/blockchain/eos/watcher/watcher.ts new file mode 100644 index 0000000..85274d9 --- /dev/null +++ b/server/src/port/adapter/service/blockchain/eos/watcher/watcher.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@nestjs/common'; +import { InjectConfig } from 'nestjs-config'; +import { BaseActionWatcher } from 'demux'; +import { NodeosActionReader } from 'demux-eos'; +import { NodeosActionReaderOptions } from './types/types'; +import { ObjectActionHandler } from './actionHandler/ObjectActionHandler'; +import handlerVersion from './actionHandler/handlerVersions/v1'; + +@Injectable() +export class WatcherService { + constructor(@InjectConfig() private config) { + const actionHandler = new ObjectActionHandler([handlerVersion], this.config.get('watch.stopAt')); + const actionReaderOpts: NodeosActionReaderOptions = { + nodeosEndpoint: this.config.get('watcher.endPoint'), + onlyIrreversible: this.config.get('watcher.irreversible') + ? true + : false, + startAtBlock: this.config.get('watcher.startAt'), + }; + const actionReader = new NodeosActionReader(actionReaderOpts); + this.actionWatcher = new BaseActionWatcher( + actionReader, + actionHandler, + 250, + ); + } + private actionWatcher: BaseActionWatcher; + + public watch() { + this.actionWatcher.watch(); + } +} From 2af0223383ce78be433df6eb34ea4ae59cde9acd Mon Sep 17 00:00:00 2001 From: emperorhan Date: Thu, 20 Jun 2019 17:12:35 +0900 Subject: [PATCH 3/3] fix: state type --- .../actionHandler/ObjectActionHandler.ts | 41 +++++++------- .../blockchain/eos/watcher/types/types.d.ts | 53 ++++--------------- .../blockchain/eos/watcher/watcher.spec.ts | 30 +++++++++++ 3 files changed, 62 insertions(+), 62 deletions(-) create mode 100644 server/src/port/adapter/service/blockchain/eos/watcher/watcher.spec.ts diff --git a/server/src/port/adapter/service/blockchain/eos/watcher/actionHandler/ObjectActionHandler.ts b/server/src/port/adapter/service/blockchain/eos/watcher/actionHandler/ObjectActionHandler.ts index 40ccad4..46320f6 100644 --- a/server/src/port/adapter/service/blockchain/eos/watcher/actionHandler/ObjectActionHandler.ts +++ b/server/src/port/adapter/service/blockchain/eos/watcher/actionHandler/ObjectActionHandler.ts @@ -3,20 +3,23 @@ import { IndexState, Block, NextBlock, - VersionedAction, -} from 'demux'; -import * as State from '../types/types'; + VersionedAction +} from "demux"; +import { State } from "../types/types"; export class ObjectActionHandler extends AbstractActionHandler { constructor([handleVersion]: any, stopAt: number) { super([handleVersion]); this.stopAt = stopAt; - this.state.indexState = { - blockNumber: 0, - blockHash: '', - isReplay: false, - handlerVersionName: 'v1', - }; + this.state = { + trx_id: "", + indexState: { + blockNumber: 0, + blockHash: "", + isReplay: false, + handlerVersionName: "v1" + } + } } public stopService = (blockNumber: number) => { // Function stop the service when meet the stopAt block number @@ -25,10 +28,10 @@ export class ObjectActionHandler extends AbstractActionHandler { // console.log('####################\n'); process.exit(1); } - } + }; - public state: any; - private hashHistory: { [key: number]: string } = { 0: '' }; + private state: State; + private hashHistory: { [key: number]: string } = { 0: "" }; private stopAt: number; get _handlerVersionName() { @@ -40,7 +43,7 @@ export class ObjectActionHandler extends AbstractActionHandler { try { await handle(this.state); } catch (err) { - throw new Error('handle state err'); + throw new Error("handle state err"); } } @@ -50,7 +53,7 @@ export class ObjectActionHandler extends AbstractActionHandler { this.state.indexState = { ...this.state.indexState, blockNumber, - blockHash: this.hashHistory[blockNumber], + blockHash: this.hashHistory[blockNumber] }; } @@ -66,7 +69,7 @@ export class ObjectActionHandler extends AbstractActionHandler { state: any, block: Block, context: any, - isReplay: boolean, + isReplay: boolean ): Promise { return this.applyUpdaters(state, block, context, isReplay); } @@ -74,7 +77,7 @@ export class ObjectActionHandler extends AbstractActionHandler { public _runEffects( versionedActions: VersionedAction[], context: any, - nextBlock: NextBlock, + nextBlock: NextBlock ) { this.runEffects(versionedActions, context, nextBlock); } @@ -85,7 +88,7 @@ export class ObjectActionHandler extends AbstractActionHandler { public async handleBlock( nextBlock: NextBlock, - isReplay: boolean, + isReplay: boolean ): Promise { const { blockNumber, blockHash } = nextBlock.block.blockInfo; this.hashHistory[blockNumber] = blockHash; @@ -96,7 +99,7 @@ export class ObjectActionHandler extends AbstractActionHandler { state: any, block: Block, isReplay: boolean, - handlerVersionName: string, + handlerVersionName: string ) { // console.log("Processing block: ", block.blockInfo.blockNumber); const { blockNumber, blockHash } = block.blockInfo; @@ -105,7 +108,7 @@ export class ObjectActionHandler extends AbstractActionHandler { blockNumber, blockHash, isReplay, - handlerVersionName, + handlerVersionName }; if (this.stopAt) { this.stopService(blockNumber); diff --git a/server/src/port/adapter/service/blockchain/eos/watcher/types/types.d.ts b/server/src/port/adapter/service/blockchain/eos/watcher/types/types.d.ts index 2552892..1614ff7 100644 --- a/server/src/port/adapter/service/blockchain/eos/watcher/types/types.d.ts +++ b/server/src/port/adapter/service/blockchain/eos/watcher/types/types.d.ts @@ -1,6 +1,4 @@ -export interface CreateEggState { - gene: string; - owner: string; +export interface State { trx_id: string; indexState: { blockNumber: number; @@ -9,65 +7,34 @@ export interface CreateEggState { handlerVersionName: string; }; } -export interface SendState { +export interface CreateEggState extends State { + gene: string; + owner: string; +} +export interface SendState extends State { from: string; to: string; gene: string; - trx_id: string; - indexState: { - blockNumber: number; - blockHash: string; - isReplay: boolean; - handlerVersionName: string; - }; } -export interface LevelUpState { +export interface LevelUpState extends State { owner: string; gene: string; level: number; - trx_id: string; - indexState: { - blockNumber: number; - blockHash: string; - isReplay: boolean; - handlerVersionName: string; - }; } -export interface AuctionState { +export interface AuctionState extends State { auctioneer: string; gene: string; minPrice: number; deadline: number; - trx_id: string; - indexState: { - blockNumber: number; - blockHash: string; - isReplay: boolean; - handlerVersionName: string; - }; } -export interface BidState { +export interface BidState extends State { bidder: string; gene: string; bid: number; - trx_id: string; - indexState: { - blockNumber: number; - blockHash: string; - isReplay: boolean; - handlerVersionName: string; - }; } -export interface ClaimState { +export interface ClaimState extends State { requester: string; gene: string; - trx_id: string; - indexState: { - blockNumber: number; - blockHash: string; - isReplay: boolean; - handlerVersionName: string; - }; } export interface NodeosActionReaderOptions extends ActionReaderOptions { nodeosEndpoint?: string; diff --git a/server/src/port/adapter/service/blockchain/eos/watcher/watcher.spec.ts b/server/src/port/adapter/service/blockchain/eos/watcher/watcher.spec.ts new file mode 100644 index 0000000..71c5cd6 --- /dev/null +++ b/server/src/port/adapter/service/blockchain/eos/watcher/watcher.spec.ts @@ -0,0 +1,30 @@ +import { Test, TestingModule } from "@nestjs/testing"; +import { ConfigModule } from "nestjs-config"; +import * as path from "path"; +import { WatcherService } from "./watcher"; + +describe("WatcherService", () => { + let service: WatcherService; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + imports: [ + ConfigModule.load(path.resolve(__dirname + '/test', 'config', '**/watch.{ts,js}')), + ], + providers: [WatcherService] + }).compile(); + + service = module.get(WatcherService); + service.watch(); + }); + + describe("dependency resolve", () => { + it("should be defined", async () => { + expect(service).toBeDefined(); + }); + }); + + // describe("#watch()", () => { + // expect(service.watch()).toBe(true); + // }); +});