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

Basic redemptions monitoring #678

Merged
merged 4 commits into from
Aug 3, 2023
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
2 changes: 2 additions & 0 deletions monitoring/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ The behavior can be configured using the following env variables:

|*LARGE_DEPOSIT_THRESHOLD_SAT* |Satoshi threshold used to determine which deposits are large. Default: _1000000000_ |No

|*LARGE_REDEMPTION_THRESHOLD_SAT* |Satoshi threshold used to determine which redemptions are large. Default: _1000000000_ |No

|*DATA_DIR_PATH* |Directory used to persist processing data. Default: _./data_ |No

|*SENTRY_DSN* |DSN of the Sentry receiver. If not set, events are not dispatched to Sentry |No
Expand Down
16 changes: 16 additions & 0 deletions monitoring/docs/monitoring-and-telemetry.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,13 @@ are propagated to the Sentry hub that decides about next steps. Specific system
events produced by the monitoring component are:

* deposit revealed,
* redemption requested,
* wallet registered,
* DKG result submitted,
* DKG result approved,
* DKG result challenged,
* large deposit revealed,
* large redemption requested,
* optimistic minting canceled,
* optimistic minting requested too early,
* optimistic minting requested for undetermined Bitcoin transaction,
Expand All @@ -131,6 +133,12 @@ An *informational system event* indicating that a new deposit was revealed to
the on-chain Bridge contract. This event is directly sent to Discord as a
notification that does not require any action.

==== Redemption requested

An *informational system event* indicating that a new redemption was requested
from the on-chain Bridge contract. This event is directly sent to Discord as a
notification that does not require any action.

==== Wallet registered

An *informational system event* indicating that a new wallet was registered
Expand Down Expand Up @@ -164,6 +172,14 @@ on-chain Bridge contract. This event is sent to Sentry hub and should get
team’s attention. The default action is making sure that the deposit is
handled correctly by the system.

==== Large redemption requested

A *warning system event* indicating that a large redemption was requested from
the on-chain Bridge contract. This event is sent to Sentry hub and should get
team’s attention. The default action is making sure that the redemption is
not a result of a malicious action, and if not, that the redemption is
handled correctly by the system.

==== Optimistic minting cancelled

A *warning system event* indicating that an optimistic minting request was
Expand Down
23 changes: 23 additions & 0 deletions monitoring/src/block-explorer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { context, Environment } from "./context"

import type { BitcoinTransactionHash, Hex } from "@keep-network/tbtc-v2.ts"

const ethTxUrlPrefixMapping = {
[Environment.Mainnet]: "https://etherscan.io/tx",
[Environment.Testnet]: "https://goerli.etherscan.io/tx",
}

export function createEthTxUrl(txHash: Hex) {
return `${
ethTxUrlPrefixMapping[context.environment]
}/${txHash.toPrefixedString()}`
}

const btcTxUrlPrefixMapping = {
[Environment.Mainnet]: "https://mempool.space/tx",
[Environment.Testnet]: "https://mempool.space/testnet/tx",
}

export function createBtcTxUrl(txHash: BitcoinTransactionHash) {
return `${btcTxUrlPrefixMapping[context.environment]}/${txHash.toString()}`
}
2 changes: 2 additions & 0 deletions monitoring/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const {
ETHEREUM_URL,
ELECTRUM_URL,
LARGE_DEPOSIT_THRESHOLD_SAT,
LARGE_REDEMPTION_THRESHOLD_SAT,
DATA_DIR_PATH,
SENTRY_DSN,
DISCORD_WEBHOOK_URL,
Expand Down Expand Up @@ -52,6 +53,7 @@ export const context = {
ethereumUrl: resolveEthereumUrl(),
electrumUrl: resolveElectrumUrl(),
largeDepositThresholdSat: LARGE_DEPOSIT_THRESHOLD_SAT ?? 1000000000, // 10 BTC by default
largeRedemptionThresholdSat: LARGE_REDEMPTION_THRESHOLD_SAT ?? 1000000000, // 10 BTC by default
dataDirPath: DATA_DIR_PATH ?? "./data",
sentryDsn: SENTRY_DSN,
discordWebhookUrl: DISCORD_WEBHOOK_URL,
Expand Down
35 changes: 7 additions & 28 deletions monitoring/src/deposit-monitor.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,21 @@
import { BigNumber } from "ethers"

import { SystemEventType } from "./system-event"
import { context, Environment } from "./context"
import { context } from "./context"
import { createBtcTxUrl, createEthTxUrl } from "./block-explorer"

import type { SystemEvent, Monitor as SystemEventMonitor } from "./system-event"
import type { DepositRevealedEvent as DepositRevealedChainEvent } from "@keep-network/tbtc-v2.ts/dist/src/deposit"
import type { Bridge } from "@keep-network/tbtc-v2.ts/dist/src/chain"

const satsToRoundedBTC = (sats: BigNumber): string =>
export const satsToRoundedBTC = (sats: BigNumber): string =>
(sats.div(BigNumber.from(1e6)).toNumber() / 100).toFixed(2)

const hashUrls = (chainEvent: DepositRevealedChainEvent) => {
let fundingHashUrlPrefix = ""
let revealHashUrlPrefix = ""
switch (context.environment) {
case Environment.Mainnet: {
fundingHashUrlPrefix = "https://mempool.space/tx/"
revealHashUrlPrefix = "https://etherscan.io/tx/"
break
}
case Environment.Testnet: {
fundingHashUrlPrefix = "https://mempool.space/testnet/tx/"
revealHashUrlPrefix = "https://goerli.etherscan.io/tx/"
break
}
}

const fundingHash = chainEvent.fundingTxHash.toString()
const transactionHash = chainEvent.transactionHash.toPrefixedString()
return {
btcFundingTxHashURL: fundingHashUrlPrefix + fundingHash,
ethRevealTxHashURL: revealHashUrlPrefix + transactionHash,
}
}

const DepositRevealed = (
chainEvent: DepositRevealedChainEvent
): SystemEvent => {
const { btcFundingTxHashURL, ethRevealTxHashURL } = hashUrls(chainEvent)
const btcFundingTxHashURL = createBtcTxUrl(chainEvent.fundingTxHash)
const ethRevealTxHashURL = createEthTxUrl(chainEvent.transactionHash)

return {
title: "Deposit revealed",
Expand All @@ -57,7 +35,8 @@ const DepositRevealed = (
const LargeDepositRevealed = (
chainEvent: DepositRevealedChainEvent
): SystemEvent => {
const { btcFundingTxHashURL, ethRevealTxHashURL } = hashUrls(chainEvent)
const btcFundingTxHashURL = createBtcTxUrl(chainEvent.fundingTxHash)
const ethRevealTxHashURL = createEthTxUrl(chainEvent.transactionHash)

return {
title: "Large deposit revealed",
Expand Down
2 changes: 2 additions & 0 deletions monitoring/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { context } from "./context"
import { MintingMonitor } from "./minting-monitor"
import { WalletMonitor } from "./wallet-monitor"
import { SupplyMonitor } from "./supply-monitor"
import { RedemptionMonitor } from "./redemption-monitor"

import type { Client as BitcoinClient } from "@keep-network/tbtc-v2.ts/dist/src/bitcoin"
import type {
Expand All @@ -27,6 +28,7 @@ const monitors: SystemEventMonitor[] = [
new MintingMonitor(contracts.bridge, contracts.tbtcVault, btcClient),
new SupplyMonitor(contracts.tbtcToken, new SupplyMonitorFilePersistence()),
new WalletMonitor(contracts.bridge),
new RedemptionMonitor(contracts.bridge),
]

const receivers: SystemEventReceiver[] = ((): SystemEventReceiver[] => {
Expand Down
88 changes: 88 additions & 0 deletions monitoring/src/redemption-monitor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { BigNumber } from "ethers"

import { context } from "./context"
import { SystemEventType } from "./system-event"
import { satsToRoundedBTC } from "./deposit-monitor"
import { createEthTxUrl } from "./block-explorer"

import type { RedemptionRequestedEvent as RedemptionRequestedChainEvent } from "@keep-network/tbtc-v2.ts/dist/src/redemption"
import type { Bridge } from "@keep-network/tbtc-v2.ts/dist/src/chain"
import type { Monitor as SystemEventMonitor, SystemEvent } from "./system-event"

const RedemptionRequested = (
chainEvent: RedemptionRequestedChainEvent
): SystemEvent => {
const ethRequestTxHashURL = createEthTxUrl(chainEvent.transactionHash)

return {
title: "Redemption requested",
type: SystemEventType.Informational,
data: {
walletPublicKeyHash: chainEvent.walletPublicKeyHash,
redeemerOutputScript: chainEvent.redeemerOutputScript,
requestedAmountBTC: satsToRoundedBTC(chainEvent.requestedAmount),
ethRequestTxHash: chainEvent.transactionHash.toPrefixedString(),
ethRequestTxHashURL,
},
block: chainEvent.blockNumber,
}
}

const LargeRedemptionRequested = (
chainEvent: RedemptionRequestedChainEvent
): SystemEvent => {
const ethRequestTxHashURL = createEthTxUrl(chainEvent.transactionHash)

return {
title: "Large redemption requested",
type: SystemEventType.Warning,
data: {
walletPublicKeyHash: chainEvent.walletPublicKeyHash,
redeemerOutputScript: chainEvent.redeemerOutputScript,
requestedAmountBTC: satsToRoundedBTC(chainEvent.requestedAmount),
ethRequestTxHash: chainEvent.transactionHash.toPrefixedString(),
ethRequestTxHashURL,
},
block: chainEvent.blockNumber,
}
}

export class RedemptionMonitor implements SystemEventMonitor {
private bridge: Bridge

constructor(bridge: Bridge) {
this.bridge = bridge
}

async check(fromBlock: number, toBlock: number): Promise<SystemEvent[]> {
// eslint-disable-next-line no-console
console.log("running redemption monitor check")

const chainEvents = await this.bridge.getRedemptionRequestedEvents({
fromBlock,
toBlock,
})

const systemEvents: SystemEvent[] = []

// eslint-disable-next-line no-plusplus
for (let i = 0; i < chainEvents.length; i++) {
const chainEvent = chainEvents[i]

systemEvents.push(RedemptionRequested(chainEvent))

if (
chainEvent.requestedAmount.gt(
BigNumber.from(context.largeRedemptionThresholdSat)
)
) {
systemEvents.push(LargeRedemptionRequested(chainEvent))
}
}

// eslint-disable-next-line no-console
console.log("completed redemption monitor check")

return systemEvents
}
}
Loading