Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Duplicate payment receive fix #236

Merged
merged 2 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions lib/android/src/main/java/com/reactnativeldk/LdkModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ enum class LdkCallbackResponses {
tx_set_unconfirmed,
process_pending_htlc_forwards_success,
claim_funds_success,
fail_htlc_backwards_success,
ldk_stop,
ldk_restart,
accept_channel_success,
Expand Down Expand Up @@ -506,6 +507,7 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod

handleResolve(promise, LdkCallbackResponses.channel_manager_init_success)
}

@ReactMethod
fun restart(promise: Promise) {
if (channelManagerConstructor == null) {
Expand Down Expand Up @@ -875,6 +877,15 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod
return handleResolve(promise, LdkCallbackResponses.claim_funds_success)
}

@ReactMethod
fun failHtlcBackwards(paymentHash: String, promise: Promise) {
channelManager ?: return handleReject(promise, LdkErrors.init_channel_manager)

channelManager!!.fail_htlc_backwards(paymentHash.hexa())

return handleResolve(promise, LdkCallbackResponses.fail_htlc_backwards_success)
}

//MARK: Fetch methods

@ReactMethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,11 @@ class LdkChannelManagerPersister: ChannelManagerConstructor.EventHandler {
val existingPayment = existingPayments.getJSONObject(i)
//Replace entry if payment hash exists (Confirmed payment replacing pending)
if (existingPayment.getString("payment_hash") == payment.getString("payment_hash")) {
//Don't replace a successful payment. If a 2nd wallet tries to pay an invoice the first successful one should not be overwritten.
if (existingPayment.getString("state") == "successful") {
return
}

payments[i] = mergeObj(existingPayment, payment.toHashMap())
paymentReplaced = true
continue
Expand Down
5 changes: 5 additions & 0 deletions lib/ios/Classes/LdkChannelManagerPersister.swift
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,11 @@ class LdkChannelManagerPersister: Persister, ExtendedChannelManagerPersister {
for (index, existingPayment) in payments.enumerated() {
if let existingPaymentHash = existingPayment["payment_hash"] as? String, let newPaymentHash = payment["payment_hash"] as? String {
if existingPaymentHash == newPaymentHash {
//Don't replace a successful payment. If a 2nd wallet tries to pay an invoice the first successful one should not be overwritten.
if existingPayment["state"] as? String == "successful" {
return
}

payments[index] = mergeObj(payments[index], payment) //Merges update into orginal entry
paymentReplaced = true
}
Expand Down
3 changes: 3 additions & 0 deletions lib/ios/Ldk.m
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ @interface RCT_EXTERN_MODULE(Ldk, NSObject)
RCT_EXTERN_METHOD(claimFunds:(NSString *)paymentPreimage
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(failHtlcBackwards:(NSString *)paymentHash
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)

//MARK: Misc methods
RCT_EXTERN_METHOD(writeToFile:(NSString *)fileName
Expand Down
20 changes: 17 additions & 3 deletions lib/ios/Ldk.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ enum LdkCallbackResponses: String {
case tx_set_unconfirmed = "tx_set_unconfirmed"
case process_pending_htlc_forwards_success = "process_pending_htlc_forwards_success"
case claim_funds_success = "claim_funds_success"
case fail_htlc_backwards_success = "fail_htlc_backwards_success"
case ldk_stop = "ldk_stop"
case ldk_restart = "ldk_restart"
case accept_channel_success = "accept_channel_success"
Expand Down Expand Up @@ -285,6 +286,9 @@ class Ldk: NSObject {

let networkGraphStoragePath = accountStoragePath.appendingPathComponent(LdkFileNames.network_graph.rawValue).standardizedFileURL

print("rapidGossipSyncUrl: \(rapidGossipSyncUrl)")
print("accountStoragePath: \(accountStoragePath)")

do {
let read = NetworkGraph.read(ser: [UInt8](try Data(contentsOf: networkGraphStoragePath)), arg: logger)
if read.isOk() {
Expand All @@ -305,9 +309,6 @@ class Ldk: NSObject {
return handleResolve(resolve, .network_graph_init_success)
}

print("rapidGossipSyncUrl: \(rapidGossipSyncUrl)")
print("accountStoragePath: \(accountStoragePath)")

//Download url passed, enable rapid gossip sync
do {
let rapidGossipSyncStoragePath = accountStoragePath.appendingPathComponent("rapid_gossip_sync")
Expand Down Expand Up @@ -953,6 +954,19 @@ class Ldk: NSObject {
return handleResolve(resolve, .claim_funds_success)
}

@objc
func failHtlcBackwards(_ paymentHash: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
guard let channelManager = channelManager else {
return handleReject(reject, .init_channel_manager)
}

LdkEventEmitter.shared.send(withEvent: .native_log, body: "Rejecting payment with failHtlcBackwards")

channelManager.failHtlcBackwards(paymentHash: String(paymentHash).hexaBytes)

return handleResolve(resolve, .fail_htlc_backwards_success)
}

//MARK: Fetch methods
@objc
func version(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
Expand Down
2 changes: 1 addition & 1 deletion lib/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@synonymdev/react-native-ldk",
"title": "React Native LDK",
"version": "0.0.137",
"version": "0.0.138",
"description": "React Native wrapper for LDK",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down
16 changes: 16 additions & 0 deletions lib/src/ldk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,22 @@ class LDK {
}
}

/**
* https://docs.rs/lightning/latest/lightning/ln/channelmanager/struct.ChannelManager.html#method.fail_htlc_backwards
* @returns {Promise<Err<unknown> | Ok<Ok<string> | Err<string>>>}
* @param paymentHash
*/
async failHtlcBackwards(paymentHash: string): Promise<Result<string>> {
try {
const res = await NativeLDK.failHtlcBackwards(paymentHash);
this.writeDebugToLog('failHtlcBackwards');
return ok(res);
} catch (e) {
this.writeErrorToLog('failHtlcBackwards', e);
return err(e);
}
}

/**
* Pays a bolt11 payment request and returns paymentId
* @param paymentRequest
Expand Down
33 changes: 30 additions & 3 deletions lib/src/lightning-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
TCreatePaymentReq,
TBackupServerDetails,
IAddress,
TLspLogPayload,

Check warning on line 58 in lib/src/lightning-manager.ts

View workflow job for this annotation

GitHub Actions / e2e-android

'TLspLogPayload' is defined but never used

Check warning on line 58 in lib/src/lightning-manager.ts

View workflow job for this annotation

GitHub Actions / e2e-ios

'TLspLogPayload' is defined but never used

Check warning on line 58 in lib/src/lightning-manager.ts

View workflow job for this annotation

GitHub Actions / Run lint check

'TLspLogPayload' is defined but never used

Check warning on line 58 in lib/src/lightning-manager.ts

View workflow job for this annotation

GitHub Actions / Run lint check

'TLspLogPayload' is defined but never used

Check warning on line 58 in lib/src/lightning-manager.ts

View workflow job for this annotation

GitHub Actions / mocha-ios

'TLspLogPayload' is defined but never used
TLspLogEvent,
} from './utils/types';
import {
Expand Down Expand Up @@ -1815,12 +1815,39 @@
); //TODO
}

private onChannelManagerPaymentClaimable(res: TChannelManagerClaim): void {
private async onChannelManagerPaymentClaimable(
res: TChannelManagerClaim,
): Promise<void> {
const claimedPayments = await this.getLdkPaymentsClaimed();

const existingClaimedPayment = claimedPayments.find(
(p) => p.payment_hash === res.payment_hash,
);

console.error(
`Existing claimed payments: ${JSON.stringify(existingClaimedPayment)}`,
);

if (
existingClaimedPayment &&
existingClaimedPayment.state === 'successful'
) {
ldk
.writeToLogFile(
'error',
`Failing payment as it was already claimed: ${res.payment_hash}`,
)
.catch(console.error);

await ldk.failHtlcBackwards(res.payment_hash);
return;
}

if (res.spontaneous_payment_preimage) {
//https://docs.rs/lightning/latest/lightning/util/events/enum.PaymentPurpose.html#variant.SpontaneousPayment
ldk.claimFunds(res.spontaneous_payment_preimage).catch(console.error);
await ldk.claimFunds(res.spontaneous_payment_preimage);
} else {
ldk.claimFunds(res.payment_preimage).catch(console.error);
await ldk.claimFunds(res.payment_preimage);
}
}

Expand Down Expand Up @@ -1911,7 +1938,7 @@
}

private onChannelManagerPendingHtlcsForwardable(
res: TChannelManagerPendingHtlcsForwardable,

Check warning on line 1941 in lib/src/lightning-manager.ts

View workflow job for this annotation

GitHub Actions / e2e-android

'res' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 1941 in lib/src/lightning-manager.ts

View workflow job for this annotation

GitHub Actions / e2e-ios

'res' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 1941 in lib/src/lightning-manager.ts

View workflow job for this annotation

GitHub Actions / Run lint check

'res' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 1941 in lib/src/lightning-manager.ts

View workflow job for this annotation

GitHub Actions / Run lint check

'res' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 1941 in lib/src/lightning-manager.ts

View workflow job for this annotation

GitHub Actions / mocha-ios

'res' is defined but never used. Allowed unused args must match /^_/u
): void {
ldk.processPendingHtlcForwards().catch(console.error);
}
Expand Down
Loading