Skip to content

Commit 5f957ef

Browse files
authored
Merge pull request talaia-labs#53 from talaia-labs/cln-plugin
Adds watchtower plugin for CoreLN
2 parents 65f0709 + 037743c commit 5f957ef

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+5770
-678
lines changed

.github/workflows/build.yaml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ jobs:
99
matrix:
1010
platform: [ ubuntu-latest, macos-latest, windows-latest ]
1111
toolchain: [ stable ]
12+
include:
13+
- platform: windows-latest
14+
arguments: --workspace --exclude watchtower-plugin
1215

1316
runs-on: ${{ matrix.platform }}
1417
steps:
@@ -22,10 +25,10 @@ jobs:
2225
profile: minimal
2326
- name: Build on Rust ${{ matrix.toolchain }}
2427
run: |
25-
cargo build --verbose --color always
28+
cargo build ${{ matrix.arguments }} --verbose --color always
2629
- name: Test on Rust ${{ matrix.toolchain }}
2730
run: |
28-
cargo test --verbose --color always
31+
cargo test ${{ matrix.arguments }} --verbose --color always
2932
3033
lint:
3134
runs-on: ubuntu-latest
@@ -43,4 +46,4 @@ jobs:
4346
cargo fmt --verbose --check -- --color always
4447
- name: Run clippy
4548
run: |
46-
cargo clippy --all-features --all-targets --color always -- --deny warnings
49+
cargo clippy --all-features --all-targets --color always -- --deny warnings

.github/workflows/cln-plugin.yaml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: CI tests for CLN watchtower-plugin
2+
3+
on: [push, pull_request]
4+
5+
env:
6+
bitcoind_version: 0.20.1
7+
cln_version: 0.11.0.1
8+
9+
jobs:
10+
cache-cln:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v2
14+
- uses: actions/setup-python@v2
15+
- name: Create CLN cache
16+
id: cache-cln
17+
uses: actions/cache@v3
18+
env:
19+
cache-name: cache-cln-dev
20+
with:
21+
path: lightning
22+
key: ${{ runner.os }}-build-${{ env.cache-name }}-v${{ env.cln_version }}
23+
- name: Compile CLN
24+
if: ${{ steps.cache-cln.outputs.cache-hit != 'true' }}
25+
run: |
26+
sudo apt-get update && sudo apt-get install gettext
27+
git clone https://github.com/ElementsProject/lightning.git && cd lightning && git checkout v${{ env.cln_version }}
28+
pip install --user poetry && poetry install
29+
./configure --enable-developer && poetry run make
30+
31+
cln-plugin:
32+
needs: cache-cln
33+
runs-on: ubuntu-latest
34+
steps:
35+
- uses: actions/checkout@v2
36+
- uses: actions/setup-python@v2
37+
- name: Install bitcoind
38+
run: |
39+
wget https://bitcoincore.org/bin/bitcoin-core-${{ env.bitcoind_version }}/bitcoin-${{ env.bitcoind_version }}-x86_64-linux-gnu.tar.gz
40+
tar -xzf bitcoin-${{ env.bitcoind_version }}-x86_64-linux-gnu.tar.gz
41+
ln -s $(pwd)/bitcoin-${{ env.bitcoind_version }}/bin/bitcoin* /usr/local/bin
42+
- name: Load CLN cache
43+
id: cache-cln
44+
uses: actions/cache@v3
45+
env:
46+
cache-name: cache-cln-dev
47+
with:
48+
path: lightning
49+
key: ${{ runner.os }}-build-${{ env.cache-name }}-v${{ env.cln_version }}
50+
- name: Link CLN
51+
run: |
52+
cd lightning && sudo make install
53+
- name: Install teos and the plugin
54+
run: |
55+
cargo install --path teos
56+
cargo install --path watchtower-plugin
57+
- name: Add test dependencies
58+
run: |
59+
cd watchtower-plugin/tests
60+
pip install --user poetry && poetry install
61+
- name: Run tests
62+
run: |
63+
cd watchtower-plugin/tests
64+
DEVELOPER=1 poetry run pytest test.py -s

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
target
2-
*/target
2+
__pycache__
33
Cargo.lock
44
.vscode
5+
.idea

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
members = [
44
"teos",
55
"teos-common",
6+
"watchtower-plugin"
67
]

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,11 @@ To run `teos-cli` remotely, you'll need to take one extra step. When `teosd` is
114114
The files are generated to the data directory (by default stored at `~/.teos`). To run remotely, users need to copy the `client.pem`, `client-key.pem`, and `ca.pem` files to the corresponding watchtower data directory on the machine where the CLI is being run.
115115

116116
## Interacting with TEOS as a client
117+
## TEOS clients
117118

118-
FIXME: Add client and docs
119+
Here is a list of the available clients for `teos`:
120+
121+
- [watchtower-plugin for CLN](watchtower-plugin/)
119122

120123
## Contributing
121124
Refer to [CONTRIBUTING.md](CONTRIBUTING.md)

teos-common/Cargo.toml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,20 @@ edition = "2018"
88

99
[dependencies]
1010
# General
11-
hex = "0.4.3"
11+
hex = { version = "0.4.3", features = [ "serde" ] }
12+
prost = "0.9"
13+
rusqlite = { version = "0.26.0", features = [ "bundled", "limits" ] }
14+
serde = "1.0.130"
15+
serde_json = "1.0"
16+
tonic = "0.6"
1217

1318
# Crypto
1419
rand = "0.8.4"
1520
chacha20poly1305 = "0.8.0"
1621

1722
# Bitcoin and Lightning
18-
bitcoin = "0.27"
23+
bitcoin = { version = "0.27", features = [ "use-serde" ] }
1924
lightning = "0.0.105"
25+
26+
[build-dependencies]
27+
tonic-build = "0.6"

teos-common/build.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
fn main() -> Result<(), Box<dyn std::error::Error>> {
2+
tonic_build::configure()
3+
.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]")
4+
.type_attribute("AppointmentData.appointment_data", "#[serde(untagged)]")
5+
.field_attribute("AppointmentData.appointment_data", "#[serde(flatten)]")
6+
.field_attribute("appointment_data", "#[serde(rename = \"appointment\")]")
7+
.field_attribute("user_id", "#[serde(with = \"hex::serde\")]")
8+
.field_attribute("locator", "#[serde(with = \"hex::serde\")]")
9+
.field_attribute("encrypted_blob", "#[serde(with = \"hex::serde\")]")
10+
.field_attribute(
11+
"GetAppointmentResponse.status",
12+
"#[serde(with = \"crate::ser::serde_status\")]",
13+
)
14+
.compile(
15+
&[
16+
"proto/common/teos/v2/appointment.proto",
17+
"proto/common/teos/v2/user.proto",
18+
],
19+
&["proto/common/teos/v2"],
20+
)?;
21+
22+
Ok(())
23+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
syntax = "proto3";
2+
package common.teos.v2;
3+
4+
message Appointment {
5+
/*
6+
Contains the basic information about an appointment (Watcher) and it's used for messages like
7+
AddAppointmentRequest or encapsulated inside AppointmentData for GetAppointmentResponse
8+
*/
9+
10+
bytes locator = 1;
11+
bytes encrypted_blob = 2;
12+
uint32 to_self_delay = 3;
13+
14+
}
15+
16+
message Tracker {
17+
// It's the equivalent of an appointment message from data held by the Responder.
18+
19+
bytes dispute_txid = 1;
20+
bytes penalty_txid = 2;
21+
bytes penalty_rawtx = 3;
22+
}
23+
24+
message AppointmentData {
25+
/*
26+
Encapsulates the data for a GetAppointmentResponse, given it can be an appointment (data is on the Watcher) or a
27+
tracker (data is on the Responder).
28+
*/
29+
30+
oneof appointment_data {
31+
Appointment appointment = 1;
32+
Tracker tracker = 2;
33+
}
34+
}
35+
36+
message AddAppointmentRequest {
37+
// Request to add an appointment to the backend, contains the appointment data and the user signature.
38+
39+
Appointment appointment = 1;
40+
string signature = 2;
41+
}
42+
43+
message AddAppointmentResponse {
44+
/*
45+
Response to an AddAppointmentRequest, contains the locator to identify the added appointment, the tower signature,
46+
the block at which the tower has started (or will start) watching for the appointment, and the updated subscription
47+
information.
48+
*/
49+
50+
bytes locator = 1;
51+
uint32 start_block = 2;
52+
string signature = 3;
53+
uint32 available_slots = 4;
54+
uint32 subscription_expiry = 5;
55+
}
56+
57+
message GetAppointmentRequest {
58+
// Request to get information about an appointment. Contains the appointment locator and a signature by the user.
59+
60+
bytes locator = 1;
61+
string signature = 2;
62+
}
63+
64+
message GetAppointmentResponse {
65+
// Response to a GetAppointmentRequest. Contains the appointment data encapsulated in an AppointmentData message.
66+
67+
AppointmentData appointment_data = 1;
68+
enum AppointmentStatus {
69+
NOT_FOUND = 0;
70+
BEING_WATCHED = 1;
71+
DISPUTE_RESPONDED = 2;
72+
73+
}
74+
AppointmentStatus status = 2;
75+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
syntax = "proto3";
2+
package common.teos.v2;
3+
4+
message RegisterRequest {
5+
// Requests a user registration with the tower. Contains the user id in the form of a compressed ECDSA public key.
6+
7+
bytes user_id = 1;
8+
}
9+
10+
message RegisterResponse {
11+
// Response to a RegisterRequest, contains the registration information alongside the tower signature of the agreement.
12+
13+
bytes user_id = 1;
14+
uint32 available_slots = 2;
15+
uint32 subscription_expiry = 3;
16+
string subscription_signature = 4;
17+
}
18+
19+
message GetSubscriptionInfoRequest {
20+
// Request to get a specific user's subscription info.
21+
22+
string signature = 1;
23+
}
24+
25+
message GetSubscriptionInfoResponse {
26+
// Response with the information the tower has about a specific user
27+
28+
uint32 available_slots = 1;
29+
uint32 subscription_expiry = 2;
30+
repeated bytes locators = 3;
31+
}

teos-common/src/appointment.rs

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
//! Logic related to appointments shared between users and the towers.
22
33
use hex;
4+
use serde::{Deserialize, Serialize};
5+
46
use std::array::TryFromSliceError;
57
use std::{convert::TryInto, fmt};
68

79
use bitcoin::Txid;
810

11+
use crate::protos as msgs;
12+
913
pub const LOCATOR_LEN: usize = 16;
1014

1115
/// User identifier for appointments.
12-
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
16+
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash, Serialize, Deserialize)]
1317
pub struct Locator([u8; LOCATOR_LEN]);
1418

1519
impl Locator {
@@ -19,36 +23,42 @@ impl Locator {
1923
}
2024

2125
/// Encodes a locator into its byte representation.
22-
pub fn serialize(&self) -> Vec<u8> {
26+
pub fn to_vec(&self) -> Vec<u8> {
2327
self.0.to_vec()
2428
}
2529

2630
/// Builds a locator from its byte representation.
27-
pub fn deserialize(data: &[u8]) -> Result<Self, TryFromSliceError> {
31+
pub fn from_slice(data: &[u8]) -> Result<Self, TryFromSliceError> {
2832
data.try_into().map(Self)
2933
}
3034
}
3135

32-
impl std::str::FromStr for Locator {
33-
type Err = String;
36+
impl fmt::Display for Locator {
37+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
38+
write!(f, "{}", hex::encode(self.to_vec()))
39+
}
40+
}
3441

35-
fn from_str(s: &str) -> Result<Self, Self::Err> {
36-
let raw_locator = hex::decode(s).map_err(|_| "Locator is not hex encoded")?;
37-
Locator::deserialize(&raw_locator)
38-
.map_err(|_| "Locator cannot be built from the given data".into())
42+
impl AsRef<[u8]> for Locator {
43+
fn as_ref(&self) -> &[u8] {
44+
&self.0
3945
}
4046
}
4147

42-
impl fmt::Display for Locator {
43-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
44-
write!(f, "{}", hex::encode(self.serialize()))
48+
impl hex::FromHex for Locator {
49+
type Error = String;
50+
51+
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
52+
let raw_locator = hex::decode(hex).map_err(|_| "Locator is not hex encoded")?;
53+
Locator::from_slice(&raw_locator)
54+
.map_err(|_| "Locator cannot be built from the given data".into())
4555
}
4656
}
4757

4858
/// Contains data regarding an appointment between a client and the tower.
4959
///
5060
/// An appointment is requested for every new channel update.
51-
#[derive(Debug, Eq, PartialEq, Clone)]
61+
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
5262
pub struct Appointment {
5363
/// The user identifier for the appointment.
5464
pub locator: Locator,
@@ -62,6 +72,7 @@ pub struct Appointment {
6272
}
6373

6474
/// Represents all the possible states of an appointment in the tower, or in a response to a client request.
75+
#[derive(Serialize, Deserialize, Debug)]
6576
pub enum AppointmentStatus {
6677
NotFound = 0,
6778
BeingWatched = 1,
@@ -118,10 +129,27 @@ impl Appointment {
118129
/// `locator || encrypted_blob || to_self_delay`
119130
///
120131
/// All values are big endian.
121-
pub fn serialize(&self) -> Vec<u8> {
122-
let mut result = self.locator.serialize();
132+
pub fn to_vec(&self) -> Vec<u8> {
133+
let mut result = self.locator.to_vec();
123134
result.extend(&self.encrypted_blob);
124135
result.extend(self.to_self_delay.to_be_bytes().to_vec());
125136
result
126137
}
127138
}
139+
140+
impl From<Appointment> for msgs::Appointment {
141+
fn from(a: Appointment) -> Self {
142+
Self {
143+
locator: a.locator.to_vec(),
144+
encrypted_blob: a.encrypted_blob.clone(),
145+
to_self_delay: a.to_self_delay,
146+
}
147+
}
148+
}
149+
150+
/// Computes the number of slots an appointment takes from a user subscription.
151+
///
152+
/// This is based on the [encrypted_blob](Appointment::encrypted_blob) size and the slot size that was defined by the [Gatekeeper](crate::gatekeeper::Gatekeeper).
153+
pub fn compute_appointment_slots(blob_size: usize, blob_max_size: usize) -> u32 {
154+
(blob_size as f32 / blob_max_size as f32).ceil() as u32
155+
}

0 commit comments

Comments
 (0)