Skip to content

Commit

Permalink
Merge pull request #3 from planetarium/feature/addStatus
Browse files Browse the repository at this point in the history
add status
  • Loading branch information
carolk-dev authored Dec 17, 2024
2 parents b9fe206 + 664b12a commit 14e0091
Show file tree
Hide file tree
Showing 11 changed files with 269 additions and 17 deletions.
7 changes: 5 additions & 2 deletions bridge/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,node
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,node

Expand Down Expand Up @@ -141,4 +140,8 @@ temp/

.env

# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,node
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,node

# macOS system files
.DS_Store
**/.DS_Store
24 changes: 12 additions & 12 deletions bridge/bridge.jest.config.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
module.exports = {
name: "bridge",
displayName: "bridge",
name: "bridge",
displayName: "bridge",

// NOTE: if you don't set this correctly then when you reference
// it later in a path string you'll get a confusing error message.
// It says something like' Module <rootDir>/config/polyfills.js in
// the setupFiles option was not found.'
rootDir: "./../",
testPathIgnorePatterns: ["/node_modules/", "/dist/", "/test/aws-dependent"],
testMatch: ["**/*/*.spec.{ts,tsx}"],
coverageDirectory: "coverage",
coverageReporters: ["lcov"],
// NOTE: if you don't set this correctly then when you reference
// it later in a path string you'll get a confusing error message.
// It says something like' Module <rootDir>/config/polyfills.js in
// the setupFiles option was not found.'
rootDir: "./../",
testPathIgnorePatterns: ["/node_modules/", "/dist/", "/test/aws-dependent"],
testMatch: ["**/*/*.spec.{ts,tsx}"],
coverageDirectory: "./bridge/coverage",
coverageReporters: ["lcov"],

// etc...
// etc...
};
11 changes: 11 additions & 0 deletions bridge/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { SpreadsheetClient } from "./spreadsheet-client";
import { google } from "googleapis";
import { MultiPlanetary } from "./multi-planetary";
import { bscBridgeContractAbi } from "./bsc-bridge-contract-abi";
import { PendingTransactionHandler } from "./pending-transactions";

consoleStamp(console);

Expand Down Expand Up @@ -336,6 +337,16 @@ process.on("uncaughtException", console.error);
};
const multiPlanetary = new MultiPlanetary(planetIds, planetVaultAddress);

const pendingTransactionRetryHandler = new PendingTransactionHandler(
exchangeHistoryStore,
ncgKmsTransfer,
multiPlanetary,
slackMessageSender
);

// 서버 시작 시 pending 트랜잭션 slack 메시지 전송
await pendingTransactionRetryHandler.messagePendingTransactions();

const ethereumBurnEventObserver = new BscBurnEventObserver(
ncgKmsTransfer,
slackMessageSender,
Expand Down
9 changes: 9 additions & 0 deletions bridge/src/interfaces/exchange-history-store.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import { TransactionStatus } from "../types/transaction-status";

export interface ExchangeHistory {
network: string;
tx_id: string;
sender: string;
recipient: string;
timestamp: string;
amount: number;
status: TransactionStatus;
}

export interface IExchangeHistoryStore {
put(history: ExchangeHistory): Promise<void>;
exist(tx_id: string): Promise<boolean>;
updateStatus(
tx_id: string,
status: TransactionStatus.COMPLETED | TransactionStatus.FAILED
): Promise<void>;

transferredAmountInLast24Hours(
network: string,
sender: string
): Promise<number>;

getPendingTransactions(): Promise<ExchangeHistory[]>;
}
69 changes: 69 additions & 0 deletions bridge/src/messages/pending-transaction-message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { ChatPostMessageArguments } from "@slack/web-api";
import { Message } from ".";
import { ForceOmit } from "../types/force-omit";
import { ExchangeHistory } from "../interfaces/exchange-history-store";
import { combineUrl } from "./utils";
import { MultiPlanetary } from "../multi-planetary";

export class PendingTransactionMessage implements Message {
private readonly _bscScanUrl: string;
private readonly _multiPlanetary: MultiPlanetary;

constructor(
private readonly transactions: ExchangeHistory[],
private readonly multiPlanetary: MultiPlanetary,
bscScanUrl: string = process.env.BSCSCAN_URL || "https://bscscan.com"
) {
this._bscScanUrl = bscScanUrl;
this._multiPlanetary = multiPlanetary;
}

render(): ForceOmit<Partial<ChatPostMessageArguments>, "channel"> {
if (this.transactions) {
console.log("Pending Transactions : ", this.transactions);
return {
text: `${this.transactions.length} Pending Transactions Found`,
attachments: this.transactions.map((tx) => ({
author_name: "[BSC] wNCG → NCG pending event",
color: "#ff0033",
fields: [
{
title: "BSC transaction",
value: combineUrl(this._bscScanUrl, `/tx/${tx.tx_id}`),
},
{
title: "Planet Name",
value: this._multiPlanetary.getRequestPlanetName(tx.recipient),
},
{
title: "Sender(BSC)",
value: tx.sender,
},
{
title: "Recipient(9c)",
value: tx.recipient,
},
{
title: "Amount",
value: tx.amount.toString(),
},
{
title: "Timestamp",
value: tx.timestamp,
},
],
})),
};
}

return {
text: "No pending transactions",
attachments: [
{
author_name: "BSC Bridge Restarted",
color: "#ffcc00",
},
],
};
}
}
11 changes: 11 additions & 0 deletions bridge/src/observers/burn-event-observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { IExchangeHistoryStore } from "../interfaces/exchange-history-store";
import { UnwrappingRetryIgnoreEvent } from "../messages/unwrapping-retry-ignore-event";
import { SpreadsheetClient } from "../spreadsheet-client";
import { MultiPlanetary } from "../multi-planetary";
import { TransactionStatus } from "../types/transaction-status";

export class BscBurnEventObserver
implements
Expand Down Expand Up @@ -138,6 +139,7 @@ export class BscBurnEventObserver
recipient: user9cAddress,
timestamp: new Date().toISOString(),
amount: parseFloat(amountString),
status: TransactionStatus.PENDING,
});

try {
Expand Down Expand Up @@ -191,6 +193,10 @@ export class BscBurnEventObserver
requestPlanetName
)
);
await this._exchangeHistoryStore.updateStatus(
transactionHash,
TransactionStatus.COMPLETED
);
await this._opensearchClient.to_opensearch("info", {
content: "wNCG -> NCG request success",
libplanetTxId: nineChroniclesTxId,
Expand Down Expand Up @@ -226,6 +232,11 @@ export class BscBurnEventObserver
)
);

await this._exchangeHistoryStore.updateStatus(
transactionHash,
TransactionStatus.FAILED
);

await this._spreadsheetClient.to_spreadsheet_burn({
slackMessageId: `${slackMsgRes?.channel}/p${slackMsgRes?.ts?.replace(
".",
Expand Down
33 changes: 33 additions & 0 deletions bridge/src/pending-transactions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { IExchangeHistoryStore } from "./interfaces/exchange-history-store";
import { INCGTransfer } from "./interfaces/ncg-transfer";
import { MultiPlanetary } from "./multi-planetary";
import { ISlackMessageSender } from "./interfaces/slack-message-sender";
import { PendingTransactionMessage } from "./messages/pending-transaction-message";
import { TransactionStatus } from "./types/transaction-status";

export class PendingTransactionHandler {
constructor(
private readonly _exchangeHistoryStore: IExchangeHistoryStore,
private readonly _ncgTransfer: INCGTransfer,
private readonly _multiPlanetary: MultiPlanetary,
private readonly _slackMessageSender: ISlackMessageSender
) {}

async messagePendingTransactions(): Promise<void> {
const pendingTransactions =
await this._exchangeHistoryStore.getPendingTransactions();

if (pendingTransactions.length > 0) {
await this._slackMessageSender.sendMessage(
new PendingTransactionMessage(pendingTransactions, this._multiPlanetary)
);

for (const tx of pendingTransactions) {
await this._exchangeHistoryStore.updateStatus(
tx.tx_id,
TransactionStatus.FAILED
);
}
}
}
}
59 changes: 56 additions & 3 deletions bridge/src/sqlite3-exchange-history-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
} from "./interfaces/exchange-history-store";
import { Database } from "sqlite3";
import { promisify } from "util";
import { TransactionStatus } from "./types/transaction-status";

export class Sqlite3ExchangeHistoryStore implements IExchangeHistoryStore {
private readonly _database: Database;
Expand All @@ -16,14 +17,15 @@ export class Sqlite3ExchangeHistoryStore implements IExchangeHistoryStore {
put(history: ExchangeHistory): Promise<void> {
this.checkClosed();

const { network, tx_id, sender, recipient, amount, timestamp } = history;
const { network, tx_id, sender, recipient, amount, timestamp, status } =
history;

const run: (sql: string, params: any[]) => Promise<void> = promisify(
this._database.run.bind(this._database)
);
return run(
"INSERT INTO exchange_histories(network, tx_id, sender, recipient, amount, timestamp) VALUES (?, ?, ?, ?, ?, ?)",
[network, tx_id, sender, recipient, amount, timestamp]
"INSERT INTO exchange_histories(network, tx_id, sender, recipient, amount, timestamp, status) VALUES (?, ?, ?, ?, ?, ?, ?)",
[network, tx_id, sender, recipient, amount, timestamp, status]
);
}

Expand Down Expand Up @@ -65,6 +67,7 @@ export class Sqlite3ExchangeHistoryStore implements IExchangeHistoryStore {
static async open(path: string): Promise<Sqlite3ExchangeHistoryStore> {
const database = new Database(path);
await this.initialize(database);
await this.ensureStatusColumn(database);
return new Sqlite3ExchangeHistoryStore(database);
}

Expand All @@ -76,6 +79,7 @@ export class Sqlite3ExchangeHistoryStore implements IExchangeHistoryStore {
recipient TEXT NOT NULL,
amount TEXT NOT NULL,
timestamp DATETIME NOT NULL,
status TEXT NOT NULL DEFAULT '${TransactionStatus.PENDING}',
PRIMARY KEY(network, tx_id)
);
CREATE INDEX IF NOT EXISTS exchange_history_idx ON exchange_histories(sender);`;
Expand All @@ -89,7 +93,29 @@ export class Sqlite3ExchangeHistoryStore implements IExchangeHistoryStore {
});
});
}
/** 운영테이블에 status 컬럼 추가후 삭제될 코드 START */
private static async ensureStatusColumn(database: Database): Promise<void> {
interface ColumnInfo {
name: string;
}

const columns = (await promisify(database.all.bind(database))(
"PRAGMA table_info(exchange_histories)"
)) as ColumnInfo[];

const hasStatusColumn = columns.some((col) => col.name === "status");

if (!hasStatusColumn) {
await promisify(database.run.bind(database))(
`ALTER TABLE exchange_histories ADD COLUMN status TEXT DEFAULT '${TransactionStatus.PENDING}'`
);

await promisify(database.run.bind(database))(
`UPDATE exchange_histories SET status = '${TransactionStatus.COMPLETED}' WHERE status IS NULL`
);
}
}
/** 운영테이블에 status 컬럼 추가후 삭제될 코드 END */
close(): void {
this.checkClosed();

Expand All @@ -102,4 +128,31 @@ export class Sqlite3ExchangeHistoryStore implements IExchangeHistoryStore {
throw new Error("This internal SQLite3 database is already closed.");
}
}

async updateStatus(
tx_id: string,
status: TransactionStatus.COMPLETED | TransactionStatus.FAILED
): Promise<void> {
this.checkClosed();

const run: (sql: string, params: any[]) => Promise<void> = promisify(
this._database.run.bind(this._database)
);
return run("UPDATE exchange_histories SET status = ? WHERE tx_id = ?", [
status,
tx_id,
]);
}

async getPendingTransactions(): Promise<ExchangeHistory[]> {
this.checkClosed();

const all: (sql: string, params: any[]) => Promise<ExchangeHistory[]> =
promisify(this._database.all.bind(this._database));

return await all(
`SELECT * FROM exchange_histories WHERE status = '${TransactionStatus.PENDING}'`,
[]
);
}
}
5 changes: 5 additions & 0 deletions bridge/src/types/transaction-status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum TransactionStatus {
PENDING = "pending",
COMPLETED = "completed",
FAILED = "failed",
}
Loading

0 comments on commit 14e0091

Please sign in to comment.