Skip to content

Commit 9bf3bce

Browse files
author
Conor Okus
committed
Adds BDK create funding transaction rust example
1 parent 40e4ea8 commit 9bf3bce

File tree

1 file changed

+48
-55
lines changed

1 file changed

+48
-55
lines changed

docs/tutorials/building-a-node-with-ldk/opening-a-channel.md

Lines changed: 48 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@ userConfig.setChannelHandshakeConfig(val: channelConfig)
6868

6969
let createChannelResults = channelManager.createChannel(
7070
theirNetworkKey: pubKey,
71-
channelValueSatoshis: amount,
72-
pushMsat: pushMsat,
73-
userChannelId: userId,
71+
channelValueSatoshis: amount,
72+
pushMsat: pushMsat,
73+
userChannelId: userId,
7474
overrideConfig: userConfig
7575
)
7676
```
@@ -81,49 +81,40 @@ let createChannelResults = channelManager.createChannel(
8181

8282
# FundingGenerationReady Event Handling
8383

84-
At this point, an outbound channel has been initiated with your peer and it will appear in `ChannelManager::list_channels`. However, the channel is not yet funded. Once your peer accepts the channel, you will be notified with a `FundingGenerationReady` event. It's then your responsibility to construct the funding transaction and pass it to ChannelManager, which will broadcast it once it receives your channel counterparty's signature.
84+
At this point, an outbound channel has been initiated with your peer and it will appear in `ChannelManager::list_channels`. However, the channel is not yet funded. Once your peer accepts the channel, you will be notified with a `FundingGenerationReady` event. It's then your responsibility to construct the funding transaction and pass it to ChannelManager, which will broadcast it once it receives your channel counterparty's signature.
8585

8686
::: tip Note
8787

88-
Remember that the funding transaction must only spend SegWit inputs.
88+
Remember that the funding transaction must only spend [SegWit](https://bitcoinops.org/en/topics/segregated-witness/) inputs.
8989

9090
:::
9191

9292
<CodeSwitcher :languages="{rust:'Rust', kotlin:'Kotlin', swift:'Swift'}">
9393
<template v-slot:rust>
9494

9595
```rust
96-
// In the event handler passed to BackgroundProcessor::start
96+
// After the peer responds with an `accept_channel` message, an
97+
// Event.FundingGenerationReady event will be generated.
9798
match event {
9899
Event::FundingGenerationReady {
99100
temporary_channel_id,
100101
channel_value_satoshis,
101102
output_script,
102103
user_channel_id,
103104
} => {
104-
// This is the same channel created earler.
105-
assert_eq!(event.user_channel_id, 42);
106-
107-
// Construct the raw transaction with one output, that is paid the amount of the
108-
// channel.
109-
let network = bitcoin_bech32::constants::Network::Testnet;
110-
let address = WitnessProgram::from_scriptpubkey(&output_script[..], network)
111-
.unwrap().to_address;
112-
let mut outputs = vec![HashMap::with_capacity(1)];
113-
outputs[0].insert(address, channel_value_satoshis as f64 / 100_000_000.0);
114-
let raw_tx = bitcoind_client.create_raw_transaction(outputs).await;
115-
116-
// Have your wallet put the inputs into the transaction such that the output is
117-
// satisfied.
118-
let funded_tx = bitcoind_client.fund_raw_transaction(raw_tx).await;
119-
assert!(funded_tx.changepos == 0 || funded_tx.changepos == 1);
120-
121-
// Sign the funding transaction and give it to ChannelManager to broadcast.
122-
let signed_tx = bitcoind_client.sign_raw_transaction_with_wallet(funded_tx.hex).await;
123-
assert_eq!(signed_tx.complete, true);
124-
let final_tx: Transaction =
125-
encode::deserialize(&hex_utils::to_vec(&signed_tx.hex).unwrap()).unwrap();
126-
channel_manager.funding_transaction_generated(&temporary_channel_id, final_tx).unwrap();
105+
// Generate the funding transaction for the channel based on the channel amount
106+
// The following uses BDK (Bitcoin Dev Kit) for on-chain logic
107+
let (psbt, _) = {
108+
let mut builder = wallet.build_tx();
109+
builder
110+
.add_recipient(output_script, channel_value_satoshis)
111+
.fee_rate(fee_rate)
112+
.enable_rbf()
113+
builder.finish()?
114+
};
115+
let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
116+
let raw_tx = finalized.extract_tx()
117+
127118
}
128119
// ...
129120
}
@@ -136,13 +127,10 @@ match event {
136127
```java
137128
// After the peer responds with an `accept_channel` message, an
138129
// Event.FundingGenerationReady event will be generated.
139-
140130
if (event is Event.FundingGenerationReady) {
141-
val funding_spk = event.output_script
131+
val fundingSpk = event.output_script
142132

143-
if (funding_spk.size == 34 && funding_spk[0].toInt() == 0 && funding_spk[1].toInt() == 32) {
144-
// Generate the funding transaction for the channel based on the channel amount
145-
// The following uses BDK (Bitcoin Dev Kit) for on-chain logic
133+
if (fundingSpk.size == 34 && fundingSpk[0].toInt() == 0 && fundingSpk[1].toInt() == 32) {
146134
val rawTx = buildFundingTx(event.channel_value_satoshis, event.output_script)
147135

148136
channelManager.funding_transaction_generated(
@@ -153,6 +141,8 @@ if (event is Event.FundingGenerationReady) {
153141
}
154142
}
155143

144+
// Generate the funding transaction for the channel based on the channel amount
145+
// The following uses BDK (Bitcoin Dev Kit) for on-chain logic
156146
fun buildFundingTx(value: Long, script: ByteArray): Transaction {
157147
val scriptListUByte: List<UByte> = script.toUByteArray().asList()
158148
val outputScript = Script(scriptListUByte)
@@ -173,21 +163,21 @@ fun buildFundingTx(value: Long, script: ByteArray): Transaction {
173163
```Swift
174164
// After the peer responds with an `accept_channel` message, an
175165
// Event.FundingGenerationReady event will be generated.
176-
177166
if let event = event.getValueAsFundingGenerationReady() {
178167
let script = Script(rawOutputScript: event.getOutputScript())
179168
let channelValue = event.getChannelValueSatoshis()
180169
let rawTx = buildFundingTx(script: script, amount: channelValue)
181170
if let rawTx = rawTx {
182171
channelManager.fundingTransactionGenerated(
183-
temporaryChannelId: event.getTemporaryChannelId(),
184-
counterpartyNodeId: event.getCounterpartyNodeId(),
172+
temporaryChannelId: event.getTemporaryChannelId(),
173+
counterpartyNodeId: event.getCounterpartyNodeId(),
185174
fundingTransaction: rawTx.serialize()
186175
)
187176
}
188177
}
189178

190-
// Building transaction using BDK
179+
// Generate the funding transaction for the channel based on the channel amount
180+
// The following uses BDK (Bitcoin Dev Kit) for on-chain logic
191181
func buildFundingTx(script: Script, amount: UInt64) -> Transaction? {
192182
do {
193183
let transaction = try TxBuilder().addRecipient(
@@ -208,6 +198,7 @@ func buildFundingTx(script: Script, amount: UInt64) -> Transaction? {
208198
</CodeSwitcher>
209199

210200
**References:** [Rust `FundingGenerationReady` docs](https://docs.rs/lightning/*/lightning/util/events/enum.Event.html#variant.FundingGenerationReady), [Java `FundingGenerationReady` bindings](https://github.com/lightningdevkit/ldk-garbagecollected/blob/main/src/main/java/org/ldk/structs/Event.java#L95)
201+
211202
# Broadcasting the Funding Transaction
212203

213204
After crafting the funding transaction you'll need to send it to the Bitcoin network where it will hopefully be mined and added to the blockchain. You'll need to watch this transaction and wait for a minimum of 6 confirmations before the channel is ready to use.
@@ -218,18 +209,18 @@ After crafting the funding transaction you'll need to send it to the Bitcoin net
218209
```rust
219210
// Using BDK (Bitcoin Dev Kit) to broadcast a transaction via the esplora client
220211
impl BroadcasterInterface for YourTxBroadcaster {
221-
fn broadcast_transaction(&self, tx: &Transaction) {
222-
let locked_runtime = self.tokio_runtime.read().unwrap();
223-
if locked_runtime.as_ref().is_none() {
224-
log_error!(self.logger, "Failed to broadcast transaction: No runtime.");
225-
return;
226-
}
212+
fn broadcast_transactions(&self, txs: &[&Transaction]) {
213+
let server_url = DEFAULT_ESPLORA_SERVER_URL.to_string();
214+
let tx_sync = Arc::new(EsploraSyncClient::new(server_url, Arc::clone(&logger)));
215+
let blockchain = EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP)
216+
.with_concurrency(BDK_CLIENT_CONCURRENCY);
217+
(blockchain, tx_sync)
227218

228219
let res = tokio::task::block_in_place(move || {
229220
locked_runtime
230221
.as_ref()
231222
.unwrap()
232-
.block_on(async move { self.blockchain.broadcast(tx).await })
223+
.block_on(async move { blockchain.broadcast(tx).await })
233224
});
234225

235226
match res {
@@ -251,20 +242,22 @@ impl BroadcasterInterface for YourTxBroadcaster {
251242

252243
// Using BDK (Bitcoin Dev Kit) to broadcast a transaction via the esplora client
253244
object YourTxBroadcaster : BroadcasterInterface.BroadcasterInterfaceInterface {
254-
override fun broadcast_transaction(tx: ByteArray?) {
255-
val esploraURL = "esploraUrl"
245+
override fun broadcast_transactions(txs: Array<out ByteArray>??) {
246+
val esploraURL = "esplora url"
256247
val blockchainConfig = BlockchainConfig.Esplora(EsploraConfig(esploraURL, null, 5u, 20u, null))
257-
val blockchain = Blockchain(blockchainConfig)
248+
val blockchain = Blockchain(blockchainConfig)
258249

259-
val uByteArray = UByteArray(tx.size) { tx[it].toUByte() }
260-
val transaction = Transaction(uByteArray.toList())
250+
txs?.let { transactions ->
251+
CoroutineScope(Dispatchers.IO).launch {
252+
transactions.forEach { txByteArray ->
253+
val uByteArray = txByteArray.toUByteArray()
254+
val transaction = Transaction(uByteArray.toList())
261255

262-
tx?.let {
263-
CoroutineScope(Dispatchers.IO).launch {
264-
blockchain.broadcast(transaction)
265-
}
256+
blockchain.broadcast(transaction)
257+
Log.i(LDKTAG, "The raw transaction broadcast is: ${txByteArray.toHex()}")
258+
}
259+
}
266260
} ?: throw(IllegalStateException("Broadcaster attempted to broadcast a null transaction"))
267-
268261
}
269262
}
270263

0 commit comments

Comments
 (0)