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

Tower payments pay endpoint: WIP #257

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 14 additions & 1 deletion teos-common/proto/common/teos/v2/user.proto
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ message RegisterRequest {
string subscription_signature = 5;
}

message PayRequest {
// Requests an invoice from the tower so that the user can make a payment.

bytes user_id = 1;
}

message PayResponse {
// Response to a PayRequest containing an invoice for the client to pay.

bytes user_id = 1;
string invoice = 2;
}

message GetSubscriptionInfoRequest {
// Request to get a specific user's subscription info.

Expand All @@ -29,4 +42,4 @@ message GetSubscriptionInfoResponse {
uint32 available_slots = 1;
uint32 subscription_expiry = 2;
repeated bytes locators = 3;
}
}
1 change: 1 addition & 0 deletions teos/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ torut = "0.2.1"
bitcoin = { version = "0.28.0", features = [ "base64" ] }
bitcoincore-rpc = "0.15.0"
lightning = "0.0.108"
lightning-invoice = "0.16.0"
lightning-net-tokio = "0.0.108"
lightning-block-sync = { version = "0.0.108", features = [ "rpc-client" ] }

Expand Down
3 changes: 2 additions & 1 deletion teos/proto/teos/v2/tower_services.proto
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ service PublicTowerServices {
// Public tower services, only reachable from the public API.

rpc register(common.teos.v2.RegisterRequest) returns (common.teos.v2.RegisterResponse) {}
rpc pay(common.teos.v2.PayRequest) returns (common.teos.v2.PayResponse) {}
rpc add_appointment(common.teos.v2.AddAppointmentRequest) returns (common.teos.v2.AddAppointmentResponse) {}
rpc get_appointment(common.teos.v2.GetAppointmentRequest) returns (common.teos.v2.GetAppointmentResponse) {}
rpc get_subscription_info(common.teos.v2.GetSubscriptionInfoRequest) returns (common.teos.v2.GetSubscriptionInfoResponse) {}
Expand All @@ -47,4 +48,4 @@ service PrivateTowerServices {
rpc get_users(google.protobuf.Empty) returns (GetUsersResponse) {}
rpc get_user(GetUserRequest) returns (GetUserResponse) {}
rpc stop(google.protobuf.Empty) returns (google.protobuf.Empty) {}
}
}
12 changes: 6 additions & 6 deletions teos/src/api/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,7 @@ mod tests_methods {
#[tokio::test]
async fn test_register_max_slots() {
let (server_addr, _, _s) =
run_tower_in_background_with_config(ApiConfig::new(u32::MAX, DURATION)).await;
run_tower_in_background_with_config(ApiConfig::new(u32::MAX, DURATION, false)).await;
let user_id = get_random_user_id();

// Register once, this should go trough and set slots to the limit
Expand Down Expand Up @@ -724,7 +724,7 @@ mod tests_methods {
#[tokio::test]
async fn test_register_service_unavailable() {
let (server_addr, _, _s) = run_tower_in_background_with_config(
ApiConfig::new(SLOTS, DURATION).bitcoind_unreachable(),
ApiConfig::new(SLOTS, DURATION, false).bitcoind_unreachable(),
)
.await;
let user_id = get_random_user_id();
Expand Down Expand Up @@ -819,7 +819,7 @@ mod tests_methods {
async fn test_add_appointment_already_triggered() {
// Get the InternalAPI so we can mess with the inner state
let (server_addr, internal_api, _s) =
run_tower_in_background_with_config(ApiConfig::new(u32::MAX, DURATION)).await;
run_tower_in_background_with_config(ApiConfig::new(u32::MAX, DURATION, false)).await;

// Register
let (user_sk, user_pk) = cryptography::get_random_keypair();
Expand Down Expand Up @@ -870,7 +870,7 @@ mod tests_methods {
#[tokio::test]
async fn test_add_appointment_service_unavailable() {
let (server_addr, _, _s) = run_tower_in_background_with_config(
ApiConfig::new(SLOTS, DURATION).bitcoind_unreachable(),
ApiConfig::new(SLOTS, DURATION, false).bitcoind_unreachable(),
)
.await;
let (user_sk, _) = cryptography::get_random_keypair();
Expand Down Expand Up @@ -1031,7 +1031,7 @@ mod tests_methods {
#[tokio::test]
async fn test_get_appointment_service_unavailable() {
let (server_addr, _, _s) = run_tower_in_background_with_config(
ApiConfig::new(SLOTS, DURATION).bitcoind_unreachable(),
ApiConfig::new(SLOTS, DURATION, false).bitcoind_unreachable(),
)
.await;

Expand Down Expand Up @@ -1130,7 +1130,7 @@ mod tests_methods {
async fn test_get_subscription_info_service_unavailable() {
let (user_sk, _) = cryptography::get_random_keypair();
let (server_addr, _, _s) = run_tower_in_background_with_config(
ApiConfig::new(SLOTS, DURATION).bitcoind_unreachable(),
ApiConfig::new(SLOTS, DURATION, false).bitcoind_unreachable(),
)
.await;

Expand Down
128 changes: 116 additions & 12 deletions teos/src/api/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use tonic::{Code, Request, Response, Status};
use triggered::Trigger;

use crate::extended_appointment::UUID;
use crate::fees::ValidatePayment;
use crate::protos as msgs;
use crate::protos::private_tower_services_server::PrivateTowerServices;
use crate::protos::public_tower_services_server::PublicTowerServices;
Expand All @@ -28,6 +29,8 @@ pub struct InternalAPI {
bitcoind_reachable: Arc<(Mutex<bool>, Condvar)>,
/// A signal indicating the tower is shuting down.
shutdown_trigger: Trigger,
/// If fee mode is on, this validates payments made to the watchtower.
validator: Option<Arc<Mutex<dyn ValidatePayment + Send>>>,
}

impl InternalAPI {
Expand All @@ -37,12 +40,14 @@ impl InternalAPI {
addresses: Vec<msgs::NetworkAddress>,
bitcoind_reachable: Arc<(Mutex<bool>, Condvar)>,
shutdown_trigger: Trigger,
validator: Option<Arc<Mutex<dyn ValidatePayment + Send>>>,
) -> Self {
Self {
watcher,
addresses,
bitcoind_reachable,
shutdown_trigger,
validator,
}
}

Expand Down Expand Up @@ -97,6 +102,45 @@ impl PublicTowerServices for Arc<InternalAPI> {
}
}

/// Pay endpoint. Part of the public API. Internally calls the payment validator to generate an
/// invoice.
async fn pay(
&self,
request: Request<common_msgs::PayRequest>,
) -> Result<Response<common_msgs::PayResponse>, Status> {
self.check_service_unavailable()?;
let req_data = request.into_inner();

if self.validator.is_none() {
return Err(Status::new(
Code::Unimplemented,
"Paying for watchtower service is unsupported",
));
};

let user_id = UserId::from_slice(&req_data.user_id).map_err(|_| {
Status::new(
Code::InvalidArgument,
"Provided public key does not match expected format (33-byte compressed key)",
)
})?;

let invoice = match self.watcher.pay(user_id) {
Ok(invoice) => invoice,
Err(_) => {
return Err(Status::new(
Code::Unimplemented,
"Paying for watchtower service is unsupported",
));
}
};

return Ok(Response::new(common_msgs::PayResponse {
user_id: req_data.user_id,
invoice: invoice.to_string(),
}));
}

/// Add appointment endpoint. Part of the public API. Internally calls [Watcher::add_appointment].
async fn add_appointment(
&self,
Expand Down Expand Up @@ -846,7 +890,8 @@ mod tests_public_api {

#[tokio::test]
async fn test_register_max_slots() {
let (internal_api, _s) = create_api_with_config(ApiConfig::new(u32::MAX, DURATION)).await;
let (internal_api, _s) =
create_api_with_config(ApiConfig::new(u32::MAX, DURATION, false)).await;

let (_, user_pk) = get_random_keypair();
let user_id = UserId(user_pk).to_vec();
Expand Down Expand Up @@ -874,8 +919,10 @@ mod tests_public_api {

#[tokio::test]
async fn test_register_service_unavailable() {
let (internal_api, _s) =
create_api_with_config(ApiConfig::new(u32::MAX, DURATION).bitcoind_unreachable()).await;
let (internal_api, _s) = create_api_with_config(
ApiConfig::new(u32::MAX, DURATION, false).bitcoind_unreachable(),
)
.await;

let (_, user_pk) = get_random_keypair();
let user_id = UserId(user_pk).to_vec();
Expand All @@ -892,6 +939,59 @@ mod tests_public_api {
}
}

#[tokio::test]
async fn test_pay() {
let cfg = ApiConfig::new(SLOTS, DURATION, true);
let (internal_api, _s) = create_api_with_config(cfg).await;
let (_, user_pk) = get_random_keypair();

let response = internal_api
.pay(Request::new(common_msgs::PayRequest {
user_id: UserId(user_pk).to_vec(),
}))
.await
.unwrap()
.into_inner();

assert!(matches!(response, common_msgs::PayResponse { .. }));

// Check that when we hit it a second time, we get the same invoice as before.
let response2 = internal_api
.pay(Request::new(common_msgs::PayRequest {
user_id: UserId(user_pk).to_vec(),
}))
.await
.unwrap()
.into_inner();

assert_eq!(response.invoice, response2.invoice);
}

#[tokio::test]
async fn test_pay_no_payments() {
// We'll test that if the tower payments aren't turned on, but a user tries to hit the pay endpoint,
// we get the expected error.
let cfg = ApiConfig::new(SLOTS, DURATION, false);
let (internal_api, _s) = create_api_with_config(cfg).await;
let (_, user_pk) = get_random_keypair();

match internal_api
.pay(Request::new(common_msgs::PayRequest {
user_id: UserId(user_pk).to_vec(),
}))
.await
{
Err(status) => {
assert_eq!(status.code(), Code::Unimplemented);
assert_eq!(
status.message(),
"Paying for watchtower service is unsupported"
)
}
_ => panic!("Test should have returned Err"),
}
}

#[tokio::test]
async fn test_add_appointment() {
let (internal_api, _s) = create_api().await;
Expand Down Expand Up @@ -948,7 +1048,7 @@ mod tests_public_api {

#[tokio::test]
async fn test_add_appointment_not_enough_slots() {
let (internal_api, _s) = create_api_with_config(ApiConfig::new(0, DURATION)).await;
let (internal_api, _s) = create_api_with_config(ApiConfig::new(0, DURATION, false)).await;

// User is registered but has no slots
let (user_sk, user_pk) = get_random_keypair();
Expand Down Expand Up @@ -977,7 +1077,7 @@ mod tests_public_api {

#[tokio::test]
async fn test_add_appointment_subscription_expired() {
let (internal_api, _s) = create_api_with_config(ApiConfig::new(SLOTS, 0)).await;
let (internal_api, _s) = create_api_with_config(ApiConfig::new(SLOTS, 0, false)).await;

// User is registered but subscription is expired
let (user_sk, user_pk) = get_random_keypair();
Expand Down Expand Up @@ -1042,8 +1142,10 @@ mod tests_public_api {

#[tokio::test]
async fn test_add_appointment_service_unavailable() {
let (internal_api, _s) =
create_api_with_config(ApiConfig::new(u32::MAX, DURATION).bitcoind_unreachable()).await;
let (internal_api, _s) = create_api_with_config(
ApiConfig::new(u32::MAX, DURATION, false).bitcoind_unreachable(),
)
.await;

let (user_sk, _) = get_random_keypair();
let appointment = generate_dummy_appointment(None).inner;
Expand Down Expand Up @@ -1154,7 +1256,7 @@ mod tests_public_api {

#[tokio::test]
async fn test_get_appointment_subscription_expired() {
let (internal_api, _s) = create_api_with_config(ApiConfig::new(SLOTS, 0)).await;
let (internal_api, _s) = create_api_with_config(ApiConfig::new(SLOTS, 0, false)).await;

// Register the user
let (user_sk, user_pk) = get_random_keypair();
Expand Down Expand Up @@ -1183,7 +1285,8 @@ mod tests_public_api {
#[tokio::test]
async fn test_get_appointment_service_unavailable() {
let (internal_api, _s) =
create_api_with_config(ApiConfig::new(SLOTS, DURATION).bitcoind_unreachable()).await;
create_api_with_config(ApiConfig::new(SLOTS, DURATION, false).bitcoind_unreachable())
.await;

let (user_sk, _) = get_random_keypair();
let appointment = generate_dummy_appointment(None).inner;
Expand Down Expand Up @@ -1229,7 +1332,7 @@ mod tests_public_api {

#[tokio::test]
async fn test_get_subscription_info_non_registered() {
let (internal_api, _s) = create_api_with_config(ApiConfig::new(SLOTS, 0)).await;
let (internal_api, _s) = create_api_with_config(ApiConfig::new(SLOTS, 0, false)).await;

// The user is not registered
let (user_sk, _) = get_random_keypair();
Expand All @@ -1252,7 +1355,7 @@ mod tests_public_api {

#[tokio::test]
async fn test_get_subscription_info_expired() {
let (internal_api, _s) = create_api_with_config(ApiConfig::new(SLOTS, 0)).await;
let (internal_api, _s) = create_api_with_config(ApiConfig::new(SLOTS, 0, false)).await;

// The user is registered but the subscription has expired
let (user_sk, user_pk) = get_random_keypair();
Expand All @@ -1277,7 +1380,8 @@ mod tests_public_api {
#[tokio::test]
async fn test_get_subscription_info_service_unavailable() {
let (internal_api, _s) =
create_api_with_config(ApiConfig::new(SLOTS, DURATION).bitcoind_unreachable()).await;
create_api_with_config(ApiConfig::new(SLOTS, DURATION, false).bitcoind_unreachable())
.await;

let (user_sk, _) = get_random_keypair();
let message = "get subscription info".to_string();
Expand Down
Loading
Loading