diff --git a/.github/workflows/mocha-android.yml b/.github/workflows/mocha-android.yml new file mode 100644 index 00000000..22d4f643 --- /dev/null +++ b/.github/workflows/mocha-android.yml @@ -0,0 +1,118 @@ +name: mocha-android + +on: + pull_request: + branches: + - 'master' + push: + branches: + - 'master' + +jobs: + mocha-android: + runs-on: macos-12 + timeout-minutes: 120 + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 1 + + - name: Setup Docker Colima 1 + uses: douglascamata/setup-docker-macos-action@v1-alpha + id: docker1 + continue-on-error: true + + - name: Setup Docker Colima 2 + if: steps.docker1.outcome != 'success' + uses: douglascamata/setup-docker-macos-action@v1-alpha + id: docker2 + continue-on-error: true + + - name: Setup Docker Default + if: steps.docker1.outcome != 'success' && steps.docker2.outcome != 'success' + uses: docker-practice/actions-setup-docker@1.0.12 + timeout-minutes: 30 + + - name: Run regtest setup + working-directory: example/docker + run: | + mkdir lnd + mkdir clightning + chmod 777 lnd clightning + docker-compose up -d + + - name: Wait for electrum server + timeout-minutes: 2 + run: while ! nc -z '127.0.0.1' 60001; do sleep 1; done + + - name: Node + uses: actions/setup-node@v3 + with: + node-version: 16 + cache: 'yarn' # cache packages, but not node_modules + cache-dependency-path: 'example/yarn.lock' + + - name: Cache lib node modules + uses: actions/cache@v3 + id: lib-npmcache + with: + path: lib/node_modules + key: node-modules-${{ hashFiles('**/yarn.lock') }} + + - name: Install lib dependencies + if: steps.lib-npmcache.outputs.cache-hit != 'true' + working-directory: lib + run: yarn --no-audit --prefer-offline || yarn --no-audit --prefer-offline + + - name: Build lib + working-directory: lib + run: yarn build + + - name: Use gradle caches + uses: actions/cache@v2 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} + restore-keys: | + ${{ runner.os }}-gradle- + + # - name: Cache node modules + # uses: actions/cache@v3 + # id: cache-nm + # with: + # path: node_modules + # key: node-modules-${{ hashFiles('**/yarn.lock') }} + + - name: Install node_modules + working-directory: example + run: yarn install && yarn rn-setup + + - name: Use specific Java version for sdkmanager to work + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: '11' + + # - name: Build + # working-directory: example + # run: npx react-native run-android --no-packager + + - name: run tests + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 31 + avd-name: Pixel_API_31_AOSP + emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -camera-back none -camera-front none -partition-size 2047 + arch: x86_64 + disable-animations: true + working-directory: example + script: | + ../.github/workflows/mocha-anrdoid.sh + + - uses: actions/upload-artifact@v3 + if: failure() + with: + name: ldk-data + path: example/artifacts/ diff --git a/.github/workflows/mocha-anrdoid.sh b/.github/workflows/mocha-anrdoid.sh new file mode 100755 index 00000000..39cc4260 --- /dev/null +++ b/.github/workflows/mocha-anrdoid.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +adb reverse tcp:8090 tcp:8090 +adb reverse tcp:9090 tcp:9090 +adb reverse tcp:9091 tcp:9091 +adb reverse tcp:9092 tcp:9092 +adb reverse tcp:9735 tcp:9735 +adb reverse tcp:9736 tcp:9736 +adb reverse tcp:9737 tcp:9737 +adb reverse tcp:18080 tcp:18080 +adb reverse tcp:28081 tcp:28081 +adb reverse tcp:60001 tcp:60001 + +set +e +yarn test:mocha:android +EXIT_CODE=$? +set -e + +echo $EXIT_CODE; +if [ $EXIT_CODE -ne 0 ]; then + adb root + sleep 10 + adb pull /data/user/0/com.exmpl/files/ldk/ artifacts/ +fi + +exit $EXIT_CODE diff --git a/.github/workflows/mocha-ios.yml b/.github/workflows/mocha-ios.yml new file mode 100644 index 00000000..1db05070 --- /dev/null +++ b/.github/workflows/mocha-ios.yml @@ -0,0 +1,126 @@ +name: mocha-ios + +on: + pull_request: + branches: + - 'master' + push: + branches: + - 'master' + +jobs: + mocha-ios: + runs-on: macos-12 + timeout-minutes: 120 + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 1 + + - name: Setup Docker Colima 1 + uses: douglascamata/setup-docker-macos-action@v1-alpha + id: docker1 + continue-on-error: true + + - name: Setup Docker Colima 2 + if: steps.docker1.outcome != 'success' + uses: douglascamata/setup-docker-macos-action@v1-alpha + id: docker2 + continue-on-error: true + + - name: Setup Docker Default + if: steps.docker1.outcome != 'success' && steps.docker2.outcome != 'success' + uses: docker-practice/actions-setup-docker@1.0.12 + timeout-minutes: 30 + + - name: Run regtest setup + working-directory: example/docker + run: | + mkdir lnd + mkdir clightning + chmod 777 lnd clightning + docker-compose up -d + + - name: Wait for electrum server + timeout-minutes: 2 + run: while ! nc -z '127.0.0.1' 60001; do sleep 1; done + + - name: Node + uses: actions/setup-node@v3 + with: + node-version: 16 + cache: 'yarn' # cache packages, but not node_modules + cache-dependency-path: 'example/yarn.lock' + + - name: Cache lib node modules + uses: actions/cache@v3 + id: lib-npmcache + with: + path: lib/node_modules + key: node-modules-${{ hashFiles('**/yarn.lock') }} + + - name: Install lib dependencies + if: steps.lib-npmcache.outputs.cache-hit != 'true' + working-directory: lib + run: yarn --no-audit --prefer-offline || yarn --no-audit --prefer-offline + + - name: Build lib + working-directory: lib + run: yarn build + + - name: Cache app node modules + uses: actions/cache@v3 + id: cache-nm + with: + path: example/node_modules + key: node-modules-${{ hashFiles('**/yarn.lock') }} + + - name: Rebuild detox + if: steps.cache-nm.outputs.cache-hit == 'true' + working-directory: example + run: yarn detox clean-framework-cache && yarn detox build-framework-cache + + - name: Install Dependencies + if: steps.cache-nm.outputs.cache-hit != 'true' + working-directory: example + run: yarn install --no-audit --prefer-offline && yarn rn-setup + + - name: Cache Pods + uses: actions/cache@v3 + id: podcache + with: + path: example/ios/Pods + key: pods-${{ hashFiles('**/Podfile.lock') }} + + - name: Install pods + working-directory: example + run: | + gem update cocoapods xcodeproj + cd ios && pod install && cd .. + + - name: Install applesimutils + run: | + brew tap wix/brew + brew install applesimutils + + - name: Build + working-directory: example + run: npx react-native run-ios --no-packager + + - name: Test iOS app + working-directory: example + run: yarn test:mocha:ios + + - name: Prepare articrafts + if: failure() + run: | + mkdir articrafts + find /Users/runner/Library/Developer/CoreSimulator/Devices/ -path '*Documents/ldk' -exec cp -r "{}" articrafts/ \; + + - uses: actions/upload-artifact@v3 + if: failure() + with: + name: ldk-data + path: articrafts diff --git a/.gitignore b/.gitignore index ced5e907..061b1ac5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ .idea/ **/.DS_Store example/.watchman* + +# docker +example/docker/lnd/ +example/docker/clightning/ diff --git a/README.md b/README.md index 40366009..2e1c5adc 100644 --- a/README.md +++ b/README.md @@ -47,12 +47,61 @@ yarn android ## Notes - It is important to not mix and match account names and seeds when starting LDK. Doing so can result in a corrupt save. + ## Using zero conf channels + Channels needs to be manually accepted but this is handled by channel-manager.ts if counterparty is in our trusted peer list. + ```javascript + const userConfig: TUserConfig = { + channel_handshake_config: { + announced_channel: false, + minimum_depth: 1, + max_htlc_value_in_flight_percent_of_channel: 100, + negotiate_anchors_zero_fee_htlc_tx: true, //Required for zero conf + }, + manually_accept_inbound_channels: true, //Required for zero conf + accept_inbound_channels: true, +}; + ``` +When starting LDK, provide a list of node public keys from which you are willing to accept zero-confirmation channels. + +```javascript +const lmStart = await lm.start( + ... + trustedZeroConfPeers: ['03fc8877790430d7fb29e7bcf6b8bbfa3050e5e89189e27f97300e8a1e9ce589a3'] +``` + +From LND update your conf as specified [here](https://github.com/lightningnetwork/lnd/blob/master/docs/zero_conf_channels.md) and open with these params: + `lncli openchannel --node_key=03c6b2081d6f333fe3a9655cdb864be7b6b46c8648188a44b6a412e41b63a43272 --local_amt=200000 --push_amt=50000 --private=true --zero_conf --channel_type=anchors` + ## Upgrading LDK - Use latest LDK-release.aar from [ldk-garbagecollected](https://github.com/lightningdevkit/ldk-garbagecollected/releases) and place in `lib/android/libs`. - Use latest LDKFramework.xcframework from [ldk-swift](https://github.com/lightningdevkit/ldk-swift/releases) and place in lib/ios. - To get `pod install` working you might have to open the `LDKFramework.xcframework` directory, delete non ios frameworks and remove all references to deleted frameworks inside `LDKFramework.xcframework/Info.plist`. - Update Swift and Kotlin code if there are any breaking changes. +## Testing + +Tests are implemented using [mocha-remote](https://github.com/kraenhansen/mocha-remote). To run tests at first you need to install docker and start tesing regtest enviroment using docker-compose: + +```bash +cd example +docker-compose up +``` + +Then to run tests open two terminals and execute the following commands: + +```bash +# Terminal 1 +cd example +npm run start +``` + + +```bash +# Terminal 2 +cd example +npm run test:mocha +``` + ## How to test your code Because it's a native module, you need to mock this package. diff --git a/example/App.tsx b/example/App.tsx index 658c85ea..8c566da9 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -1,694 +1,53 @@ import './shim'; import React, { ReactElement, useEffect, useState } from 'react'; -import { - Alert, - Button, - EmitterSubscription, - Modal, - SafeAreaView, - ScrollView, - StyleSheet, - Text, - View, -} from 'react-native'; -import Clipboard from '@react-native-clipboard/clipboard'; -import { - backupAccount, - importAccount, - setupLdk, - syncLdk, - getAddressBalance, - updateHeader, -} from './ldk'; -import { connectToElectrum, subscribeToHeader } from './electrum'; -import ldk from '@synonymdev/react-native-ldk/dist/ldk'; -import lm, { - EEventTypes, - TChannelManagerClaim, - TChannelManagerPaymentPathFailed, - TChannelManagerPaymentPathSuccessful, - TChannelUpdate, -} from '@synonymdev/react-native-ldk'; -import { peers } from './utils/constants'; -import { - createNewAccount, - getAddress, - simulateStaleRestore, -} from './utils/helpers'; -import RNFS from 'react-native-fs'; +import { Button, SafeAreaView, StyleSheet, View } from 'react-native'; -let logSubscription: EmitterSubscription | undefined; -let paymentSubscription: EmitterSubscription | undefined; -let onChannelSubscription: EmitterSubscription | undefined; -let paymentFailedSubscription: EmitterSubscription | undefined; -let paymentPathSuccess: EmitterSubscription | undefined; -let backupSubscriptionId: string | undefined; +import Tests from './Tests'; +import Dev from './Dev'; +import { getItem, setItem } from './ldk'; const App = (): ReactElement => { - const [message, setMessage] = useState('...'); - const [nodeStarted, setNodeStarted] = useState(false); - const [showLogs, setShowLogs] = useState(false); - const [logContent, setLogContent] = useState(''); + const [tab, setTab] = useState(''); useEffect(() => { - //Restarting LDK on each code update causes constant errors. - if (nodeStarted) { - return; - } + getItem('tab').then((t) => { + handleChangeTab(t || 'tests'); + }); + }); - (async (): Promise => { - // Connect to Electrum Server - const electrumResponse = await connectToElectrum({}); - if (electrumResponse.isErr()) { - setMessage( - `Unable to connect to Electrum Server:\n ${electrumResponse.error.message}`, - ); - return; - } - // Subscribe to new blocks and sync LDK accordingly. - const headerInfo = await subscribeToHeader({ - onReceive: async (): Promise => { - const syncRes = await syncLdk(); - if (syncRes.isErr()) { - setMessage(syncRes.error.message); - return; - } - setMessage(syncRes.value); - }, - }); - if (headerInfo.isErr()) { - setMessage(headerInfo.error.message); - return; - } - await updateHeader({ header: headerInfo.value }); - // Setup LDK - const setupResponse = await setupLdk(); - if (setupResponse.isErr()) { - setMessage(setupResponse.error.message); - return; - } - - setNodeStarted(true); - setMessage(setupResponse.value); - })(); - }, [nodeStarted]); - - useEffect(() => { - if (!logSubscription) { - // @ts-ignore - logSubscription = ldk.onEvent(EEventTypes.ldk_log, (log: string) => - setLogContent((logs) => `${logs}${log}`), - ); - } - - if (!paymentSubscription) { - // @ts-ignore - paymentSubscription = ldk.onEvent( - EEventTypes.channel_manager_payment_claimed, - (res: TChannelManagerClaim) => alert(`Received ${res.amount_sat} sats`), - ); - } - - if (!paymentFailedSubscription) { - // @ts-ignore - paymentFailedSubscription = ldk.onEvent( - EEventTypes.channel_manager_payment_path_failed, - (res: TChannelManagerPaymentPathFailed) => - setMessage( - `Payment path failed ${ - res.payment_failed_permanently ? 'permanently' : 'temporarily' - }`, - ), - ); - } - - if (!paymentPathSuccess) { - // @ts-ignore - paymentPathSuccess = ldk.onEvent( - EEventTypes.channel_manager_payment_path_successful, - (res: TChannelManagerPaymentPathSuccessful) => - setMessage(`Payment path success ${res.payment_id}`), - ); - } - - if (!onChannelSubscription) { - // @ts-ignore - onChannelSubscription = ldk.onEvent( - EEventTypes.new_channel, - (res: TChannelUpdate) => - alert( - `Channel received from ${res.counterparty_node_id} Channel ${res.channel_id}`, - ), - ); - } - - if (!backupSubscriptionId) { - backupSubscriptionId = lm.subscribeToBackups((backupRes) => { - if (backupRes.isErr()) { - return alert('Backup required but failed to export account'); - } - - console.log( - `Backup updated for account ${backupRes.value.account.name}`, - ); - }); - } - - return (): void => { - logSubscription && logSubscription.remove(); - paymentSubscription && paymentSubscription.remove(); - paymentFailedSubscription && paymentFailedSubscription.remove(); - paymentPathSuccess && paymentPathSuccess.remove(); - onChannelSubscription && onChannelSubscription.remove(); - backupSubscriptionId && lm.unsubscribeFromBackups(backupSubscriptionId); - }; - }, []); + const handleChangeTab = (t: string): void => { + setTab(t); + setItem('tab', t); + }; return ( - - - react-native-ldk - - {message} - - -