cli + ldk works; dumb rpc server

This commit is contained in:
Overtorment 2021-10-25 20:24:02 +01:00
parent 96f7fc9590
commit 8dd49ac839
No known key found for this signature in database
GPG Key ID: AB15F43F78CCBC06
81 changed files with 517 additions and 20 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
build
out
.gradle

View File

@ -1,2 +0,0 @@
#Sat Oct 23 18:56:06 BST 2021
gradle.version=7.1

Binary file not shown.

View File

@ -1,16 +1,13 @@
<component name="ArtifactManager">
<artifact type="jar" name="hello:jar">
<output-path>$PROJECT_DIR$/out/artifacts/hello_jar</output-path>
<root id="archive" name="hello.jar">
<artifact type="jar" name="hello.main:jar">
<output-path>$PROJECT_DIR$/out/artifacts/hello_main_jar</output-path>
<root id="archive" name="hello.main.jar">
<element id="module-output" name="hello.main" />
<element id="extracted-dir" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/junit/junit/4.12/2973d150c0dc1fefe998f834810d68f278ea58ec/junit-4.12.jar" path-in-jar="/" />
<element id="extracted-dir" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-core/1.3/42a25dc3219429f0e5d060061f71acb49bf010a0/hamcrest-core-1.3.jar" path-in-jar="/" />
<element id="extracted-dir" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk7/1.5.10/c49d0703d16c6cb1526cc07b9b46486da1dd8a60/kotlin-stdlib-jdk7-1.5.10.jar" path-in-jar="/" />
<element id="extracted-dir" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.5.10/da6a904b132f0402fa4d79169a3c1770598d4702/kotlin-stdlib-1.5.10.jar" path-in-jar="/" />
<element id="extracted-dir" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test/1.5.10/ebefbbeba6915b14d36a6c7f1cf30146af7d8165/kotlin-test-1.5.10.jar" path-in-jar="/" />
<element id="extracted-dir" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk7/1.5.10/c49d0703d16c6cb1526cc07b9b46486da1dd8a60/kotlin-stdlib-jdk7-1.5.10.jar" path-in-jar="/" />
<element id="extracted-dir" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.5.10/6b84d926e28493be69daf673e40076f89492ef7/kotlin-stdlib-common-1.5.10.jar" path-in-jar="/" />
<element id="extracted-dir" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.5.10/3f4af7aff21c4ec46e3cdd645639d0a63a68d3d0/kotlin-stdlib-jdk8-1.5.10.jar" path-in-jar="/" />
<element id="extracted-dir" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-junit/1.5.10/7f3ea6eb4b81817074ad414fc7dafed7aaf8e97d/kotlin-test-junit-1.5.10.jar" path-in-jar="/" />
<element id="extracted-dir" path="$PROJECT_DIR$/libs/ldk-java.jar" path-in-jar="/" />
<element id="extracted-dir" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/13.0/919f0dfe192fb4e063e7dacadee7f8bb9a2672a9/annotations-13.0.jar" path-in-jar="/" />
</root>
</artifact>

3
.idea/gradle.xml generated
View File

@ -3,8 +3,9 @@
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="WRAPPED" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />

2
.idea/misc.xml generated
View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -14,6 +14,7 @@ repositories {
dependencies {
testImplementation(kotlin("test"))
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
}
tasks.test {

BIN
libs/ldk-java.jar Normal file

Binary file not shown.

3
run.sh Executable file
View File

@ -0,0 +1,3 @@
echo 'start' | timeout 2 nc localhost 8310 -i 1
echo 'connect 037cc5f9f1da20ac0d60e83989729a204a33cc2d8e80438969fadf35c1c5f1233b 165.227.103.83 9735' | timeout 2 nc localhost 8310 -i 1

View File

@ -1,7 +1,497 @@
fun main(args: Array<String>) {
println("Hello World!")
import org.ldk.batteries.ChannelManagerConstructor
import org.ldk.batteries.ChannelManagerConstructor.EventHandler
import org.ldk.batteries.NioPeerHandler
import org.ldk.enums.ConfirmationTarget
import org.ldk.enums.Network
import org.ldk.structs.*
import org.ldk.structs.FeeEstimator
import org.ldk.structs.Filter.FilterInterface
import org.ldk.structs.Persist
import org.ldk.structs.Persist.PersistInterface
import java.io.IOException
import java.net.InetSocketAddress
// Try adding program arguments via Run/Debug configuration.
// Learn more about running applications: https://www.jetbrains.com/help/idea/running-applications.html.
println("Program arguments: ${args.joinToString()}")
}
import java.io.OutputStream
import java.net.ServerSocket
import java.net.Socket
import java.nio.charset.Charset
import java.util.*
import kotlin.concurrent.thread
// borrowed from JS:
const val MARKER_LOG = "log";
const val MARKER_REGISTER_OUTPUT = "marker_register_output";
const val MARKER_REGISTER_TX = "register_tx";
const val MARKER_BROADCAST = "broadcast";
const val MARKER_PERSIST = "persist";
const val MARKER_PAYMENT_SENT = "payment_sent";
const val MARKER_PAYMENT_FAILED = "payment_failed";
const val MARKER_PAYMENT_RECEIVED = "payment_received";
const val MARKER_PERSIST_MANAGER = "persist_manager";
const val MARKER_FUNDING_GENERATION_READY = "funding_generation_ready";
const val MARKER_CHANNEL_CLOSED = "channel_closed";
//
var feerate_fast = 7500; // estimate fee rate in BTC/kB
var feerate_medium = 7500; // estimate fee rate in BTC/kB
var feerate_slow = 7500; // estimate fee rate in BTC/kB
var refund_address_script = "76a91419129d53e6319baf19dba059bead166df90ab8f588ac";
var nio_peer_handler: NioPeerHandler? = null;
var channel_manager: ChannelManager? = null;
var peer_manager: PeerManager? = null;
var chain_monitor: ChainMonitor? = null;
var temporary_channel_id: ByteArray? = null;
var keys_manager: KeysManager? = null;
var channel_manager_constructor: ChannelManagerConstructor? = null;
fun main(args: Array<String>) {
println("Hello Lightning!")
val server = ServerSocket(8310)
println("Server is running on port ${server.localPort}")
while (true) {
val client = server.accept()
println("Client connected: ${client.inetAddress.hostAddress}")
// Run client in it's own thread.
thread { ClientHandler(client).run() }
}
}
fun start(
entropyHex: String,
blockchainTipHeight: Int,
blockchainTipHashHex: String,
serializedChannelManagerHex: String,
monitorHexes: String
) {
println("ReactNativeLDK: " + "start")
// INITIALIZE THE FEEESTIMATOR #################################################################
// What it's used for: estimating fees for on-chain transactions that LDK wants broadcasted.
val fee_estimator = FeeEstimator.new_impl { confirmation_target: ConfirmationTarget? ->
var ret = feerate_fast;
if (confirmation_target != null) {
if (confirmation_target.equals(ConfirmationTarget.LDKConfirmationTarget_HighPriority)) ret = feerate_fast;
if (confirmation_target.equals(ConfirmationTarget.LDKConfirmationTarget_Normal)) ret = feerate_medium;
if (confirmation_target.equals(ConfirmationTarget.LDKConfirmationTarget_Background)) ret = feerate_slow;
}
return@new_impl ret;
}
// INITIALIZE THE LOGGER #######################################################################
// What it's used for: LDK logging
val logger = Logger.new_impl { arg: String? ->
println("ReactNativeLDK: " + arg)
// val params = Arguments.createMap()
// params.putString("line", arg)
// sendEvent(MARKER_LOG, params)
}
// INITIALIZE THE BROADCASTERINTERFACE #########################################################
// What it's used for: broadcasting various lightning transactions
val tx_broadcaster = BroadcasterInterface.new_impl { tx ->
println("ReactNativeLDK: " + "broadcaster sends an event asking to broadcast some txhex...")
// val params = Arguments.createMap()
// params.putString("txhex", byteArrayToHex(tx))
// sendEvent(MARKER_BROADCAST, params)
}
// INITIALIZE PERSIST ##########################################################################
// What it's used for: persisting crucial channel data in a timely manner
val persister = Persist.new_impl(object : PersistInterface {
override fun persist_new_channel(id: OutPoint, data: ChannelMonitor): Result_NoneChannelMonitorUpdateErrZ {
val channel_monitor_bytes = data.write()
println("ReactNativeLDK: persist_new_channel")
// val params = Arguments.createMap()
// params.putString("id", byteArrayToHex(id.to_channel_id()))
// params.putString("data", byteArrayToHex(channel_monitor_bytes))
// sendEvent(MARKER_PERSIST, params);
return Result_NoneChannelMonitorUpdateErrZ.ok();
}
override fun update_persisted_channel(
id: OutPoint,
update: ChannelMonitorUpdate,
data: ChannelMonitor
): Result_NoneChannelMonitorUpdateErrZ {
val channel_monitor_bytes = data.write()
println("ReactNativeLDK: update_persisted_channel");
// val params = Arguments.createMap()
// params.putString("id", byteArrayToHex(id.to_channel_id()))
// params.putString("data", byteArrayToHex(channel_monitor_bytes))
// sendEvent(MARKER_PERSIST, params);
return Result_NoneChannelMonitorUpdateErrZ.ok();
}
})
// now, initializing channel manager persister that is responsoble for backing up channel_manager bytes
val channel_manager_persister = object : EventHandler {
override fun handle_event(event: Event) {
handleEvent(event);
}
override fun persist_manager(channel_manager_bytes: ByteArray?) {
if (channel_manager_bytes != null) {
// val params = Arguments.createMap()
// params.putString("channel_manager_bytes", byteArrayToHex(channel_manager_bytes))
// sendEvent(MARKER_PERSIST_MANAGER, params);
}
}
}
// INITIALIZE THE CHAINMONITOR #################################################################
// 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
// Filter 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.
val tx_filter: Filter? = Filter.new_impl(object : FilterInterface {
override fun register_tx(txid: ByteArray, script_pubkey: ByteArray) {
println("ReactNativeLDK: register_tx");
// val params = Arguments.createMap()
// params.putString("txid", byteArrayToHex(txid))
// params.putString("script_pubkey", byteArrayToHex(script_pubkey))
// sendEvent(MARKER_REGISTER_TX, params);
}
override fun register_output(output: WatchedOutput): Option_C2Tuple_usizeTransactionZZ {
println("ReactNativeLDK: register_output");
// val params = Arguments.createMap()
// val blockHash = output._block_hash;
// if (blockHash is ByteArray) {
// params.putString("block_hash", byteArrayToHex(blockHash))
// }
// params.putString("index", output._outpoint._index.toString())
// params.putString("script_pubkey", byteArrayToHex(output._script_pubkey))
// sendEvent(MARKER_REGISTER_OUTPUT, params);
return Option_C2Tuple_usizeTransactionZZ.none();
}
})
val filter = Option_FilterZ.some(tx_filter);
System.out.println(org.ldk.impl.version.get_ldk_java_bindings_version() + ", " + org.ldk.impl.bindings.get_ldk_c_bindings_version() + ", " + org.ldk.impl.bindings.get_ldk_version());
chain_monitor = ChainMonitor.of(filter, tx_broadcaster, logger, fee_estimator, persister);
// INITIALIZE THE KEYSMANAGER ##################################################################
// What it's used for: providing keys for signing lightning transactions
keys_manager = KeysManager.of(
hexStringToByteArray(entropyHex),
System.currentTimeMillis() / 1000,
(System.currentTimeMillis() * 1000).toInt()
)
// READ CHANNELMONITOR STATE FROM DISK #########################################################
// Initialize the hashmap where we'll store the `ChannelMonitor`s read from disk.
// This hashmap will later be given to the `ChannelManager` on initialization.
var channelMonitors = arrayOf<ByteArray>();
if (monitorHexes != "") {
println("ReactNativeLDK: initing channel monitors...");
val channelMonitorHexes = monitorHexes.split(",").toTypedArray();
val channel_monitor_list = ArrayList<ByteArray>()
channelMonitorHexes.iterator().forEach {
val channel_monitor_bytes = hexStringToByteArray(it);
channel_monitor_list.add(channel_monitor_bytes);
}
channelMonitors = channel_monitor_list.toTypedArray();
}
// INITIALIZE THE CHANNELMANAGER ###############################################################
// What it's used for: managing channel state
try {
if (serializedChannelManagerHex != "") {
// loading from disk
channel_manager_constructor = ChannelManagerConstructor(
hexStringToByteArray(serializedChannelManagerHex),
channelMonitors,
keys_manager?.as_KeysInterface(),
fee_estimator,
chain_monitor,
tx_filter,
null,
tx_broadcaster,
logger
);
channel_manager = channel_manager_constructor!!.channel_manager;
channel_manager_constructor!!.chain_sync_completed(channel_manager_persister);
peer_manager = channel_manager_constructor!!.peer_manager;
nio_peer_handler = channel_manager_constructor!!.nio_peer_handler;
} else {
// fresh start
channel_manager_constructor = ChannelManagerConstructor(
Network.LDKNetwork_Bitcoin,
UserConfig.with_default(),
hexStringToByteArray(blockchainTipHashHex),
blockchainTipHeight,
keys_manager?.as_KeysInterface(),
fee_estimator,
chain_monitor,
null,
tx_broadcaster,
logger
);
channel_manager = channel_manager_constructor!!.channel_manager;
channel_manager_constructor!!.chain_sync_completed(channel_manager_persister);
peer_manager = channel_manager_constructor!!.peer_manager;
nio_peer_handler = channel_manager_constructor!!.nio_peer_handler;
}
// promise.resolve("hello ldk");
} catch (e: Exception) {
println("ReactNativeLDK: can't start, " + e.message);
// promise.reject(e.message);
}
}
fun hexStringToByteArray(strArg: String): ByteArray {
val HEX_CHARS = "0123456789ABCDEF"
val str = strArg.toUpperCase();
if (str.length % 2 != 0) return hexStringToByteArray("");
val result = ByteArray(str.length / 2)
for (i in 0 until str.length step 2) {
val firstIndex = HEX_CHARS.indexOf(str[i]);
val secondIndex = HEX_CHARS.indexOf(str[i + 1]);
val octet = firstIndex.shl(4).or(secondIndex)
result.set(i.shr(1), octet.toByte())
}
return result
}
fun byteArrayToHex(bytesArg: ByteArray): String {
return bytesArg.joinToString("") { String.format("%02X", (it.toInt() and 0xFF)) }.toLowerCase()
}
fun sendEvent(eventName: String) {
// nop
}
fun handleEvent(event: Event) {
if (event is Event.SpendableOutputs) {
println("ReactNativeLDK: " + "trying to spend output");
val txResult = keys_manager?.spend_spendable_outputs(
event.outputs,
emptyArray<TxOut>(),
hexStringToByteArray(refund_address_script),
feerate_fast
);
if (txResult is Result_TransactionNoneZ.Result_TransactionNoneZ_OK) {
// success building the transaction, passing it to outer code to broadcast
// val params = Arguments.createMap();
// params.putString("txhex", byteArrayToHex(txResult.res))
// this.sendEvent(MARKER_BROADCAST, params)
}
}
if (event is Event.PaymentSent) {
println("ReactNativeLDK: " + "payment sent, preimage: " + byteArrayToHex((event as Event.PaymentSent).payment_preimage));
// val params = Arguments.createMap();
// params.putString("payment_preimage", byteArrayToHex((event as Event.PaymentSent).payment_preimage));
// this.sendEvent(MARKER_PAYMENT_SENT, params);
}
if (event is Event.PaymentPathFailed) {
println("ReactNativeLDK: " + "payment failed, payment_hash: " + byteArrayToHex(event.payment_hash));
// val params = Arguments.createMap();
// params.putString("payment_hash", byteArrayToHex(event.payment_hash));
// params.putString("rejected_by_dest", event.rejected_by_dest.toString());
// this.sendEvent(MARKER_PAYMENT_FAILED, params);
}
if (event is Event.PaymentReceived) {
println("ReactNativeLDK: " + "payment received, payment_hash: " + byteArrayToHex(event.payment_hash));
var paymentPreimage: ByteArray? = null;
var paymentSecret: ByteArray? = null;
if (event.purpose is PaymentPurpose.InvoicePayment) {
paymentPreimage = (event.purpose as PaymentPurpose.InvoicePayment).payment_preimage;
paymentSecret = (event.purpose as PaymentPurpose.InvoicePayment).payment_secret;
channel_manager?.claim_funds(paymentPreimage);
} else if (event.purpose is PaymentPurpose.SpontaneousPayment) {
paymentPreimage = (event.purpose as PaymentPurpose.SpontaneousPayment).spontaneous_payment;
channel_manager?.claim_funds(paymentPreimage);
}
// val params = Arguments.createMap();
// params.putString("payment_hash", byteArrayToHex(event.payment_hash));
// if (paymentSecret != null) {
// params.putString("payment_secret", byteArrayToHex(paymentSecret));
// }
// if (paymentPreimage != null) {
// params.putString("payment_preimage", byteArrayToHex(paymentPreimage));
// }
// params.putString("amt", event.amt.toString());
// this.sendEvent(MARKER_PAYMENT_RECEIVED, params);
}
if (event is Event.PendingHTLCsForwardable) {
channel_manager?.process_pending_htlc_forwards();
}
if (event is Event.FundingGenerationReady) {
println("ReactNativeLDK: " + "FundingGenerationReady");
val funding_spk = event.output_script;
if (funding_spk.size == 34 && funding_spk[0].toInt() == 0 && funding_spk[1].toInt() == 32) {
// val params = Arguments.createMap();
// params.putString("channel_value_satoshis", event.channel_value_satoshis.toString());
// params.putString("output_script", byteArrayToHex(event.output_script));
// params.putString("temporary_channel_id", byteArrayToHex(event.temporary_channel_id));
// params.putString("user_channel_id", event.user_channel_id.toString());
temporary_channel_id = event.temporary_channel_id;
// this.sendEvent(MARKER_FUNDING_GENERATION_READY, params);
}
}
if (event is Event.PaymentForwarded) {
// todo. one day, when ldk is a full routing node...
}
if (event is Event.ChannelClosed) {
println("ReactNativeLDK: " + "ChannelClosed");
// val params = Arguments.createMap();
// val reason = event.reason;
// params.putString("channel_id", byteArrayToHex(event.channel_id));
// params.putString("user_channel_id", event.user_channel_id.toString());
// if (reason is ClosureReason.CommitmentTxConfirmed) {
// params.putString("reason", "CommitmentTxConfirmed");
// }
// if (reason is ClosureReason.CooperativeClosure) {
// params.putString("reason", "CooperativeClosure");
// }
// if (reason is ClosureReason.CounterpartyForceClosed) {
// params.putString("reason", "CounterpartyForceClosed");
// params.putString("text", reason.peer_msg);
// }
// if (reason is ClosureReason.DisconnectedPeer) {
// params.putString("reason", "DisconnectedPeer");
// }
// if (reason is ClosureReason.HolderForceClosed) {
// params.putString("reason", "HolderForceClosed");
// }
// if (reason is ClosureReason.OutdatedChannelManager) {
// params.putString("reason", "OutdatedChannelManager");
// }
// if (reason is ClosureReason.ProcessingError) {
// params.putString("reason", "ProcessingError");
// params.putString("text", reason.err);
// }
// this.sendEvent(MARKER_CHANNEL_CLOSED, params);
}
}
fun connectPeer(pubkeyHex: String, hostname: String, port: Int) {
println("ReactNativeLDK: connecting to peer " + pubkeyHex);
try {
nio_peer_handler?.connect(hexStringToByteArray(pubkeyHex), InetSocketAddress(hostname, port), 9000);
} catch (e: IOException) {
}
}
class ClientHandler(client: Socket) {
private val client: Socket = client
private val reader: Scanner = Scanner(client.getInputStream())
private val writer: OutputStream = client.getOutputStream()
private val executor: Executor = Executor()
private var running: Boolean = false
fun run() {
running = true
// Welcome message
write(
"Welcome to the Hello Lightning server!\n" +
"To exit, write: 'EXIT'.\n" +
"Available commands: 'start', 'connect', 'ldkversion'"
)
while (running) {
try {
val text = reader.nextLine()
if (text == "EXIT") {
shutdown()
continue
}
val values = text.split(' ')
val result = executor.execute(
values[0],
values.elementAtOrNull(1),
values.elementAtOrNull(2),
values.elementAtOrNull(3)
)
write(result)
} catch (ex: Exception) {
// TODO: Implement exception handling
println("Exception handling command:" + ex.message);
shutdown()
} finally {
}
}
}
private fun write(message: String) {
writer.write((message + '\n').toByteArray(Charset.defaultCharset()))
}
private fun shutdown() {
running = false
client.close()
println("${client.inetAddress.hostAddress} closed the connection")
}
}
class Executor {
fun execute(command: String, arg1: String?, arg2: String?, arg3: String?, arg4: String? = null, arg5: String? = null): String {
when (command) {
"start" -> {
println("starting LDK...");
start(
"00000000000000000000000000000000000000000000000000000000000000f6",
706619,
"0000000000000000000162f379c00029d72b99d2d1ea0fe95b9563c0d2898894",
"",
""
);
// connectPeer("037cc5f9f1da20ac0d60e83989729a204a33cc2d8e80438969fadf35c1c5f1233b", "165.227.103.83", 9735)
return "ok";
}
"ldkversion" -> return (org.ldk.impl.version.get_ldk_java_bindings_version() + ", " + org.ldk.impl.bindings.get_ldk_c_bindings_version() + ", " + org.ldk.impl.bindings.get_ldk_version())
"connect" -> {
if (arg1 != null && arg2 != null && arg3 != null) {
connectPeer(arg1, arg2, arg3.toInt())
return "ok";
} else {
return "Something whent wrong"
}
}
else -> {
return "Something whent wrong"
}
}
}
}