Skip to content

Commit ae16bb6

Browse files
authored
Merge pull request #19 from synonymdev/feat/emit-replaced-tx-conflicts
Emit replaced tx conflicts
2 parents 5274c76 + 462ed61 commit ae16bb6

File tree

12 files changed

+129
-31
lines changed

12 files changed

+129
-31
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
- `OnchainTransactionReceived`: Emitted when a new unconfirmed transaction is
77
first detected in the mempool (instant notification for incoming payments!)
88
- `OnchainTransactionConfirmed`: Emitted when a transaction receives confirmations
9-
- `OnchainTransactionReplaced`: Emitted when a transaction is replaced (via RBF or different transaction using a commmon input)
9+
- `OnchainTransactionReplaced`: Emitted when a transaction is replaced (via RBF or different transaction using a common input). Includes the replaced transaction ID and the list of conflicting replacement transaction IDs.
1010
- `OnchainTransactionReorged`: Emitted when a previously confirmed transaction
1111
becomes unconfirmed due to a blockchain reorg
1212
- `OnchainTransactionEvicted`: Emitted when a transaction is evicted from the mempool

MOBILE_DEVELOPER_GUIDE.md

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ These events notify you about Bitcoin transactions affecting your onchain wallet
4141
- **When**: Transaction is replaced via Replace-By-Fee (RBF)
4242
- **Use Case**: Update UI to show the transaction was replaced
4343
- **Fields**:
44-
- `txid`: Transaction ID of the replacement transaction
44+
- `txid`: Transaction ID of the transaction that was replaced (the old transaction)
45+
- `conflicts`: Array of transaction IDs that replaced this transaction (the replacement transactions)
4546

4647
#### `OnchainTransactionReorged`
4748
- **When**: Previously confirmed transaction becomes unconfirmed due to blockchain reorg
@@ -201,8 +202,8 @@ class WalletEventHandler {
201202
case .onchainTransactionConfirmed(let txid, let blockHash, let blockHeight, let confirmationTime, let details):
202203
handleConfirmedTransaction(txid: txid, height: blockHeight, details: details)
203204

204-
case .onchainTransactionReplaced(let txid):
205-
handleReplacedTransaction(txid: txid)
205+
case .onchainTransactionReplaced(let txid, let conflicts):
206+
handleReplacedTransaction(txid: txid, conflicts: conflicts)
206207

207208
case .onchainTransactionReorged(let txid):
208209
handleReorgedTransaction(txid: txid)
@@ -278,14 +279,27 @@ func handleConfirmedTransaction(txid: String, height: UInt32, details: Transacti
278279
}
279280
}
280281

281-
func handleReplacedTransaction(txid: String) {
282+
func handleReplacedTransaction(txid: String, conflicts: [String]) {
282283
DispatchQueue.main.async {
283284
self.updateTransactionStatus(txid: txid, status: .replaced)
284-
self.showNotification(
285-
title: "Transaction Replaced",
286-
body: "Transaction was replaced via RBF",
287-
txid: txid
288-
)
285+
286+
if conflicts.isEmpty {
287+
self.showNotification(
288+
title: "Transaction Replaced",
289+
body: "Transaction was replaced via RBF",
290+
txid: txid
291+
)
292+
} else {
293+
self.showNotification(
294+
title: "Transaction Replaced",
295+
body: "Transaction was replaced by \(conflicts.count) transaction(s)",
296+
txid: txid
297+
)
298+
// Track replacement transactions
299+
for conflictTxid in conflicts {
300+
self.trackReplacementTransaction(originalTxid: txid, replacementTxid: conflictTxid)
301+
}
302+
}
289303
}
290304
}
291305

@@ -460,7 +474,7 @@ class WalletEventHandler(private val node: Node) {
460474
}
461475

462476
is Event.OnchainTransactionReplaced -> {
463-
handleReplacedTransaction(event.txid)
477+
handleReplacedTransaction(event.txid, event.conflicts)
464478
}
465479

466480
is Event.OnchainTransactionReorged -> {
@@ -556,14 +570,27 @@ class TransactionNotificationManager(private val context: Context) {
556570
}
557571
}
558572

559-
fun handleReplacedTransaction(txid: String) {
573+
fun handleReplacedTransaction(txid: String, conflicts: List<String>) {
560574
GlobalScope.launch(Dispatchers.Main) {
561575
updateTransactionStatus(txid, TransactionStatus.REPLACED)
562-
showNotification(
563-
title = "Transaction Replaced",
564-
message = "Transaction was replaced via RBF",
565-
txid = txid
566-
)
576+
577+
if (conflicts.isEmpty()) {
578+
showNotification(
579+
title = "Transaction Replaced",
580+
message = "Transaction was replaced via RBF",
581+
txid = txid
582+
)
583+
} else {
584+
showNotification(
585+
title = "Transaction Replaced",
586+
message = "Transaction was replaced by ${conflicts.size} transaction(s)",
587+
txid = txid
588+
)
589+
// Track replacement transactions
590+
conflicts.forEach { conflictTxid ->
591+
trackReplacementTransaction(originalTxid = txid, replacementTxid = conflictTxid)
592+
}
593+
}
567594
}
568595
}
569596

Binary file not shown.
Binary file not shown.
Binary file not shown.

bindings/kotlin/ldk-node-android/lib/src/main/kotlin/org/lightningdevkit/ldknode/ldk_node.android.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8336,6 +8336,7 @@ object FfiConverterTypeEvent : FfiConverterRustBuffer<Event>{
83368336
)
83378337
11 -> Event.OnchainTransactionReplaced(
83388338
FfiConverterTypeTxid.read(buf),
8339+
FfiConverterSequenceTypeTxid.read(buf),
83398340
)
83408341
12 -> Event.OnchainTransactionReorged(
83418342
FfiConverterTypeTxid.read(buf),
@@ -8476,6 +8477,7 @@ object FfiConverterTypeEvent : FfiConverterRustBuffer<Event>{
84768477
(
84778478
4UL
84788479
+ FfiConverterTypeTxid.allocationSize(value.`txid`)
8480+
+ FfiConverterSequenceTypeTxid.allocationSize(value.`conflicts`)
84798481
)
84808482
}
84818483
is Event.OnchainTransactionReorged -> {
@@ -8614,6 +8616,7 @@ object FfiConverterTypeEvent : FfiConverterRustBuffer<Event>{
86148616
is Event.OnchainTransactionReplaced -> {
86158617
buf.putInt(11)
86168618
FfiConverterTypeTxid.write(value.`txid`, buf)
8619+
FfiConverterSequenceTypeTxid.write(value.`conflicts`, buf)
86178620
Unit
86188621
}
86198622
is Event.OnchainTransactionReorged -> {
@@ -11364,6 +11367,31 @@ object FfiConverterSequenceTypeSocketAddress: FfiConverterRustBuffer<List<Socket
1136411367

1136511368

1136611369

11370+
11371+
object FfiConverterSequenceTypeTxid: FfiConverterRustBuffer<List<Txid>> {
11372+
override fun read(buf: ByteBuffer): List<Txid> {
11373+
val len = buf.getInt()
11374+
return List<Txid>(len) {
11375+
FfiConverterTypeTxid.read(buf)
11376+
}
11377+
}
11378+
11379+
override fun allocationSize(value: List<Txid>): ULong {
11380+
val sizeForLength = 4UL
11381+
val sizeForItems = value.sumOf { FfiConverterTypeTxid.allocationSize(it) }
11382+
return sizeForLength + sizeForItems
11383+
}
11384+
11385+
override fun write(value: List<Txid>, buf: ByteBuffer) {
11386+
buf.putInt(value.size)
11387+
value.iterator().forEach {
11388+
FfiConverterTypeTxid.write(it, buf)
11389+
}
11390+
}
11391+
}
11392+
11393+
11394+
1136711395
object FfiConverterMapStringString: FfiConverterRustBuffer<Map<kotlin.String, kotlin.String>> {
1136811396
override fun read(buf: ByteBuffer): Map<kotlin.String, kotlin.String> {
1136911397
val len = buf.getInt()

bindings/kotlin/ldk-node-android/lib/src/main/kotlin/org/lightningdevkit/ldknode/ldk_node.common.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1278,6 +1278,7 @@ sealed class Event {
12781278
@kotlinx.serialization.Serializable
12791279
data class OnchainTransactionReplaced(
12801280
val `txid`: Txid,
1281+
val `conflicts`: List<Txid>,
12811282
) : Event() {
12821283
}
12831284
@kotlinx.serialization.Serializable
@@ -1916,6 +1917,8 @@ sealed class VssHeaderProviderException(message: String): kotlin.Exception(messa
19161917

19171918

19181919

1920+
1921+
19191922

19201923

19211924

bindings/ldk_node.udl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ interface Event {
431431
ChannelClosed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, ClosureReason? reason);
432432
OnchainTransactionConfirmed(Txid txid, BlockHash block_hash, u32 block_height, u64 confirmation_time, TransactionDetails details);
433433
OnchainTransactionReceived(Txid txid, TransactionDetails details);
434-
OnchainTransactionReplaced(Txid txid);
434+
OnchainTransactionReplaced(Txid txid, sequence<Txid> conflicts);
435435
OnchainTransactionReorged(Txid txid);
436436
OnchainTransactionEvicted(Txid txid);
437437
SyncProgress(SyncType sync_type, u8 progress_percent, u32 current_block_height, u32 target_block_height);

bindings/swift/Sources/LDKNode/LDKNode.swift

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5848,8 +5848,7 @@ public enum Event {
58485848
case channelClosed(channelId: ChannelId, userChannelId: UserChannelId, counterpartyNodeId: PublicKey?, reason: ClosureReason?)
58495849
case onchainTransactionConfirmed(txid: Txid, blockHash: BlockHash, blockHeight: UInt32, confirmationTime: UInt64, details: TransactionDetails)
58505850
case onchainTransactionReceived(txid: Txid, details: TransactionDetails)
5851-
case onchainTransactionReplaced(txid: Txid
5852-
)
5851+
case onchainTransactionReplaced(txid: Txid, conflicts: [Txid])
58535852
case onchainTransactionReorged(txid: Txid
58545853
)
58555854
case onchainTransactionEvicted(txid: Txid
@@ -5885,8 +5884,7 @@ public struct FfiConverterTypeEvent: FfiConverterRustBuffer {
58855884

58865885
case 10: return try .onchainTransactionReceived(txid: FfiConverterTypeTxid.read(from: &buf), details: FfiConverterTypeTransactionDetails.read(from: &buf))
58875886

5888-
case 11: return try .onchainTransactionReplaced(txid: FfiConverterTypeTxid.read(from: &buf)
5889-
)
5887+
case 11: return try .onchainTransactionReplaced(txid: FfiConverterTypeTxid.read(from: &buf), conflicts: FfiConverterSequenceTypeTxid.read(from: &buf))
58905888

58915889
case 12: return try .onchainTransactionReorged(txid: FfiConverterTypeTxid.read(from: &buf)
58925890
)
@@ -5981,9 +5979,10 @@ public struct FfiConverterTypeEvent: FfiConverterRustBuffer {
59815979
FfiConverterTypeTxid.write(txid, into: &buf)
59825980
FfiConverterTypeTransactionDetails.write(details, into: &buf)
59835981

5984-
case let .onchainTransactionReplaced(txid):
5982+
case let .onchainTransactionReplaced(txid, conflicts):
59855983
writeInt(&buf, Int32(11))
59865984
FfiConverterTypeTxid.write(txid, into: &buf)
5985+
FfiConverterSequenceTypeTxid.write(conflicts, into: &buf)
59875986

59885987
case let .onchainTransactionReorged(txid):
59895988
writeInt(&buf, Int32(12))
@@ -8613,6 +8612,28 @@ private struct FfiConverterSequenceTypeSocketAddress: FfiConverterRustBuffer {
86138612
}
86148613
}
86158614

8615+
private struct FfiConverterSequenceTypeTxid: FfiConverterRustBuffer {
8616+
typealias SwiftType = [Txid]
8617+
8618+
static func write(_ value: [Txid], into buf: inout [UInt8]) {
8619+
let len = Int32(value.count)
8620+
writeInt(&buf, len)
8621+
for item in value {
8622+
FfiConverterTypeTxid.write(item, into: &buf)
8623+
}
8624+
}
8625+
8626+
static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [Txid] {
8627+
let len: Int32 = try readInt(&buf)
8628+
var seq = [Txid]()
8629+
seq.reserveCapacity(Int(len))
8630+
for _ in 0 ..< len {
8631+
try seq.append(FfiConverterTypeTxid.read(from: &buf))
8632+
}
8633+
return seq
8634+
}
8635+
}
8636+
86168637
private struct FfiConverterDictionaryStringString: FfiConverterRustBuffer {
86178638
static func write(_ value: [String: String], into buf: inout [UInt8]) {
86188639
let len = Int32(value.count)

src/chain/mod.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -411,9 +411,15 @@ where
411411
);
412412
// We don't emit an event for chain tip changes as this is too noisy
413413
},
414-
BdkWalletEvent::TxReplaced { txid, .. } => {
415-
log_info!(logger, "Onchain transaction {} was replaced", txid);
416-
let event = Event::OnchainTransactionReplaced { txid };
414+
BdkWalletEvent::TxReplaced { txid, conflicts, .. } => {
415+
let conflict_txids: Vec<Txid> = conflicts.iter().map(|(_, conflict_txid)| *conflict_txid).collect();
416+
log_info!(
417+
logger,
418+
"Onchain transaction {} was replaced by {} transaction(s)",
419+
txid,
420+
conflict_txids.len()
421+
);
422+
let event = Event::OnchainTransactionReplaced { txid, conflicts: conflict_txids };
417423
event_queue.add_event(event).map_err(|e| {
418424
log_error!(logger, "Failed to push onchain event to queue: {}", e);
419425
e

0 commit comments

Comments
 (0)