|
| 1 | +--- |
| 2 | +id: build_node |
| 3 | +title: "Building a Node: Checklist" |
| 4 | +--- |
| 5 | + |
| 6 | +## Introduction |
| 7 | + |
| 8 | +This document is a few checklists for everything you need to make a node using LDK. |
| 9 | + |
| 10 | +* The first checklist is everything you need to do on startup. |
| 11 | +* The second checklist is everything you need to do while LDK is running to keep it operational. |
| 12 | +* The third checklist covers most lightning operations you'll want to use, such as opening a channel |
| 13 | + |
| 14 | +Note that LDK does not assume that safe shutdown is available, so there is no |
| 15 | +shutdown checklist. |
| 16 | + |
| 17 | +This guide covers all major LDK operations besides sending and receiving payments, |
| 18 | +which are unsupported at the moment but Coming Soon^TM. |
| 19 | + |
| 20 | +## Startup Checklist |
| 21 | +- [ ] Initialize the fee estimator |
| 22 | + * What it's used for: estimating fees for on-chain transactions that LDK wants broadcasted. |
| 23 | + * Dependencies: none |
| 24 | + |
| 25 | +Example fee estimator that always returns `253` satoshis: |
| 26 | +```java |
| 27 | +// FeeEstimatorInterface is a functional interface, so we can implement it with a lambda |
| 28 | +final fee_estimator = FeeEstimator.new_impl((confirmation_target -> 253)); |
| 29 | +``` |
| 30 | +Rather than using static fees, you'll want to fill in the lambda with fetching up-to-date fees from a source like bitcoin core or your own API endpoint. |
| 31 | +- [ ] Initialize the logger |
| 32 | +Example logger that prints to the console: |
| 33 | +```java |
| 34 | +// LoggerInterface is a functional interface, so we can implement it with a lambda |
| 35 | +final logger = Logger.new_impl((String arg) -> System.out.println(arg)); |
| 36 | +``` |
| 37 | +- [ ] Initialize the transaction broadcaster |
| 38 | + * What it's used for: broadcasting various lightning transactions |
| 39 | + * Dependencies: none |
| 40 | + * Example transaction broadcaster skeleton: |
| 41 | +```java |
| 42 | +// Note that the `tx` argument is a []byte type. |
| 43 | +final tx_broadcaster = BroadcasterInterface.new_impl(tx -> { |
| 44 | + <insert code to actually broadcast the given transaction here> |
| 45 | +}); |
| 46 | +``` |
| 47 | +- [ ] Initialize the channel data persister |
| 48 | + * What it's used for: persisting crucial channel data in a timely manner |
| 49 | + * Dependencies: none |
| 50 | + * Example: |
| 51 | +```java |
| 52 | +Persist persister = Persist.new_impl(new Persist.PersistInterface() { |
| 53 | + @Override |
| 54 | + public Result_NoneChannelMonitorUpdateErrZ persist_new_channel(OutPoint id, ChannelMonitor data) { |
| 55 | + byte[] channel_monitor_bytes = data.write(); |
| 56 | + <insert code to write these bytes to disk, keyed by `OutPoint`> |
| 57 | + } |
| 58 | + |
| 59 | + @Override |
| 60 | + public Result_NoneChannelMonitorUpdateErrZ update_persisted_channel(OutPoint id, ChannelMonitorUpdate update, ChannelMonitor data) { |
| 61 | + byte[] channel_monitor_bytes = data.write(); |
| 62 | + <insert code to update the channel monitor's file on disk with these new bytes, keyed by `OutPoint`> |
| 63 | + } |
| 64 | +}); |
| 65 | +``` |
| 66 | +- [ ] Initialize the chain monitor |
| 67 | + * What it's used for: monitoring the chain for lighting transactions that are relevant to our node, and broadcasting force close transactions if need be |
| 68 | + * Dependencies: fee estimator, logger, transaction broadcaster, channel data persister |
| 69 | + * Optional dependency: a chain filter that allows LDK to let you know what transactions you should filter blocks for. This is useful if you pre-filter blocks or use compact filters. Otherwise, LDK will need full blocks. |
| 70 | + * Example: |
| 71 | +```java |
| 72 | +// Example of a ChainMonitor if you *are* running a light client or filtering for transactions: |
| 73 | +Filter tx_filter = Filter.new_impl(new Filter.FilterInterface() { |
| 74 | + @Override |
| 75 | + public void register_tx(byte[] txid, byte[] script_pubkey) { |
| 76 | + <insert code for you to watch for this transaction on-chain> |
| 77 | + } |
| 78 | + |
| 79 | + @Override |
| 80 | + void register_output(OutPoint outpoint, byte[] script_pubkey) { |
| 81 | + <insert code for you to watch for this output on-chain> |
| 82 | + } |
| 83 | +}); |
| 84 | +final chain_monitor = ChainMonitor.constructor_new(tx_filter, tx_broadcaster, logger, fee_estimator, persister); |
| 85 | + |
| 86 | +// Example of a ChainMonitor if you are *not* running a light client and can provide |
| 87 | +full blocks: |
| 88 | +final chain_monitor = ChainMonitor.constructor_new(null, tx_broadcaster, logger, fee_estimator, persister); |
| 89 | +``` |
| 90 | +- [ ] Initialize the keys manager |
| 91 | + * What it's used for: providing keys for signing lightning transactions |
| 92 | + * Dependencies: random bytes, the current bitcoin network |
| 93 | + * Example: |
| 94 | +```java |
| 95 | +byte[] key_seed = new byte[32]; |
| 96 | +<insert code to fill key_seed with random bytes> |
| 97 | +// Notes about this KeysManager: |
| 98 | +// * it is parameterized by the mainnet bitcoin network, but this should be swapped out for testnet or regtest as needed. |
| 99 | +// * TODO document why the current time is part of the parameters |
| 100 | +KeysManager keys = KeysManager.constructor_new(key_seed, LDKNetwork.LDKNetwork_Bitcoin, System.currentTimeMillis() / 1000, (int) (System.currentTimeMillis() * 1000)); |
| 101 | +``` |
| 102 | +
|
| 103 | +See the Key Management guide for more information. |
| 104 | +- [ ] If LDK is restarting, fill in the chain monitor's existing channel monitor state |
| 105 | + * Dependencies: the keys manager |
| 106 | + * If LDK is restarting and has existing channels, then it's very important to read its current channel state off of disk during the restart process. |
| 107 | + * Equally important: when you read each channel monitor off of disk, it comes with a blockhash which was the last block the channel monitor saw. So it's very important to take this blockhash, and: |
| 108 | + 1. If the blockhash is on a fork that's no longer current to the chain, then first you need to disconnect blocks until the channel monitor gets to the common ancestor with the main chain |
| 109 | + 2. Then after this disconnection happens if it needs to, you then need to connect recent blocks until the channel monitor is at the current chain tip. |
| 110 | +
|
| 111 | +Example of reading channel monitors from disk, where each channel monitor's file is named after its funding outpoint: |
| 112 | +```java |
| 113 | +byte[] channel_monitor_bytes = <read the bytes from disk the same way you wrote them in step "Initialize the channel data persister">; |
| 114 | +Result_C2Tuple_BlockHashChannelMonitorZDecodeErrorZ channel_monitor_read_result = |
| 115 | + UtilMethods.constructor_BlockHashChannelMonitorZ_read(monitor_bytes, keys_interface); |
| 116 | + |
| 117 | +// Assert that the result of reading bytes from disk is OK. |
| 118 | +assert channel_monitor_read_result instanceof Result_C2Tuple_BlockHashChannelMonitorZDecodeErrorZ.Result_C2Tuple_BlockHashChannelMonitorZDecodeErrorZ_OK; |
| 119 | + |
| 120 | +// Cast the result of reading bytes from disk into its type in the `success` read case. |
| 121 | +TwoTuple<OutPoint, byte[]> funding_txo_and_monitor = |
| 122 | + ((Result_C2Tuple_BlockHashChannelMonitorZDecodeErrorZ |
| 123 | + .Result_C2Tuple_BlockHashChannelMonitorZDecodeErrorZ_OK) channel_monitor_read_result) |
| 124 | + |
| 125 | +// Cast the bytes in the result as a ChannelMonitor. |
| 126 | +ChannelMonitor monitor = ((Result_C2Tuple_BlockHashChannelMonitorZDecodeErrorZ |
| 127 | + .Result_C2Tuple_BlockHashChannelMonitorZDecodeErrorZ_OK) res).res.b; |
| 128 | + |
| 129 | +<insert code here to bring the channel monitor up to chain tip> |
| 130 | + |
| 131 | +// Give the channel monitor to the chain monitor. |
| 132 | +final chain_watch = chain_monitor.as_Watch(); |
| 133 | +chain_watch.watch_channel(mon.get_funding_txo().a, mon); |
| 134 | +``` |
| 135 | + |
| 136 | +Rust example of bringing a channel monitor up to chain tip: https://github.com/rust-bitcoin/rust-lightning/pull/763/files#diff-f457bab978fc8b89ad308d5195f99d7b65a4a6ba1673c5f164104b2dda9a0db6R251 where the channel monitor is the `chain_listener` parameter. See the linked function and the `find_fork` function within it. |
| 137 | +- [ ] Optional: initialize the router (which we call the `NetGraphMsgHandler`) |
| 138 | + * What it's used for: generating routes to send payments over |
| 139 | + * Dependencies: logger |
| 140 | + * Optional dependency: source of chain information, recommended for light clients to be able to verify channels |
| 141 | + * Example: |
| 142 | +```java |
| 143 | +final router = NetGraphMsgHandler.constructor_new(new byte[32], null, logger); |
| 144 | +``` |
| 145 | +- [ ] Initialize the channel manager |
| 146 | + * What it's used for: managing channel state |
| 147 | + * Dependencies: keys manager, fee estimator, chain monitor, transaction broadcaster, logger, channel configuration info, and the set of channel monitors we read from disk in the previous step |
| 148 | + |
| 149 | +Example of initializing a channel manager on a fresh node: |
| 150 | +```java |
| 151 | +final chain_watch = chain_monitor.as_Watch(); |
| 152 | +// TODO: document the last param, that 1 |
| 153 | +final channel_manager = ChannelManager.constructor_new( |
| 154 | + LDKNetwork.LDKNetwork_Bitcoin, FeeEstimator.new_impl(confirmation_target -> 0), |
| 155 | + chain_watch, tx_broadcaster, logger, keys_interface, UserConfig.constructor_default(), 1); |
| 156 | +``` |
| 157 | +Example of initializing a channel manager on restart: |
| 158 | +```java |
| 159 | +byte[] serialized_channel_manager = <insert bytes you would have written in following the later step "Persist channel manager">; |
| 160 | +Result_C2Tuple_BlockHashChannelManagerZDecodeErrorZ channel_manager_read_result = |
| 161 | + UtilMethods.constructor_BlockHashChannelManagerZ_read(serialized_channel_manager, |
| 162 | + keys_interface, fee_estimator, chain_monitor.as_Watch(), tx_broadcaster, logger, |
| 163 | + UserConfig.constructor_default(), channel_monitors); |
| 164 | + |
| 165 | +// Assert we were able to read successfully. |
| 166 | +assert channel_manager_read_result instanceof Result_C2Tuple_BlockHashChannelManagerZDecodeErrorZ.Result_C2Tuple_BlockHashChannelManagerZDecodeErrorZ_OK; |
| 167 | + |
| 168 | +final channel_manager = ((Result_C2Tuple_BlockHashChannelManagerZDecodeErrorZ |
| 169 | + .Result_C2Tuple_BlockHashChannelManagerZDecodeErrorZ_OK) channel_manager_read_result) |
| 170 | + .res.b; |
| 171 | +``` |
| 172 | +- [ ] Initialize the peer manager using LDK's `PeerManager` struct combined with LDK's supplied `NioPeerHandler` networking battery |
| 173 | + * What it's used for: connecting to peers, facilitating peer data to and from LDK |
| 174 | + * Dependencies: channel manager, router (optional), keys manager, random bytes, logger |
| 175 | + * Example: |
| 176 | +```java |
| 177 | +byte[] random_data = new byte[32]; |
| 178 | +<insert code to fill in `random_data` with random bytes> |
| 179 | +final nio_peer_handler; |
| 180 | +final peer_manager = PeerManager.constructor_new(chan_manager.as_ChannelMessageHandler(), |
| 181 | + router.as_RoutingMessageHandler(), keys_interface.get_node_secret(), random_data, logger); |
| 182 | +try { nio_peer_handler = new NioPeerHandler(peer_manager); } catch (IOException e) { assert false; } |
| 183 | +
|
| 184 | +// Finally, start NioPeerHandler listening for connections. |
| 185 | +final port = 9735; |
| 186 | +nio_peer_handler.bind_listener(new InetSocketAddress("127.0.0.1", port)); |
| 187 | +``` |
| 188 | +- [ ] Start a loop to handle the channel manager's generated events |
| 189 | + * What it's used for: the channel manager and chain monitor generate events that must be handled by you, such as telling you when a payment has been successfully received or when a funding transaction is ready for broadcast. |
| 190 | + * Dependencies: channel manager and chain monitor |
| 191 | +
|
| 192 | +Rust example: https://github.com/TheBlueMatt/rust-lightning-bitcoinrpc/blob/master/src/main.rs#L122 |
| 193 | +- [ ] Persist channel manager: in the loop you started in the previous step, add the feature of persisting the channel manager after each event. |
| 194 | + * If the channel manager does not get persisted properly to disk, there is risk of channels force closing the next time LDK starts up. However, in this situation, no funds other than those used to pay force-closed channel fees are at risk of being lost. |
| 195 | +```java |
| 196 | +while (true) { |
| 197 | + <code from the previous step that handles channel manager events> |
| 198 | + ... |
| 199 | + byte[] channel_manager_bytes_to_write = channel_manager.write(); |
| 200 | + <insert code that writes these bytes to disk and/or backups> |
| 201 | +} |
| 202 | +``` |
| 203 | +- [ ] Start a loop to call the channel manager's `timer_chan_freshness_every_min()` every minute |
| 204 | + * What it's used for: the channel manager needs to be told every time a minute passes so that it can broadcast fresh channel updates if needed |
| 205 | +Example: |
| 206 | +```java |
| 207 | +while (true) { |
| 208 | + <wait 60 seconds> |
| 209 | + channel_manager.timer_chan_freshness_every_min(); |
| 210 | +} |
| 211 | +``` |
| 212 | +
|
| 213 | +## Running LDK Checklist |
| 214 | +- [ ] Connect and disconnect blocks to LDK as they come in |
| 215 | +```java |
| 216 | +// header is a []byte type, height is `int`, txdata is a |
| 217 | +// TwoTuple<Long, byte[]>[], where the 0th element is block index and the 1st |
| 218 | +// element is the transaction bytes |
| 219 | +channel_manager.block_connected(header, txn, height); |
| 220 | +chain_monitor.block_connected(header, txn, height); |
| 221 | +
|
| 222 | +channel_manager.block_disconnected(header); |
| 223 | +chain_monitor.block_disconnected(header); |
| 224 | +``` |
| 225 | +
|
| 226 | +## Using LDK |
| 227 | +- [ ] Opening a channel: see the "Opening a Channel" guide |
| 228 | +- [ ] Closing a channel |
| 229 | + * Dependencies: channel manager |
| 230 | +```java |
| 231 | +// Cooperative close: |
| 232 | +// Assuming 1 open channel |
| 233 | +byte[] channel_id = channel_manager.list_channels()[0].get_channel_id(); |
| 234 | +Result_NoneAPIErrorZ close_result = channel_manager.close_channel( |
| 235 | + channel_id); |
| 236 | +assert close_result instanceof Result_NoneAPIErrorZ.Result_NoneAPIErrorZ_OK; |
| 237 | +// Make sure the peer manager processes this new event. |
| 238 | +nio_peer_handler.check_events(); |
| 239 | +// LDK should give the transaction broadcaster a closing transaction to broadcast |
| 240 | +// after this |
| 241 | +
|
| 242 | +// Force close: |
| 243 | +// Assuming 1 open channel |
| 244 | +byte[] channel_id = channel_manager.list_channels()[0].get_channel_id(); |
| 245 | +Result_NoneAPIErrorZ channel_manager.force_close_channel(channel_id); |
| 246 | +``` |
| 247 | +- [ ] List open channels |
| 248 | +```java |
| 249 | +ChannelDetails[] channels = channel_manager.list_channels(); |
| 250 | +``` |
| 251 | +- [ ] Sending/receiving payments **NOTE: CURRENTLY UNSUPPORTED IN JAVA** |
| 252 | + * Currently unsatisfied dependencies: |
| 253 | + 1. a way of constructing `NodeFeatures` and `ChannelFeatures` LDK structs (which should be exposed soon) |
| 254 | + 2. a way to parse invoices (we need to generate bindings for the `rust-invoices` crate) |
| 255 | +- [ ] Connect to a peer |
| 256 | + * Dependencies: peer manager, peer's pubkey, peer's host and port |
| 257 | +
|
| 258 | +Example: |
| 259 | +```java |
| 260 | +byte[] peer_pubkey = <peer's pubkey bytes> |
| 261 | +int peer_port = 9745; |
| 262 | +SocketAddress peer_socket_addr = new InetSocketAddress("192.168.1.2", peer_port); |
| 263 | +nio_peer_handler.connect(peer_pubkey, peer_socket_address); |
| 264 | +``` |
| 265 | +- [ ] List peers |
| 266 | +// TODO |
0 commit comments