From 3bd7ade2989e99b10869a53bb66e3fa00ce0fe12 Mon Sep 17 00:00:00 2001 From: +shyfire131 <116033104+shyfire131@users.noreply.github.com> Date: Thu, 18 May 2023 05:14:11 -0600 Subject: [PATCH] Fix lnproxy support and add lnproxy relays workflow (#586) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use new lnproxy API - Use POST instead of GET, so create and send a body parameter - Path is /spec/ instead of /api/, and list of relays from lnproxy will contain /spec already, so path parameter for ApiClient.post() is an empty string * add lnproxy sync workflow * Use new lnproxy JSON structure * Remove virtualenv doing this so that the “scripts” subfolder in .github/workflows can be added * Move workflow script to subfolder * Add translation support Locale strings not added yet * Simplify coordinator updates, automatic migrations and collect statics (#583) * Delete commited proto files * Run sync workflow weekly instead of hourly * Tweak display name for relays * Update sync script to be append-only * Use new naming convention for relays * Fix bitcoinNetwork hydration * PR Feedback - Change hook deps from settings.network to settings - routing_msat param updates for lnproxy API * Actually set host in settings * Updated parsing of LnProxy response --------- Co-authored-by: +shyfire131 Co-authored-by: Reckless_Satoshi <90936742+Reckless-Satoshi@users.noreply.github.com> Co-authored-by: Reckless_Satoshi --- .github/workflows/lnproxy-sync.yml | 30 ++++++++ .github/workflows/scripts/lnproxy-sync.js | 46 +++++++++++++ .../TradeBox/Forms/LightningPayout.tsx | 69 ++++++++++--------- frontend/src/models/Settings.model.ts | 2 + frontend/static/lnproxies.json | 23 +++++-- 5 files changed, 130 insertions(+), 40 deletions(-) create mode 100644 .github/workflows/lnproxy-sync.yml create mode 100644 .github/workflows/scripts/lnproxy-sync.js diff --git a/.github/workflows/lnproxy-sync.yml b/.github/workflows/lnproxy-sync.yml new file mode 100644 index 00000000..f8f75bcc --- /dev/null +++ b/.github/workflows/lnproxy-sync.yml @@ -0,0 +1,30 @@ +name: Syncs relay list from lnproxy.org and stores in format used by RoboSats + +on: + schedule: + # * is a special character in YAML so you have to quote this string + - cron: '0 12 * * 0' # Run every Sunday at noon + +jobs: + sync: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Fetch and process JSON + run: | + curl https://github.com/shyfire131/lnproxy-webui2/blob/17d6131a72a92978e5c0dc57ab2b2fbe467c7722/assets/relays.json -o lnproxy_tmplist.json + node .github/workflows/scripts/lnproxy-sync.js + + - name: Commit and push if it's not up to date + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add ./frontend/static/lnproxies.json + git diff --quiet && git diff --staged --quiet || git commit -m "Updated LNProxy relay list" + git push + + - name: Remove tmp lnproxy json file + run: rm lnproxy_tmplist.json \ No newline at end of file diff --git a/.github/workflows/scripts/lnproxy-sync.js b/.github/workflows/scripts/lnproxy-sync.js new file mode 100644 index 00000000..eec2e81f --- /dev/null +++ b/.github/workflows/scripts/lnproxy-sync.js @@ -0,0 +1,46 @@ +const fs = require('fs'); + +let incomingRelays = JSON.parse(fs.readFileSync('./lnproxy_tmplist.json')); +let existingRelays = JSON.parse(fs.readFileSync('./frontend/static/lnproxies.json')) + +let newRelays = []; + +let torCount = 0; +let i2pCount = 0; +let clearnetCount = 0; + +//Merge relay lists. URL is the unique ID used to merge records and only inserts supported. No updates or deletes +let existingRelayURLs = existingRelays.map((relay) => relay.url); +let newIncomingRelays = incomingRelays.filter((relay)=> existingRelayURLs.indexOf(relay) === -1) + +for (let url of newIncomingRelays) { + let relayType; + const LNPROXY_API_PATH = '/spec' + const fqdn = url.replace(LNPROXY_API_PATH, ''); + if (fqdn.endsWith('.onion')) { + relayType = "TOR"; + torCount++; + } + else if (fqdn.endsWith('i2p')) { + relayType = "I2P"; + i2pCount++; + } + else { + relayType = "Clearnet"; + clearnetCount++; + } + + let relayName = `${relayType}${relayType === "TOR" ? torCount : ''}${relayType === "I2P" ? i2pCount : ''}${relayType === "Clearnet" ? clearnetCount : ''} ${url.split('/')[2].substring(0,6)}` + + newRelays.push({ + name: relayName, + url: url, + relayType: relayType, + network: "mainnet" //TODO: testnet + }); +} + +if (newRelays.length > 0) { + existingRelays.push(...newRelays); + fs.writeFileSync('./frontend/static/lnproxies.json', JSON.stringify(existingRelays, null, 2)); +} \ No newline at end of file diff --git a/frontend/src/components/TradeBox/Forms/LightningPayout.tsx b/frontend/src/components/TradeBox/Forms/LightningPayout.tsx index 83077047..b1e3d672 100644 --- a/frontend/src/components/TradeBox/Forms/LightningPayout.tsx +++ b/frontend/src/components/TradeBox/Forms/LightningPayout.tsx @@ -30,9 +30,10 @@ import { pn } from '../../../utils'; import { ContentCopy, Help, SelfImprovement } from '@mui/icons-material'; import { apiClient } from '../../../services/api'; -import lnproxies from '../../../../static/lnproxies.json'; import { systemClient } from '../../../services/System'; +import lnproxies from '../../../../static/lnproxies.json'; +let filteredProxies: { [key: string]: any }[] = []; export interface LightningForm { invoice: string; amount: number; @@ -92,7 +93,7 @@ export const LightningPayoutForm = ({ const theme = useTheme(); const [loadingLnproxy, setLoadingLnproxy] = useState(false); - const [badLnproxyServer, setBadLnproxyServer] = useState(''); + const [noMatchingLnProxies, setNoMatchingLnProxies] = useState(''); const computeInvoiceAmount = function () { const tradeAmount = order.trade_satoshis; @@ -145,49 +146,51 @@ export const LightningPayoutForm = ({ } }, [lightning.lnproxyInvoice, lightning.lnproxyAmount]); - const lnproxyUrl = function () { - const bitcoinNetwork = settings?.network ?? 'mainnet'; - let internetNetwork: 'Clearnet' | 'I2P' | 'TOR' = 'Clearnet'; + //filter lnproxies when the network settings are updated + let bitcoinNetwork: string = 'mainnet'; + let internetNetwork: 'Clearnet' | 'I2P' | 'TOR' = 'Clearnet'; + useEffect(() => { + bitcoinNetwork = settings?.network ?? 'mainnet'; if (settings.host?.includes('.i2p')) { internetNetwork = 'I2P'; } else if (settings.host?.includes('.onion') || window.NativeRobosats != undefined) { internetNetwork = 'TOR'; } - const url = lnproxies[lightning.lnproxyServer][`${bitcoinNetwork}${internetNetwork}`]; - if (url != 'undefined') { - return url; - } else { - setBadLnproxyServer( - t(`Server not available for {{bitcoinNetwork}} bitcoin over {{internetNetwork}}`, { - bitcoinNetwork, + filteredProxies = lnproxies + .filter((node) => node.relayType == internetNetwork) + .filter((node) => node.network == bitcoinNetwork); + }, [settings]); + + //if "use lnproxy" checkbox is enabled, but there are no matching proxies, enter error state + useEffect(() => { + setNoMatchingLnProxies(''); + if (filteredProxies.length === 0) { + setNoMatchingLnProxies( + t(`No proxies available for {{bitcoinNetwork}} bitcoin over {{internetNetwork}}`, { + bitcoinNetwork: settings?.network ?? 'mainnet', internetNetwork: t(internetNetwork), }), ); } - }; - - useEffect(() => { - setBadLnproxyServer(''); - lnproxyUrl(); - }, [lightning.lnproxyServer]); + }, [lightning.useLnproxy]); const fetchLnproxy = function () { setLoadingLnproxy(true); + let body: { invoice: string; description: string; routing_msat?: string } = { + invoice: lightning.lnproxyInvoice, + description: '', + }; + if (lightning.lnproxyBudgetSats > 0) { + body['routing_msat'] = String(lightning.lnproxyBudgetSats * 1000); + } apiClient - .get( - lnproxyUrl(), - `/api/${lightning.lnproxyInvoice}${ - lightning.lnproxyBudgetSats > 0 - ? `?routing_msat=${lightning.lnproxyBudgetSats * 1000}` - : '' - }&format=json`, - ) + .post(filteredProxies[lightning.lnproxyServer]['url'], '', body) .then((data) => { if (data.reason) { setLightning({ ...lightning, badLnproxy: data.reason }); - } else if (data.wpr) { - setLightning({ ...lightning, invoice: data.wpr, badLnproxy: '' }); + } else if (data.proxy_invoice) { + setLightning({ ...lightning, invoice: data.proxy_invoice, badLnproxy: '' }); } else { setLightning({ ...lightning, badLnproxy: 'Unknown lnproxy response' }); } @@ -416,7 +419,7 @@ export const LightningPayoutForm = ({ spacing={1} > - + {t('Server')} - {badLnproxyServer != '' ? ( - {t(badLnproxyServer)} + {noMatchingLnProxies != '' ? ( + {t(noMatchingLnProxies)} ) : ( <> )} @@ -563,7 +566,7 @@ export const LightningPayoutForm = ({ loading={loadingLnproxy} disabled={ lightning.lnproxyInvoice.length < 20 || - badLnproxyServer != '' || + noMatchingLnProxies != '' || lightning.badLnproxy != '' } onClick={fetchLnproxy} diff --git a/frontend/src/models/Settings.model.ts b/frontend/src/models/Settings.model.ts index a5273c4f..9d7940d9 100644 --- a/frontend/src/models/Settings.model.ts +++ b/frontend/src/models/Settings.model.ts @@ -1,5 +1,6 @@ import i18n from '../i18n/Web'; import { systemClient } from '../services/System'; +import { getHost } from '../utils'; import type Coordinator from './Coordinator.model'; export type Language = @@ -42,6 +43,7 @@ class BaseSettings { const networkCookie = systemClient.getItem('settings_network'); this.network = networkCookie !== '' ? networkCookie : 'mainnet'; + this.host = getHost(); } public frontend: 'basic' | 'pro' = 'basic'; diff --git a/frontend/static/lnproxies.json b/frontend/static/lnproxies.json index 57b90d5e..37ed8edd 100644 --- a/frontend/static/lnproxies.json +++ b/frontend/static/lnproxies.json @@ -1,11 +1,20 @@ [ { - "name": "↬ Lnproxy Dev", - "mainnetClearnet": "https://lnproxy.org", - "mainnetTOR": "http://rdq6tvulanl7aqtupmoboyk2z3suzkdwurejwyjyjf4itr3zhxrm2lad.onion", - "mainnetI2P": "undefined", - "testnetClearnet": "undefined", - "testnetTOR": "undefined", - "testnetI2P": "undefined" + "name": "TOR1 w3sqmn", + "url": "http://w3sqmns2ct7ai2wiwzq5uplp2pqglpm6qpeey4blvn6agj3jr5abthqd.onion/spec", + "relayType": "TOR", + "network": "mainnet" + }, + { + "name": "TOR2 rdq6tv", + "url": "http://rdq6tvulanl7aqtupmoboyk2z3suzkdwurejwyjyjf4itr3zhxrm2lad.onion/spec", + "relayType": "TOR", + "network": "mainnet" + }, + { + "name": "Clearnet1 lnprox", + "url": "https://lnproxy.org/spec", + "relayType": "Clearnet", + "network": "mainnet" } ]