Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add status #3

Merged
merged 4 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading