Skip to content

Commit

Permalink
Merge pull request #658 from BoltzExchange/ws-fallback
Browse files Browse the repository at this point in the history
WebSocket fallback endpoint
  • Loading branch information
michael1011 authored Aug 15, 2024
2 parents ab66019 + 8659a57 commit 6030b1e
Show file tree
Hide file tree
Showing 15 changed files with 134 additions and 192 deletions.
9 changes: 6 additions & 3 deletions src/components/CreateButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ export const CreateButton = () => {
}

if (msg === "invalid pair hash") {
setPairs(await getPairs(assetReceive()));
setPairs(await getPairs());
notify("error", t("feecheck"));
} else {
notify("error", msg);
Expand All @@ -269,8 +269,11 @@ export const CreateButton = () => {

const buttonClick = async () => {
setButtonDisable(true);
await create();
setButtonDisable(false);
try {
await create();
} finally {
setButtonDisable(false);
}
};

const getButtonLabel = (label: ButtonLabelParams) => {
Expand Down
3 changes: 0 additions & 3 deletions src/components/RefundButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ export const RefundEvm = ({
);
} else {
const { signature } = await getEipRefundSignature(
asset,
// The preimage hash can be used as an identifier
preimageHash,
// The endpoints for submarine and chain swap call the same endpoint
Expand Down Expand Up @@ -195,7 +194,6 @@ const RefundButton = ({
setRefundRunning(true);

const transactionToRefund = await getLockupTransaction(
swap().assetSend,
swap().id,
swap().type,
);
Expand Down Expand Up @@ -255,7 +253,6 @@ const RefundButton = ({
if (!swap()) return;

const transactionToRefund = await getLockupTransaction(
swap().assetSend,
swap().id,
swap().type,
);
Expand Down
145 changes: 67 additions & 78 deletions src/components/SwapChecker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { OutputType } from "boltz-core";
import log from "loglevel";
import { createEffect, onCleanup, onMount } from "solid-js";

import { BTC, LBTC, RBTC } from "../consts/Assets";
import { config } from "../config";
import { RBTC } from "../consts/Assets";
import { SwapType } from "../consts/Enums";
import {
swapStatusFinal,
Expand Down Expand Up @@ -41,45 +42,20 @@ class BoltzWebSocket {

constructor(
private readonly url: string,
private readonly wsFallback: string | undefined,
private readonly relevantIds: Set<string>,
private readonly prepareSwap: (id: string, status: any) => void,
private readonly claimSwap: (id: string, status: any) => Promise<void>,
) {}

public connect = () => {
this.isClosed = false;
clearTimeout(this.reconnectTimeout);
this.ws?.close();
this.ws = new WebSocket(
`${BoltzWebSocket.formatWsUrl(this.url)}/v2/ws`,
);

this.ws.onopen = () => {
this.subscribeUpdates(Array.from(this.relevantIds.values()));
};
this.ws.onclose = () => {
log.warn(`ws ${this.url} closed`);
this.handleClose();
};
this.ws.onmessage = async (msg) => {
const data = JSON.parse(msg.data);
if (data.event === "pong" || data.event === "ping") {
return;
}

log.debug(`ws ${this.url} message`, data);

if (data.event === "update" && data.channel === "swap.update") {
const swapUpdates = data.args as SwapStatus[];
for (const status of swapUpdates) {
this.relevantIds.add(status.id);
this.prepareSwap(status.id, status);
await this.swapClaimLock.acquire(() =>
this.claimSwap(status.id, status),
);
}
log.debug("Opening WebSocket");
this.openWebSocket(`${this.url}/v2/ws`).catch(() => {
if (this.wsFallback !== undefined) {
log.debug("Opening fallback WebSocket");
this.openWebSocket(this.wsFallback).then().catch();
}
};
});
};

public close = () => {
Expand All @@ -106,6 +82,49 @@ class BoltzWebSocket {
);
};

private openWebSocket = async (url: string) => {
this.isClosed = false;
clearTimeout(this.reconnectTimeout);
this.ws?.close();

return new Promise<void>((resolve, reject) => {
this.ws = new WebSocket(BoltzWebSocket.formatWsUrl(url));

this.ws.onopen = () => {
this.subscribeUpdates(Array.from(this.relevantIds.values()));
};
this.ws.onclose = (error) => {
log.warn("WebSocket closed", error);
this.handleClose();

if (error.wasClean) {
resolve();
} else {
reject(error);
}
};
this.ws.onmessage = async (msg) => {
const data = JSON.parse(msg.data);
if (data.event === "pong" || data.event === "ping") {
return;
}

log.debug("WebSocket message", data);

if (data.event === "update" && data.channel === "swap.update") {
const swapUpdates = data.args as SwapStatus[];
for (const status of swapUpdates) {
this.relevantIds.add(status.id);
this.prepareSwap(status.id, status);
await this.swapClaimLock.acquire(() =>
this.claimSwap(status.id, status),
);
}
}
};
});
};

private handleClose = () => {
// Don't reconnect when it has been closed manually
if (this.isClosed) {
Expand Down Expand Up @@ -133,7 +152,7 @@ export const SwapChecker = () => {
const { notify, updateSwapStatus, getSwap, getSwaps, setSwapStorage, t } =
useGlobalContext();

const assetWebsocket = new Map<string, BoltzWebSocket>();
let ws: BoltzWebSocket | undefined = undefined;

const prepareSwap = async (swapId: string, data: any) => {
const currentSwap = await getSwap(swapId);
Expand Down Expand Up @@ -170,19 +189,13 @@ export const SwapChecker = () => {
}

if (data.status === swapStatusSuccess.InvoiceSettled) {
data.transaction = await getReverseTransaction(
getRelevantAssetForSwap(currentSwap),
currentSwap.id,
);
data.transaction = await getReverseTransaction(currentSwap.id);
} else if (
currentSwap.type === SwapType.Chain &&
data.status === swapStatusSuccess.TransactionClaimed
) {
data.transaction = (
await getChainSwapTransactions(
getRelevantAssetForSwap(currentSwap),
currentSwap.id,
)
await getChainSwapTransactions(currentSwap.id)
).serverLock.transaction;
}

Expand Down Expand Up @@ -246,14 +259,6 @@ export const SwapChecker = () => {
};

onMount(async () => {
const urlsToAsset = new Map<string, string[]>();
for (const [asset, url] of [BTC, LBTC, RBTC].map((asset) => [
asset,
getApiUrl(asset),
])) {
urlsToAsset.set(url, (urlsToAsset.get(url) || []).concat(asset));
}

const swapsToCheck = (await getSwaps()).filter(
(s) =>
!swapStatusFinal.includes(s.status) ||
Expand All @@ -263,32 +268,18 @@ export const SwapChecker = () => {
s.claimTx === undefined),
);

for (const [url, assets] of urlsToAsset.entries()) {
log.debug(`opening ws for assets [${assets.join(", ")}]: ${url}`);
const ws = new BoltzWebSocket(
url,
new Set<string>(
swapsToCheck
.filter((s) =>
assets.includes(getRelevantAssetForSwap(s)),
)
.map((s) => s.id),
),
prepareSwap,
claimSwap,
);
ws.connect();
for (const asset of assets) {
assetWebsocket.set(asset, ws);
}
}
ws = new BoltzWebSocket(
getApiUrl(),
config.apiUrl.wsFallback,
new Set<string>(swapsToCheck.map((s) => s.id)),
prepareSwap,
claimSwap,
);
ws.connect();
});

onCleanup(() => {
const sockets = assetWebsocket.values();
assetWebsocket.clear();

for (const ws of sockets) {
if (ws !== undefined) {
ws.close();
}
});
Expand All @@ -298,12 +289,10 @@ export const SwapChecker = () => {
if (activeSwap === undefined || activeSwap === null) {
return;
}
// on page reload assetWebsocket is not yet initialized
const ws = assetWebsocket.get(getRelevantAssetForSwap(activeSwap));
if (ws === undefined) {
return;
// on page reload assetWebsocket might not be initialized yet
if (ws !== undefined) {
ws.subscribeUpdates([activeSwap.id]);
}
ws.subscribeUpdates([activeSwap.id]);
});

return "";
Expand Down
5 changes: 3 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ const defaults = {
};

type Asset = {
apiUrl?: Url;
network?: any;
blockExplorerUrl?: Url;

Expand All @@ -45,7 +44,9 @@ type Url = {
};

export type Config = {
apiUrl?: Url;
// The wsFallback is used on regtest when the backend is being run without
// nginx and the WebSocket is on a different port than the rest of the API
apiUrl?: Url & { wsFallback?: string };
network?: "mainnet" | "testnet" | "regtest";
isBoltzClient?: boolean;
boltzClientApiUrl?: string;
Expand Down
3 changes: 2 additions & 1 deletion src/configs/regtest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"network": "regtest",
"loglevel": "debug",
"apiUrl": {
"normal": "http://localhost:9001"
"normal": "http://localhost:9001",
"wsFallback": "http://localhost:9004"
},
"assets": {
"BTC": {
Expand Down
7 changes: 3 additions & 4 deletions src/context/Global.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
} from "solid-js";

import { config } from "../config";
import { BTC } from "../consts/Assets";
import { Denomination } from "../consts/Enums";
import { swapStatusFinal } from "../consts/SwapStatus";
import { detectLanguage } from "../i18n/detect";
Expand Down Expand Up @@ -76,7 +75,7 @@ export type GlobalContextType = {
audio?: boolean,
) => void;
playNotificationSound: () => void;
fetchPairs: (asset?: string) => void;
fetchPairs: () => void;

getLogs: () => Promise<Record<string, string[]>>;
clearLogs: () => Promise<void>;
Expand Down Expand Up @@ -192,8 +191,8 @@ const GlobalProvider = (props: { children: any }) => {
audio.play();
};

const fetchPairs = (asset: string = BTC) => {
getPairs(asset)
const fetchPairs = () => {
getPairs()
.then((data) => {
log.debug("getpairs", data);
setOnline(true);
Expand Down
2 changes: 1 addition & 1 deletion src/context/Web3.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ const Web3SignerProvider = (props: {
return undefined;
}

return (await getContracts(RBTC))["rsk"];
return (await getContracts())["rsk"];
});

const getEtherSwap = () => {
Expand Down
3 changes: 1 addition & 2 deletions src/pages/Hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import bitcoin from "../assets/bitcoin-icon.svg";
import lightning from "../assets/lightning-icon.svg";
import liquid from "../assets/liquid-icon.svg";
import rbtc from "../assets/rootstock-icon.svg";
import { BTC } from "../consts/Assets";
import { Denomination } from "../consts/Enums";
import { useGlobalContext } from "../context/Global";
import Create from "../pages/Create";
Expand All @@ -33,7 +32,7 @@ export const Hero = () => {

onMount(async () => {
try {
const statsRes = await getNodeStats(BTC);
const statsRes = await getNodeStats();

log.debug("node stats", statsRes);
const stats = statsRes.BTC.total;
Expand Down
5 changes: 2 additions & 3 deletions src/pages/Pay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import TransactionConfirmed from "../status/TransactionConfirmed";
import TransactionLockupFailed from "../status/TransactionLockupFailed";
import TransactionMempool from "../status/TransactionMempool";
import { getSwapStatus } from "../utils/boltzClient";
import { getRelevantAssetForSwap } from "../utils/swapCreator";

const Pay = () => {
const params = useParams();
Expand All @@ -48,8 +47,8 @@ const Pay = () => {
if (currentSwap) {
log.debug("selecting swap", currentSwap);
setSwap(currentSwap);
const asset = getRelevantAssetForSwap(currentSwap);
const res = await getSwapStatus(asset, currentSwap.id);

const res = await getSwapStatus(currentSwap.id);
setSwapStatus(res.status);
setSwapStatusTransaction(res.transaction);
setFailureReason(res.failureReason);
Expand Down
Loading

0 comments on commit 6030b1e

Please sign in to comment.