Skip to content

Commit

Permalink
Basic redemptions monitoring
Browse files Browse the repository at this point in the history
This changeset adds redemptions to the monitoring system. Specifically,
two new system events are supported now:

### 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.

### 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.
  • Loading branch information
lukasz-zimnoch committed Aug 1, 2023
1 parent 34a7b09 commit 8e1f06e
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 28 deletions.
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
}
}

0 comments on commit 8e1f06e

Please sign in to comment.