Skip to content

Commit

Permalink
Merge pull request #147 from sotatek-dev/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
Sotatek-TanHoang authored Oct 4, 2024
2 parents d773202 + 3167b5d commit 37c04ad
Show file tree
Hide file tree
Showing 19 changed files with 236 additions and 222 deletions.
4 changes: 2 additions & 2 deletions DockerfileUAT
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
FROM node:18-alpine As build
FROM public.ecr.aws/docker/library/node:18-alpine As build
WORKDIR /app

COPY package*.json ./
RUN npm i
COPY . .
RUN npm run build

FROM node:18-alpine
FROM public.ecr.aws/docker/library/node:18-alpine
WORKDIR /app
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
Expand Down
42 changes: 42 additions & 0 deletions design/admin-update-common-config.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
@startuml 1
title Lock From Eth to Mina
actor User
boundary fe as "Frontend"
control be as "Backend"
participant Ethereum
control evm_crawler as "EVM crawler"
control job_provider as "Job provider"
database db as "Database"
queue queue1 as "Queue"
control mina_validator as "Mina signature validators"
control mina_sender as "Mina tx sender"
control mina_crawler as "Mina crawler"
participant Mina
autonumber

group#LightGreen (1) User lock token from Evm

User -> fe : select network to bridge

activate fe
fe -> be : get list of token pairs
activate be
be --> fe : list of token pairs
deactivate be

User -> fe: select destination wallet, amount
fe --> User: display amount, tip, fee
User -> fe: perform bridge action
fe -> Ethereum: call lock tx using user's wallet
activate Ethereum
Ethereum --> fe: tx status success
deactivate Ethereum

fe --> User: show popup success
deactivate fe


end

@enduml

Empty file.
2 changes: 1 addition & 1 deletion docker-compose.dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ services:
REDIS_URI: redis://redis:${REDIS_PORT}
USERNAME: admin
PASSWORD: minabridge
QUEUES: EVM_SENDER_QUEUE,MINA_SENDER_QUEUE,EVM_VALIDATOR_1,EVM_VALIDATOR_2,EVM_VALIDATOR_2,MINA_VALIDATOR_1,MINA_VALIDATOR_2,MINA_VALIDATOR_3
QUEUES: EVM_SENDER_QUEUE,MINA_SENDER_QUEUE,EVM_VALIDATOR_1,EVM_VALIDATOR_2,EVM_VALIDATOR_3,MINA_VALIDATOR_1,MINA_VALIDATOR_2,MINA_VALIDATOR_3
ports:
- '${BULL_MONITOR_PORT}:3011'
depends_on:
Expand Down
1 change: 1 addition & 0 deletions src/config/config.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import redisConfig from './redis.config.js';
[EEnvKey.ETH_BRIDGE_DOMAIN_NAME]: Joi.string().required(),
[EEnvKey.ETH_BRIDGE_DOMAIN_VERSION]: Joi.string().required(),
[EEnvKey.MINA_CRAWL_SAFE_BLOCK]: Joi.number().default(MINA_CRAWL_SAFE_BLOCK),
[EEnvKey.EVM_SAFE_BLOCK]: Joi.number().default(10),
// mina validator
[EEnvKey.MINA_VALIDATOR_THRESHHOLD]: Joi.number().required(),
// fee
Expand Down
2 changes: 0 additions & 2 deletions src/constants/blockchain.constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ export enum EEventStatus {
PROCESSING = 'processing',
COMPLETED = 'completed',
FAILED = 'failed',
NOTOKENPAIR = 'noTokenPair',
CANNOT_PROCESS = 'cannotProcess',
}

export enum ETokenPairStatus {
Expand Down
1 change: 1 addition & 0 deletions src/constants/env.constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export enum EEnvKey {
EVM_VALIDATOR_PRIVATE_KEY = 'EVM_VALIDATOR_PRIVATE_KEY',
MINA_CRAWL_SAFE_BLOCK = 'MINA_CRAWL_SAFE_BLOCK',
THIS_VALIDATOR_INDEX = 'THIS_VALIDATOR_INDEX',
EVM_SAFE_BLOCK = 'SAFE_BLOCK',
}

export enum EEnvironments {
Expand Down
14 changes: 10 additions & 4 deletions src/database/repositories/event-log.repository.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { IJobUnlockPayload } from 'modules/crawler/interfaces/job.interface.js';
import { EntityRepository } from 'nestjs-typeorm-custom-repository';
import { Brackets } from 'typeorm';

Expand Down Expand Up @@ -43,21 +44,26 @@ export class EventLogRepository extends BaseRepository<EventLog> {
network: ENetworkName,
isSignatureFullFilled: boolean,
numOfSignaturesNeeded: number,
): Promise<Array<{ id: number; networkReceived: ENetworkName }>> {
): Promise<Array<IJobUnlockPayload>> {
const currentUnixTimestamp = nowUnix();
const qb = this.createQueryBuilder(`${this.alias}`);
qb.select([`${this.alias}.id as "id"`, `${this.alias}.network_received as "networkReceived"`]);
qb.select([
`${this.alias}.id as "eventLogId"`,
`${this.alias}.network_received as "network"`,
`${this.alias}.sender_address as "senderAddress"`,
]);
qb.leftJoin(`${this.alias}.validator`, 'signature');

qb.where(`${this.alias}.network_received = :network`, { network });

qb.andWhere(`${this.alias}.status IN (:...status)`, {
status: [EEventStatus.WAITING], // EEventStatus.PROCESSING add in future
status: [EEventStatus.WAITING, EEventStatus.FAILED], // EEventStatus.PROCESSING add in future
})
.andWhere(`${this.alias}.retry < :retryNumber`, { retryNumber: MAX_RETRIES })
.orderBy(`${this.alias}.id`, EDirection.DESC)
.groupBy(`${this.alias}.id`)
.addGroupBy(`${this.alias}.network_received`);
.addGroupBy(`${this.alias}.network_received`)
.addGroupBy(`${this.alias}.sender_address`);
if (isSignatureFullFilled) {
qb.andWhere(`${this.alias}.next_send_tx_job_time < :currentUnixTimestamp`, { currentUnixTimestamp });
qb.having(`COUNT(signature.id) = :numOfSignaturesNeeded`, { numOfSignaturesNeeded });
Expand Down
3 changes: 2 additions & 1 deletion src/modules/crawler/crawler.console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ export class CrawlerConsole {
description: 'Crawl ETH Bridge contract',
})
async handleCrawlETHBridge() {
const safeBlock = +this.configService.get(EEnvKey.ETH_TOKEN_BRIDGE_ADDRESS);
try {
while (true) {
await this.blockchainEVMCrawler.handleEventCrawlBlock();
await this.blockchainEVMCrawler.handleEventCrawlBlock(safeBlock);
await sleep(15);
}
} catch (error) {
Expand Down
97 changes: 47 additions & 50 deletions src/modules/crawler/crawler.evmbridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@ import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import assert from 'assert';
import { Logger } from 'log4js';
import { DataSource, QueryRunner } from 'typeorm';
import { DataSource, EntityManager } from 'typeorm';
import { EventData } from 'web3-eth-contract';

import { EAsset } from '../../constants/api.constant.js';
import { EEventName, EEventStatus, ENetworkName } from '../../constants/blockchain.constant.js';
import { EEnvKey } from '../../constants/env.constant.js';
import { CommonConfigRepository } from '../../database/repositories/common-configuration.repository.js';
import { CrawlContractRepository } from '../../database/repositories/crawl-contract.repository.js';
import { CrawlContract, EventLog } from '../../modules/crawler/entities/index.js';
import { LoggerService } from '../../shared/modules/logger/logger.service.js';
import { ETHBridgeContract } from '../../shared/modules/web3/web3.service.js';
import { calculateUnlockFee } from '../../shared/utils/bignumber.js';
import { CommonConfig } from './entities/common-config.entity.js';

@Injectable()
export class BlockchainEVMCrawler {
Expand All @@ -25,59 +25,58 @@ export class BlockchainEVMCrawler {
private readonly crawlContractRepository: CrawlContractRepository,
private readonly loggerService: LoggerService,
private readonly ethBridgeContract: ETHBridgeContract,
private readonly commonConfigRepository: CommonConfigRepository,
) {
this.numberOfBlockPerJob = +this.configService.get<number>(EEnvKey.NUMBER_OF_BLOCK_PER_JOB)!;
this.logger = loggerService.getLogger('BLOCKCHAIN_EVM_CRAWLER');
}

public async handleEventCrawlBlock() {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
public async handleEventCrawlBlock(safeBlock: number) {
const { startBlockNumber, toBlock } = await this.getFromToBlock(safeBlock);
if (startBlockNumber > toBlock) {
this.logger.info(
`Block <${startBlockNumber}> is the newest block can be processed (on network: ${toBlock}). Wait for the next tick...`,
);
return;
}
const events = await this.ethBridgeContract.getEvent(startBlockNumber, toBlock);
try {
const { startBlockNumber, toBlock } = await this.getFromToBlock();
if (startBlockNumber > toBlock) {
this.logger.info(
`Block <${startBlockNumber}> is the newest block can be processed (on network: ${toBlock}). Wait for the next tick...`,
);
return;
}
const events = await this.ethBridgeContract.getEvent(startBlockNumber, toBlock);

for (const event of events) {
switch (event.event) {
case 'Lock':
await this.handlerLockEvent(event, queryRunner);
break;
case 'Unlock':
await this.handlerUnLockEvent(event, queryRunner);
break;
default:
continue;
await this.dataSource.transaction(async (entityManager: EntityManager) => {
for (const event of events) {
switch (event.event) {
case 'Lock':
await this.handlerLockEvent(event, entityManager);
break;
case 'Unlock':
await this.handlerUnLockEvent(event, entityManager);
break;
default:
continue;
}
}
}
this.logger.info(`[handleCrawlETHBridge] Crawled from ${startBlockNumber} to ${toBlock}`);
await this.updateLatestBlockCrawl(toBlock, queryRunner);
return await queryRunner.commitTransaction();
await this.updateLatestBlockCrawl(toBlock, entityManager);
});
} catch (error) {
await queryRunner.rollbackTransaction();
this.logger.error(error);
throw error;
} finally {
await queryRunner.release();
this.logger.info(`[handleCrawlETHBridge] Crawled from ${startBlockNumber} to ${toBlock}`);
}
}

public async handlerLockEvent(event: EventData, queryRunner: QueryRunner) {
const inputAmount = event.returnValues.amount;
const isExist = await queryRunner.manager.findOneBy(EventLog, { txHashLock: event.transactionHash });
public async handlerLockEvent(event: EventData, entityManager: EntityManager): Promise<void> {
const eventLogRepo = entityManager.getRepository(EventLog);
const configRepo = entityManager.getRepository(CommonConfig);

const isExist = await eventLogRepo.findOneBy({ txHashLock: event.transactionHash });
if (isExist) {
this.logger.warn('Duplicated event', event.transactionHash);
return;
}

const inputAmount = event.returnValues.amount;
const fromTokenDecimal = this.configService.get(EEnvKey.DECIMAL_TOKEN_EVM),
toTokenDecimal = this.configService.get(EEnvKey.DECIMAL_TOKEN_MINA);
const config = await this.commonConfigRepository.getCommonConfig();
const config = await configRepo.findOneBy({});
assert(!!config?.tip, 'tip config undefined');
const {
success,
Expand All @@ -97,7 +96,7 @@ export class BlockchainEVMCrawler {
this.logger.error('Calculate error', error);
}
const blockTimeOfBlockNumber = await this.ethBridgeContract.getBlockTimeByBlockNumber(event.blockNumber);
const eventUnlock: Partial<EventLog> = {
const eventUnlock = new EventLog({
senderAddress: event.returnValues.locker,
amountFrom: inputAmount,
tokenFromAddress: event.returnValues.token,
Expand All @@ -119,23 +118,21 @@ export class BlockchainEVMCrawler {
tip: tipWithDecimalPlaces,
amountReceived: amountReceiveNoDecimalPlace,
protocolFee: protocolFeeNoDecimalPlace,
};
});

await queryRunner.manager.save(EventLog, eventUnlock);
return {
success: true,
};
await eventLogRepo.save(eventUnlock);
}

public async handlerUnLockEvent(event: EventData, queryRunner: QueryRunner) {
const existLockTx = await queryRunner.manager.findOne(EventLog, {
public async handlerUnLockEvent(event: EventData, entityManager: EntityManager) {
const eventLogRepo = entityManager.getRepository(EventLog);
const existLockTx = await eventLogRepo.findOne({
where: { txHashLock: event.returnValues.hash },
});
if (!existLockTx) {
return;
}

await queryRunner.manager.update(EventLog, existLockTx.id, {
await eventLogRepo.update(existLockTx.id, {
status: EEventStatus.COMPLETED,
txHashUnlock: event.transactionHash,
amountReceived: event.returnValues.amount,
Expand All @@ -149,9 +146,9 @@ export class BlockchainEVMCrawler {
};
}

public async updateLatestBlockCrawl(blockNumber: number, queryRunner: QueryRunner) {
await queryRunner.manager.update(
CrawlContract,
public async updateLatestBlockCrawl(blockNumber: number, entityManager: EntityManager) {
const crawlContractRepo = entityManager.getRepository(CrawlContract);
await crawlContractRepo.update(
{
contractAddress: this.configService.get(EEnvKey.ETH_BRIDGE_CONTRACT_ADDRESS),
networkName: ENetworkName.ETH,
Expand All @@ -162,9 +159,9 @@ export class BlockchainEVMCrawler {
);
}

private async getFromToBlock(): Promise<{ startBlockNumber: number; toBlock: number }> {
private async getFromToBlock(safeBlock: number): Promise<{ startBlockNumber: number; toBlock: number }> {
let startBlockNumber = this.ethBridgeContract.getStartBlock();
let toBlock = await this.ethBridgeContract.getBlockNumber();
let toBlock = await this.ethBridgeContract.getBlockNumber(safeBlock);

const currentCrawledBlock = await this.crawlContractRepository.findOne({
where: { networkName: ENetworkName.ETH },
Expand Down
Loading

0 comments on commit 37c04ad

Please sign in to comment.