# Building a Node with LDK in Swift
# Introduction
This document covers some things you need to make a node using LDK in Swift.
- 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. Not completed (yet)
Note: LDK does not assume that safe shutdown is available, so there is no shutdown checklist.
LDK Swift (opens new window) is a set of
auto-generated decorators that call the C methods defined in lightning.h
. The
wrappers (mostly) take care of conveniences such as converting Swift types into
C types and parsing C types back into Swift.
In Bindings.swift
, various additional generic utility methods aid the
developer in passing data back and forth.
The most significant effort on the part of users of this project comes in when dealing with traits. All files within bindings/LDK/traits are meant to be interpreted as abstract classes. However, as Swift does not allow abstract classes, and using protocols would shift both implementation and boilerplate burden on developers, we instead recommend an overriding pattern, which we will describe in Phase 1.
# Setup
This guide is essentially a conversion of the Java guide for Swift.
# Phase 1: Trait Overrides
The first set of steps we need to set up is to create classes to override the
trait classes. In this example, we will simply be taking trait names such as
FeeEstimator
and create classes inheriting from them prefixed with My-
.
# FeeEstimator
First, define an inheriting class called MyFeeEstimator
:
// MyFeeEstimator.swift
import Foundation
class MyFeeEstimator: FeeEstimator {
override func get_est_sat_per_1000_weight(confirmation_target: LDKConfirmationTarget) -> UInt32 {
return 253
}
}
Second, somewhere within the app initialization context, e.g. the app delegate's
didFinishLaunchingWithOptions
method, instantiate the fee estimator:
// main context
let feeEstimator = MyFeeEstimator()
# Logger
Define the inheriting class:
// MyLogger.swift
import Foundation
class MyLogger: Logger {
override func log(record: Record) {
print("log event: \(record.get_args())")
}
}
Instantiate the value:
// main context (continued)
let logger = MyLogger()
# BroadcasterInterface
Define the subclass:
// MyBroadcasterInterface.swift
import Foundation
class MyBroadcasterInterface: BroadcasterInterface {
override func broadcast_transaction(tx: [UInt8]) {
// insert code to broadcast transaction
}
}
Instantiate it:
// main context (continued)
let broadcaster = MyBroadcasterInterface()
# Persist
Define the subclass:
// MyPersister.swift
import Foundation
class MyPersister: Persist {
override func persist_new_channel(channel_id: OutPoint, data: ChannelMonitor, update_id: MonitorUpdateId) -> Result_NoneChannelMonitorUpdateErrZ {
let idBytes: [UInt8] = channel_id.write()
let monitorBytes: [UInt8] = data.write()
// persist monitorBytes to disk, keyed by idBytes
return Result_NoneChannelMonitorUpdateErrZ.ok()
}
override func update_persisted_channel(channel_id: OutPoint, update: ChannelMonitorUpdate, data: ChannelMonitor, update_id: MonitorUpdateId) -> Result_NoneChannelMonitorUpdateErrZ {
let idBytes: [UInt8] = channel_id.write()
let monitorBytes: [UInt8] = data.write()
// modify persisted monitorBytes keyed by idBytes on disk
return Result_NoneChannelMonitorUpdateErrZ.ok()
}
}
Instantiate it:
// main context (continued)
let persister = MyPersister()
# Filter
Define the subclass:
// MyFilter.swift
import Foundation
class MyFilter: Filter {
override func register_tx(txid: [UInt8]?, script_pubkey: [UInt8]) {
// watch this transaction on-chain
}
override func register_output(output: WatchedOutput) -> Option_C2Tuple_usizeTransactionZZ {
let scriptPubkeyBytes = output.get_script_pubkey()
let outpoint = output.get_outpoint()!
let txid = outpoint.get_txid()
let outputIndex = outpoint.get_index()
// watch for any transactions that spend this output on-chain
let blockHashBytes = output.get_block_hash()
// if block hash bytes are not null, return any transaction spending the output that is found in the corresponding block along with its index
return Option_C2Tuple_usizeTransactionZZ.none()
}
}
Instantiate it:
// main context (continued)
let filter = MyFilter()
# Phase 2: Initializations
# ChainMonitor
// main context (continued)
let filterOption = Option_FilterZ(value: filter)
let chainMonitor = ChainMonitor(chain_source: filterOption.dangle(), broadcaster: broadcaster, logger: logger, feeest: feeEstimator, persister: persister)
# KeysManager
// main context (continued)
var keyData = Data(count: 32)
keyData.withUnsafeMutableBytes {
// returns 0 on success
let didCopySucceed = SecRandomCopyBytes(kSecRandomDefault, 32, $0.baseAddress!)
assert(didCopySucceed == 0)
}
let seed = [UInt8](keyData)
let timestamp_seconds = UInt64(NSDate().timeIntervalSince1970)
let timestamp_nanos = UInt32.init(truncating: NSNumber(value: timestamp_seconds * 1000 * 1000))
let keysManager = KeysManager(seed: seed, starting_time_secs: timestamp_seconds, starting_time_nanos: timestamp_nanos)
let keysInterface = keysManager.as_KeysInterface()
We will keep needing to pass around a keysInterface instance, and we will also need to pass its node secret to the peer manager initialization, so let's prepare it right here:
let keysInterface = keysManager.as_KeysInterface()
let nodeSecret = self.keysInterface.get_node_secret()
This is a bit inelegant, but we will be providing simpler casting methods for user-provided types shortly.
# ChannelManager
To instantiate the channel manager, we need a couple of minor prerequisites.
First, we need the current block height and hash. For the sake of this example, we'll use a random block at a height that does not exist at the time of this writing.
let latestBlockHash = [UInt8](Data(base64Encoded: "AAAAAAAAAAAABe5Xh25D12zkQuLAJQbBeLoF1tEQqR8=")!)
let latestBlockHeight = 700123
Second, we also need to initialize a default user config, which we do like this:
let userConfig = UserConfig()
Finally, we can proceed by instantiating the ChannelManager
using
ChannelManagerConstructor
.
// main context (continued)
let channelManagerConstructor = ChannelManagerConstructor(
network: LDKNetwork_Bitcoin,
config: userConfig,
current_blockchain_tip_hash: latestBlockHash,
current_blockchain_tip_height: latestBlockHeight,
keys_interface: keysInterface,
fee_estimator: feeEstimator,
chain_monitor: chainMonitor,
net_graph: nil, // see `NetworkGraph`
tx_broadcaster: broadcaster,
logger: logger
)
let channelManager = channelManagerConstructor.channelManager
# NetworkGraph
If you intend to use the LDK's built-in routing algorithm, you will need to
instantiate a NetworkGraph
that can later be passed to the
ChannelManagerConstructor
:
// main context (continued)
let networkGraph = NetworkGraph(genesis_hash: [UInt8](Data(base64Encoded: "AAAAAAAZ1micCFrhZYMek0/3Y65GoqbBcrPxtgqM4m8=")!))
Note that a network graph instance needs to be provided upon initialization, which in turn requires the genesis block hash.
# Serializing and restoring a ChannelManager
If you need to serialize a channel manager, you can call its write method on itself:
let serializedChannelManager: [UInt8] = channelManager.write(obj: channelManager)
If you have a channel manager you previously serialized, you can restore it like this:
let serializedChannelManager: [UInt8] = [2, 1, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247, 79, 147, 30, 131, 101, 225, 90, 8, 156, 104, 214, 25, 0, 0, 0, 0, 0, 0, 10, 174, 219, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 238, 87, 135, 110, 67, 215, 108, 228, 66, 226, 192, 37, 6, 193, 120, 186, 5, 214, 209, 16, 169, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // <insert bytes you would have written in following the later step "Persist channel manager">
let serializedChannelMonitors: [[UInt8]] = []
let channelManagerConstructor = try ChannelManagerConstructor(
channel_manager_serialized: serializedChannelManager,
channel_monitors_serialized: serializedChannelMonitors,
keys_interface: keysInterface,
fee_estimator: feeEstimator,
chain_monitor: chainMonitor,
filter: filter,
net_graph: nil, // or networkGraph
tx_broadcaster: broadcaster,
logger: logger
)
let channelManager = channelManagerConstructor.channelManager
# PeerHandler
Finally, let's get the peer handler. It has conveniently already been
instantiated by the ChannelManagerConstructor
.
// main context (continued)
let peerManager = channelManagerConstructor.peerManager
Now, all that remains is setting up the actual syscalls that are necessary within the host environment, and route them to the appropriate LDK objects.