Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(lightning): Add Lightning as transport #85

Open
wants to merge 14 commits into
base: master
Choose a base branch
from

Conversation

mariocynicys
Copy link
Collaborator

@mariocynicys mariocynicys commented Aug 2, 2022

This PR introduces bolt13 message structs and their lightning (de)serialization, to be used by the tower (for the lightning server) and client apps.

LDK's (de)serialization traits are used to make the message structs sendable over wire (Readable and Writeable).

A small customized macro-based serialization framework similar to LDK's was added to help organize the code and reduce its size.

Will push the lightning server to this PR later on.

Addresses #31

Comment on lines 317 to 319
// Initialize the fields we will read from. An unused_assignment happens here.
$(init_tlv_field_var!($field_name, $fieldty);)*
$($field_name = $field;)*
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be replaced with

$(let $field_name = $field;)*

and remove #[allow(unused_assignments)].

Comment on lines 289 to 290
let v = vec![1_u8, 2, 3, 6];
test_encode_decode_tlv!(1, v.len(), v, vec);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test other vector types (e.g. Vec<u16>)

// Allow unreachable patterns which happen when we have some non-unique tlv types.
#[allow(unreachable_patterns)]
// Runs `test_encode_decode_tlv_stream` inside a closure so that we don't need to
// return a `Result<(), DecodeError` from the test functions.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing >

Comment on lines 35 to 36
writer.write_all(&self.to_vec())?;
Ok(())
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplify to

writer.write_all(&self.to_vec())

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed

fn test_msg<T: Debug + Readable + Writeable + PartialEq>(msg: T) {
// Get a writer and write the message to it.
let mut stream = TestVecWriter(Vec::new());
msg.write(&mut stream).ok().unwrap();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drop .ok().

msg.write(&mut stream).ok().unwrap();
// Create a reader out of the written buffer.
let mut stream = Cursor::new(stream.0);
let read_msg: T = Readable::read(&mut stream).ok().unwrap();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drop .ok().

*/

macro_rules! encode_tlv {
($stream: expr, $type: expr, $field: expr, opt) => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In std and other crates, they leave no space between the macro variable and its type: $stream:expr.

/// Consume the remaining bytes.
#[inline]
pub fn eat_remaining(&mut self) -> Result<(), DecodeError> {
copy(self, &mut sink()).unwrap();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe tolerate this unwrap here and map to a decode error instead.

@sr-gi sr-gi self-assigned this Aug 8, 2022
@sr-gi sr-gi added feature request New feature or request Summer of Bitcoin Summer of Bitcoin 2023 projects Seeking Code Review review me pls labels Aug 8, 2022
@sr-gi
Copy link
Member

sr-gi commented Sep 17, 2022

Sorry I haven't taken the time to review this yet. Will do as soon as v0.1.2 gets released.

Copy link
Member

@sr-gi sr-gi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First pass. Not super familiar with Rust macros, so more like an Concept / Approach ACK than a thorough review.

use crate::cryptography::get_random_bytes;
use bitcoin::hashes::Hash;
use bitcoin::Txid;
pub use lightning::util::test_utils::TestVecWriter;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is imported here but not use here. I'm guessing this is because you're grouping it as part of the serialization tools and then re-importing it in messages.rs and ser_macros.rs.

Not against it but just wanted to verify that's the approach.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing this is because you're grouping it as part of the serialization tools and then re-importing it in messages.rs and ser_macros.rs

Yup.


use crate::appointment::Locator;
use crate::lightning::ser_macros::{impl_writeable_msg, set_msg_type};
use bitcoin::secp256k1::PublicKey;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: line break to separate crate stuff from btc/ln stuff

/// The register message sent by the user to subscribe for the watching service.
#[derive(Debug)]
pub struct Register {
pub pubkey: PublicKey,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be user_id: UserId

pub struct SubscriptionDetails {
pub appointment_max_size: u16,
pub start_block: u32,
pub amount_msat: u32,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't be necessary. Payment methods can go in the TLV fields (as happens with the invoice), so we can assume that the service is altruistic if there is no amount_msat. In case there is, then this value would be redundant.

#[derive(Debug)]
pub struct AddUpdateAppointment {
pub locator: Locator,
// NOTE: LDK will prefix varying size fields (e.g. vectors and strings) with their length.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the length fields in BOLT13 are so it can be implemented in different langs properly. Don't thing this annotation is neeced.

Comment on lines 71 to 85
/// The appointment data message sent by the tower after a get appointment message.
#[derive(Debug)]
pub struct AppointmentData {
pub locator: Locator,
pub encrypted_blob: Vec<u8>,
}

/// The tracker data message sent by the tower when the requested appointment has been acted upon.
#[derive(Debug)]
pub struct TrackerData {
pub dispute_txid: Txid,
pub penalty_txid: Txid,
pub penalty_rawtx: Vec<u8>,
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BOLT-wise this should be two different messages right? Have you put any thought on how this would look like?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BOLT-wise this should be two different messages right?

Indeed they are.

Have you put any thought on how this would look like?

Do you mean in terms of return types of methods and the typing stuff in Rust?
They are both TowerMessage at the end.

Comment on lines 35 to 36
writer.write_all(&self.to_vec())?;
Ok(())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed

Copy link
Member

@sr-gi sr-gi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second pass. This should be more thorough. Haven't checked test yet.

ser_macros.rs is based on lightning::util::ser_macros.rs but it significantly deviates from it. Is there any good reasoning behind it? Has lightning::util::ser_macros.rs been updated since you wrote this?

As a general question, I saw you had some discussion regarding some of the serialization functionality being exposed in lightning. Is that still on track? What parts of this would be exported and how would that affect this part of the codebase?

Ok(None) => {}
// If we failed to read any bytes at all, we reached the end of our TLV
// stream and have simply exhausted all entries.
Err(e) if e == DecodeError::ShortRead && !track_read.have_read => break,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Err(e) if e == DecodeError::ShortRead && !track_read.have_read => break,
Err(ref e) if e == &DecodeError::ShortRead && !track_read.have_read => break,

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this more ergonomic or it has performance impact?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It implies one less allocation AFAICT

impl<'a, T: Writeable> Writeable for LightningVecWriter<'a, T> {
#[inline]
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), Error> {
for item in self.0.iter() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for item in self.0.iter() {
for ref item in self.0.iter() {

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this as well?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, iter actually returns an iterator of references over the original data.
So no extra allocations on this one.

Comment on lines 27 to 28
let locator = Self::from_slice(&buf).map_err(|_| DecodeError::InvalidValue)?;
Ok(locator)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let locator = Self::from_slice(&buf).map_err(|_| DecodeError::InvalidValue)?;
Ok(locator)
Self::from_slice(&buf).map_err(|_| DecodeError::InvalidValue)

}
}

#[cfg(test)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason why this is test only? Also, why not derive this for the messages? Is it so you can test it generally within ser_macros.rs?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't need to impl it at the beginning for usual functionality, and saw LDK also doesn't implement it for their messages. But then I needed it for tests, that's why I made it test only.

ser_macros wouldn't use it because it tests primitive types which already implement Eq. Generally, tests in ser_macros don't test impl_writeable_msg but test the macros used inside of it.

Comment on lines +179 to +191
set_msg_type!(Register, 48848);
set_msg_type!(SubscriptionDetails, 48850);
set_msg_type!(AddUpdateAppointment, 48852);
set_msg_type!(AppointmentAccepted, 48854);
set_msg_type!(AppointmentRejected, 48856);
set_msg_type!(GetAppointment, 48858);
set_msg_type!(AppointmentData, 48860);
set_msg_type!(TrackerData, 48862);
set_msg_type!(AppointmentNotFound, 48864);
// Let these messages get odd types since they are auxiliary messages.
set_msg_type!(GetSubscriptionInfo, 48865);
set_msg_type!(SubscriptionInfo, 48867);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reasoning behind the values taken for the message types? (apart from them being part of the custom message range and being even/odd depending on whether they are required or not)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not specifically, just random numbers as placeholders.

pub trait Type {
/// The type identifying the message payload.
const TYPE: u16;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mas to be a trait? How does LDK implement this for the standard messages?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might work as something else, but I just followed LDK on this one. They have that trait Encode which does the some thing. Encode is a little bit of a weird name, but I think they named it that way because they have another trait already called Type.

@mariocynicys
Copy link
Collaborator Author

@sr-gi

ser_macros.rs is based on lightning::util::ser_macros.rs but it significantly deviates from it. Is there any good reasoning behind it? Has lightning::util::ser_macros.rs been updated since you wrote this?

I mainly wanted impl_writeable_msg! to be exposed (and of course any macros called inside of it) because it handles all the TLV stuff, but that wasn't what LDK team wanted. They wanted to expose more of the utility macros instead (e.g. encode_tlv_stream!) which then the users could use to craft their things.

Not a big deal too. Exposing the utility macros would do fine. But after going on with BOLT13, some TLV datatypes there weren't covered by LDK's macro system (like vectors and strings) as they don't use it internally nor that it is used in other BOLTs, which led me to make some tweaks to my copy till it deviated that much.

You can also find other parts trimmed out (like check_tlv_order!, check_missing_tlv!, and default macro arm option), this is mainly because these parts weren't used in BOLT13 (and I think not even in other BOLTs, I think they mainly use it for internal messaging stuff).

As a general question, I saw you had some discussion regarding some of the serialization functionality being exposed in lightning. Is that still on track? What parts of this would be exported and how would that affect this part of the codebase?

Exposing them wouldn't help much now, because LDK's macros don't support strings and vector in TLVs, which we would like to have being supported.

@sr-gi
Copy link
Member

sr-gi commented Sep 24, 2022

@sr-gi

ser_macros.rs is based on lightning::util::ser_macros.rs but it significantly deviates from it. Is there any good reasoning behind it? Has lightning::util::ser_macros.rs been updated since you wrote this?

I mainly wanted impl_writeable_msg! to be exposed (and of course any macros called inside of it) because it handles all the TLV stuff, but that wasn't what LDK team wanted. They wanted to expose more of the utility macros instead (e.g. encode_tlv_stream!) which then the users could use to craft their things.

Not a big deal too. Exposing the utility macros would do fine. But after going on with BOLT13, some TLV datatypes there weren't covered by LDK's macro system (like vectors and strings) as they don't use it internally nor that it is used in other BOLTs, which led me to make some tweaks to my copy till it deviated that much.

You can also find other parts trimmed out (like check_tlv_order!, check_missing_tlv!, and default macro arm option), this is mainly because these parts weren't used in BOLT13 (and I think not even in other BOLTs, I think they mainly use it for internal messaging stuff).

As a general question, I saw you had some discussion regarding some of the serialization functionality being exposed in lightning. Is that still on track? What parts of this would be exported and how would that affect this part of the codebase?

Exposing them wouldn't help much now, because LDK's macros don't support strings and vector in TLVs, which we would like to have being supported.

Right. I'd like to do one last pass on this before accepting it.

@sr-gi
Copy link
Member

sr-gi commented Oct 5, 2022

Ok, I think this looks good. Not super used to reviewing macros but if something is not correct we will notice it when using the macros to implement the LN API.

LGTM modulo making the pending changes.

@mariocynicys
Copy link
Collaborator Author

Not super used to reviewing macros but if something is not correct we will notice it when using the macros to implement the LN API.

Yeah macros are tough but so cool and powerful. It's a pain that it ends up that you need to test macros with other macros 😆.

LGTM modulo making the pending changes.

Will patch make them soon 👍.

@mariocynicys
Copy link
Collaborator Author

These next commits are implementing the server functionality/interface.

@mariocynicys mariocynicys force-pushed the lightning-msgs branch 3 times, most recently from b6889bd to ddfd755 Compare February 5, 2023 19:04
@sr-gi
Copy link
Member

sr-gi commented Feb 6, 2023

Looks like this needs rebasing

mariocynicys and others added 10 commits February 6, 2023 14:09
Add message (de)serilaization to teos-common
This commit adds lightning messages and message (de)serialization using helper local modules (ser_utils, ser_macros) and LDK v0.0.108 instead of the attempted fork used before (https://github.com/meryacine/rust-lightning/tree/expose-tlv-macros)
Most of the serialization macros and utils are copied from LDK's own serialization framework though. Also we might remove some of the locally implemented TLV stuff if they ever get exposed by LDK.

But some of the macros have been edited and diverged from LDK's first
implementation: for example, the `vec` arms for the macros were adjusted
so that it works with `impl_writeable_msg` macro to send arrays as TLV (which is not the case
for LDK ATM).
Note that we could have had vectors(arrays) be inside `Option`s and put
in a TLV, but this will waste extra two bytes for vector length
annotation (we don't need the vector length annotated in the V(value) of
the TLV since L(length) already embeds this information)

We also now have another macro match arm `str_opt`, which
indicates an optional string inside a TLV, this was created to avoid
adding extra length field for strings just like `vec`.
This commit adds the lightning server and its port configuration option.
The peer manager in charge of the lightning server doesn't know about routing and lightning channels (yet?). It can only responde to the user custom messages defined in `teos_common::lightning::messages`.
A \`block_in_place\` is used in \`handle_tower_message\` to synchronously block on grpc calls.
These tests are analogous to tests in `http::tests_methods` and they test the tower message handler logic only (not the tower message handler when inside a peer manager)
These test utils include:
- A test lightning client: this simulates a client's peer manager that is trying to connect to the tower and send/receive tower messages. For a real client, this should be part of a bigger application (say a lightning node) and the custom message handler adds the capabilities of speaking with the tower over Lightning.
- Some utils to launch a Lightning server and connect one peer manager to another
- A simple proof of concept test
This fixes a deadlock issue that appears with too many concurrent requtests. This solution was taken from devrandom's issue: lightningdevkit/rust-lightning #1367
Another solution that would probably work is that lightning net tokio wraps our sync calls with `tokio::task::spawn_blocking(|| sync_call()).await.unwarp()`, but not sure how would this affect performace of other users with no async code requirements.

Note that peer_manager_tests now don't need to be anotated with `flavor = "multi_thread"`, that's because the runtime we block_on (inside lightning net tokio) is the artificial runtime created in `api::lightning::server` which is already "multi_thread"
Comment on lines +285 to +288
let tcp_stream = listener.accept().await.unwrap().0;
if shutdown_signal.is_triggered() {
return;
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This actually waits until there is a new inbound connection then checks for if a shutdown signal was issued.
We need to listen for inbound and shutdown signal at the same time.

@aruokhai
Copy link

Any update regarding this ?

1 similar comment
@aruokhai
Copy link

Any update regarding this ?

@aruokhai
Copy link

Any update regarding this feature ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request New feature or request Seeking Code Review review me pls Summer of Bitcoin Summer of Bitcoin 2023 projects
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants