# Building a Node with LDK in Java

# Introduction

This document covers everything you need to make a node using LDK in Java.

  • Setup covers everything you need to do to set up LDK on startup.
  • Running LDK covers everything you need to do while LDK is running to keep it operational.

Note that LDK does not assume that safe shutdown is available, so there is no shutdown checklist.

# Setup

# 1. Initialize the FeeEstimator

What it's used for: estimating fees for on-chain transactions that LDK wants broadcasted.

Example:

class YourFeeEstimator implements FeeEstimator.FeeEstimatorInterface {
    @Override
    public int get_est_sat_per_1000_weight(ConfirmationTarget conf_target) {
        if (conf_target ==
            ConfirmationTarget.LDKConfirmationTarget_Background) {
            // <insert code to retrieve a background feerate>
        } else if (conf_target ==
            ConfirmationTarget.LDKConfirmationTarget_Normal) {
            // <insert code to retrieve a normal (i.e. within ~6 blocks) feerate>
        } else if (conf_target ==
            ConfirmationTarget.LDKConfirmationTarget_HighPriority) {
            // <insert code to retrieve a high-priority feerate>
        }
    }
}

FeeEstimator fee_estimator = FeeEstimator.new_impl(new YourFeeEstimator());

Implementation notes:

  1. Fees must be returned in: satoshis per 1000 weight units
  2. Fees returned must be no smaller than 253 (equivalent to 1 satoshi/vbyte, rounded up)
  3. To reduce network traffic, you may want to cache fee results rather than retrieving fresh ones every time

Dependencies: none

References: Rust docs (opens new window), Java bindings (opens new window)

# 2. Initialize the Logger

What it's used for: LDK logging

Example:

class YourLogger implements Logger.LoggerInterface {
    @Override
    public void log(String record) {
        // <insert code to print this log and/or write this log to a file>
    }
}

Logger logger = Logger.new_impl(new YourLogger());

Implementation notes: you'll most likely want to write the logs to a file for debugging purposes.

Dependencies: none

References: Rust docs (opens new window), Java bindings (opens new window)

# 3. Initialize the BroadcasterInterface

What it's used for: broadcasting various Lightning transactions

Example:

class YourBroadcaster implements
    BroadcasterInterface.BroadcasterInterfaceInterface {

    @Override
    public void broadcast_transaction(byte[] tx) {
        // <insert code to broadcast the given transaction here>
    }
}

BroadcasterInterface tx_broadcaster =
    BroadcasterInterface.new_impl(new YourBroadcaster());

Dependencies: none

References: Rust docs (opens new window), Java bindings (opens new window)

# 4. Optional: Initialize the NetworkGraph

You must follow this step if: you need LDK to provide routes for sending payments (i.e. you are not providing your own routes)

What it's used for: generating routes to send payments over

Example: initializing NetworkGraph on signet

Network network = Network.LDKNetwork_Signet;

BestBlock genesisBlock = BestBlock.from_genesis(network);
final byte[] genesis_block_hash = genesisBlock.block_hash();

final NetworkGraph networkGraph = NetworkGraph.of(genesis_block_hash);

Implementation notes: this struct is not required if you are providing your own routes. It will be used internally in ChannelManagerConstructor to build a NetGraphMsgHandler. Other networking options are: LDKNetwork_Bitcoin, LDKNetwork_Regtest and LDKNetwork_Testnet.

Dependencies: none

Optional dependency: BestBlock, the best known block as identified by its hash and height. Recommended to retrieve the genesis block from the target network.

References: NetworkGraph Rust docs (opens new window), NetworkGraph Java bindings (opens new window), BestBlock Rust docs (opens new window), BestBlock Java bindings (opens new window)

# 5. Initialize Persist

What it's used for: persisting crucial channel data in a timely manner

Example:

Persist persister = Persist.new_impl(new Persist.PersistInterface() {
  @Override
  public Result_NoneChannelMonitorUpdateErrZ persist_new_channel(OutPoint id,
    ChannelMonitor data) {
      byte[] channel_monitor_bytes = data.write();
      // <insert code to write these bytes to disk, keyed by `id`>
  }

  @Override
  public Result_NoneChannelMonitorUpdateErrZ update_persisted_channel(
    OutPoint id, ChannelMonitorUpdate update, ChannelMonitor data) {
      byte[] channel_monitor_bytes = data.write();
      // <insert code to update the `ChannelMonitor`'s file on disk with these
      //  new bytes, keyed by `id`>
  }
});

Implementation notes: ChannelMonitors are objects which are capable of responding to on-chain events for a given channel. Thus, you will have one ChannelMonitor per channel, identified by the funding output id, above. They are persisted in real-time and the Persist methods will block progress on sending or receiving payments until they return. You must ensure that ChannelMonitors are durably persisted to disk before returning or you may lose funds.

Dependencies: none

References: Rust docs (opens new window), Java bindings (opens new window)

# 6. Initialize the EventHandler

What it's used for: (1) LDK produces 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. (2) After new event(s) are handled, the ChannelManager must be re-persisted to disk and/or backups.

Example:

class YourObj implements ChannelManagerConstructor.EventHandler {
    @Override
    public void handle_event(Event e) {
        if (e instanceof Event.FundingGenerationReady) {
            // <insert code to handle this event>
        }
        else if (e instanceof Event.PaymentReceived) {
            // <insert code to handle this event>
        }
        else if (e instanceof Event.PaymentSent) {
            // <insert code to handle this event>
        }
        else if (e instanceof Event.PaymentPathFailed) {
            // <insert code to handle this event>
        }
        else if (e instanceof Event.PendingHTLCsForwardable) {
            // <insert code to handle this event>
        }
        else if (e instanceof Event.SpendableOutputs) {
            // <insert code to handle this event>
        }
        else if (e instanceof Event.PaymentForwarded) {
            // <insert code to handle this event>
        }
        else if (e instanceof Event.ChannelClosed) {
            // <insert code to handle this event>
        }
    }

    @Override
    public void persist_manager(byte[] channel_manager_bytes) {
        // <insert code to persist channel_manager_bytes to disk and/or backups>
    }
}

ChannelManagerConstructor.EventHandler customEventHandler = new YourObj();

Dependencies: none

Implementation notes:

  • See References for an integrated example of handling LDK events (in Rust)
  • It's important to read the documentation (linked in References) for each event to make sure you satisfy the API requirements for handling it

References: Example of handling LDK events in Rust (opens new window), Rust docs for LDK events (opens new window)

# 7. Optional: Initialize the Transaction Filter

You must follow this step if: you are not providing full blocks to LDK, i.e. if you're using BIP 157/158 or Electrum as your chain backend

What it's used for: if you are not providing full blocks, LDK uses this object to tell you what transactions and outputs to watch for on-chain. You'll inform LDK about these transactions/outputs in Step 14.

Example:

Filter tx_filter = Filter.new_impl(new Filter.FilterInterface() {
    @Override
    public void register_tx(byte[] txid, byte[] script_pubkey) {
        // <insert code for you to watch for this transaction on-chain>
    }

    @Override
    public Option_C2Tuple_usizeTransactionZZ register_output(WatchedOutput output)
    {
        // <insert code for you to watch for any transactions that spend this
        //  output on-chain>
    }
});

Implementation notes: see the Blockchain Data guide for more info

Dependencies: none

References: Rust docs (opens new window), Java bindings (opens new window), Blockchain Data guide

# 8. Initialize the ChainMonitor

What it's used for: monitoring the chain for lighting transactions that are relevant to our node, and broadcasting transactions if need be

Example:

final filter = // leave this as `null` or insert the Filter object, depending on
               // what you did for Step 7
final chain_monitor = ChainMonitor.of(filter, tx_broadcaster, logger,
    fee_estimator, persister);

Dependencies: FeeEstimator, Logger, BroadcasterInterface, Persist

Optional dependency: Filter

References: Rust docs (opens new window), Java bindings (opens new window)

# 9. Initialize the KeysManager

What it's used for: providing keys for signing Lightning transactions

Example:

byte[] key_seed = new byte[32];
// <insert code to fill key_seed with random bytes OR if restarting, reload the
// seed from disk>
KeysManager keys_manager = KeysManager.of(key_seed,
    System.currentTimeMillis() / 1000,
    (int) (System.currentTimeMillis() * 1000));

Implementation notes:

  • See the Key Management guide for more info
  • Note that you must write the key_seed you give to the KeysManager on startup to disk, and keep using it to initialize the KeysManager every time you restart. This key_seed is used to derive your node's secret key (which corresponds to its node pubkey) and all other secret key material.
  • The current time is part of the KeysManager's parameters because it is used to derive random numbers from the seed where required, to ensure all random generation is unique across restarts.

Dependencies: random bytes

References: Rust docs (opens new window), Java bindings (opens new window), Key Management guide

# 10. Read ChannelMonitors from disk

What it's used for: if LDK is restarting and has at least 1 channel, its channel state will need to be read from disk and fed to the ChannelManager on the next step.

Example: reading ChannelMonitors from disk, where each ChannelMonitor's file is named after its funding outpoint:

// Initialize the array where we'll store the `ChannelMonitor`s read from disk.
final ArrayList channel_monitor_list = new ArrayList<>();

// For each monitor stored on disk, deserialize it and place it in
// `channel_monitors`.
for (... : monitor_files) {
    byte[] channel_monitor_bytes = // read the bytes from disk the same way you
                                   // wrote them in Step 5
	channel_monitor_list.add(channel_monitor_bytes);
}

// Convert the ArrayList into an array so we can pass it to
// `ChannelManagerConstructor` in Step 11.
final byte[][] channel_monitors = (byte[][])channel_monitor_list.toArray(new byte[1][]);

Dependencies: none

# 11. Initialize the ChannelManager

What it's used for: managing channel state

Example:

/* FRESH CHANNELMANAGER */

int block_height = // <insert current chain tip height>;
byte[] best_block_hash = // <insert current chain tip block hash>;
ChannelManagerConstructor channel_manager_constructor = new ChannelManagerConstructor(
  Network.LDKNetwork_Bitcoin, UserConfig.default(), best_block_hash,
  block_height, keys_manager.as_KeysInterface(), fee_estimator, chain_monitor,
  router, tx_broadcaster, logger);

/* RESTARTING CHANNELMANAGER */

byte[] serialized_channel_manager = // <insert bytes as written to disk in Step 6>
ChannelManagerConstructor channel_manager_constructor = new ChannelManagerConstructor(
  serialized_channel_manager, channel_monitors, keys_manager.as_KeysInterface(),
  fee_estimator, chain_monitor, filter, router, tx_broadcaster, logger);

final ChannelManager channel_manager = channel_manager_constructor.channel_manager;

Implementation notes: No methods should be called on ChannelManager until after Step 12.

Dependencies: KeysManager, FeeEstimator, ChainMonitor, BroadcasterInterface, Logger, and ChannelMonitors and ChannelManager bytes from Step 10 and Step 6 respectively, if restarting

Optional dependencies: Filter, NetGraphMsgHandler (can be left as null if you're not providing them)

References: Rust ChannelManager docs (opens new window), Java ChannelManager bindings (opens new window)

# 12. Sync ChannelMonitors and ChannelManager to chain tip

What it's used for: this step is only necessary if you're restarting and have open channels. This step ensures that LDK channel state is up-to-date with the bitcoin blockchain

Example: with Electrum backend

// Retrieve transaction IDs to check the chain for un-confirmation.
byte[][] relevant_txids_1 = channel_manager.as_Confirm().get_relevant_txids();
byte[][] relevant_txids_2 = chain_monitor.as_Confirm().get_relevant_txids();
byte[][] relevant_txids = ArrayUtils.addAll(relevant_txids_1, relevant_txids_2);

byte[][] unconfirmed_txids = // <insert code to find out from your chain source
                             //  if any of relevant_txids have been reorged out
                             //  of the chain>

for (byte[] txid : unconfirmed_txids) {
    channel_manager.transaction_unconfirmed(txid);
    chain_monitor.transaction_unconfirmed(txid);
}

// Retrieve transactions and outputs that were registered through the `Filter`
// interface.

// If any of these txs/outputs were confirmed on-chain, then:
byte[] header = // insert block header from the block with confirmed tx/output
int height = // insert block height of `header`
Long tx_index = // insert tx index in block
byte[] serialized_tx = // insert tx hex as byte array
TwoTuple_usizeTransactionZ tx = TwoTuple_usizeTransactionZ.of(tx_index, serialized_tx);

// Marshall all TwoTuples you built right above into an array
TwoTuple_usizeTransactionZ[] tx_list = new TwoTuple_usizeTransactionZ[]{tx, .. };

channel_manager.transactions_confirmed(header, height, tx_list);
chain_monitor.transactions_confirmed(header, height, tx_list);

byte[] best_header = // <insert code to get your best known header>
int best_height = // <insert code to get your best known block height>
channel_manager.update_best_block(best_header, best_height);
chain_monitor.update_best_block(best_header, best_height);

// Finally, tell LDK that chain sync is complete. This will also spawn several
// background threads to handle networking and event processing.
channel_manager_constructor.chain_sync_completed(customEventHandler);

Implementation notes:

  • There are 2 main options for synchronizing to chain on startup:
    • If you are connecting full blocks or using BIP 157/158: the high-level steps that must be done for both ChannelManager and each ChannelMonitor are as follows:

      1. Get the last blockhash that each object saw.
      • ChannelManager's is in channel_manager_constructor.channel_manager_latest_block_hash
      • Each ChannelMonitor's is in channel_manager_constructor.channel_monitors, as the 2nd element in each tuple
      1. For each object, if its latest known blockhash has been reorged out of the chain, then disconnect blocks using channel_manager.as_Listen().block_disconnected(..) or channel_monitor.block_disconnected(..) until you reach the last common ancestor with the main chain.
      2. For each object, reconnect blocks starting from the common ancestor until it gets to your best known chain tip using channel_manager.as_Listen().block_connected(..) and/or channel_monitor.block_connected(..).
      3. Call channel_manager_constructor.chain_sync_completed(..) to complete the initial sync process.
    • Otherwise, you can use LDK's Confirm interface as in the example above. The high-level steps are as follows:

      1. Tell LDK about relevant confirmed and unconfirmed transactions.
      2. Tell LDK what your best known block header and height is.
      3. Call channel_manager_constructor.chain_sync_completed(..) to complete the initial sync process.
  • More details about LDK's interfaces to provide chain info in Step 14.

References: Rust Listen docs (opens new window), Rust Confirm docs (opens new window)

Dependencies: ChannelManager, ChainMonitor

# 13. Optional: Bind a Listening Port

You must follow this step if: you want to listen for incoming peer connections

What it's used for: accepting incoming peer connections

Example:

final NioPeerHandler nio_peer_handler = channel_manager_constructor.nio_peer_handler;
final int port = 9735;
nio_peer_handler.bind_listener(new InetSocketAddress("0.0.0.0", port));

Dependencies: ChannelManagerConstructor (after chain_sync_completed(..) has been called on it)

References: Java NioPeerHandler sample networking module (opens new window)

# Running LDK

This section assumes you've already run all the steps in Setup.

# 14. Keep LDK Up-to-date with Chain Info

What it's used for: LDK needs to know when blocks are newly connected and disconnected and when relevant transactions are confirmed and/or reorged out.

Example:

    Implementation notes:

    • If you're using the Listen interface: blocks must be connected and disconnected in chain order
    • If you're using the Confirm interface: it's important to read the Confirm docs linked in References, to make sure you satisfy the interface's requirements

    Dependencies: ChannelManager, ChainMonitor

    References: Rust Listen docs (opens new window), Rust Confirm docs (opens new window)

    Last Updated: 11/29/2021, 3:23:07 PM