Skip to content

Commit

Permalink
Basic redemptions monitoring (#678)
Browse files Browse the repository at this point in the history
Refs: keep-network/keep-core#3664
Depends on: #677

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
pdyraga committed Aug 3, 2023
2 parents 1ed9224 + 371db78 commit 5db4161
Show file tree
Hide file tree
Showing 8 changed files with 384 additions and 163 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
}
}
Loading

0 comments on commit 5db4161

Please sign in to comment.