Skip to content

Commit

Permalink
Added configure method to greenlight proto file, regenerated grpc fil…
Browse files Browse the repository at this point in the history
…es, and added python bindings.

Added functionality to allow the last configure request to be sent to the signer on every signing request.
  • Loading branch information
Randy808 committed Oct 31, 2023
1 parent 008844c commit 795467f
Show file tree
Hide file tree
Showing 18 changed files with 3,531 additions and 593 deletions.
3 changes: 3 additions & 0 deletions libs/Cargo.lock

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

6 changes: 2 additions & 4 deletions libs/gl-client-py/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ PYDIR=${REPO}/libs/gl-client-py
PROTODIR=${REPO}/libs/proto

PYPROTOC_OPTS = \
-I${PROTODIR} \
-I ${PROTODIR} \
--python_out=${PYDIR}/glclient \
--grpc_python_out=${PYDIR}/glclient \
--experimental_allow_proto3_optional \
Expand All @@ -30,9 +30,7 @@ PROTOSRC = \

GENALL += ${PYPROTOS}

pygrpc: ${PYPROTOS}

${PYPROTOS}: ${PROTOSRC}
pygrpc: ${PROTOSRC}
python -m grpc_tools.protoc ${PYPROTOC_OPTS} scheduler.proto
python -m grpc_tools.protoc ${PYPROTOC_OPTS} greenlight.proto
sed -i 's/import scheduler_pb2 as scheduler__pb2/from . import scheduler_pb2 as scheduler__pb2/g' ${PYDIR}/glclient/scheduler_pb2_grpc.py
Expand Down
6 changes: 6 additions & 0 deletions libs/gl-client-py/glclient/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,12 @@ def get_lsp_client(
native_lsps = self.inner.get_lsp_client()
return LspClient(native_lsps)

def configure(self, close_to_addr: str) -> None:
req = nodepb.GlConfig(
close_to_addr=close_to_addr
).SerializeToString()

return self.inner.configure(bytes(req))

def normalize_node_id(node_id, string=False):
if len(node_id) == 66:
Expand Down
78 changes: 27 additions & 51 deletions libs/gl-client-py/glclient/greenlight_pb2.py

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions libs/gl-client-py/glclient/greenlight_pb2.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -1517,6 +1517,27 @@ class NodeConfig(google.protobuf.message.Message):

global___NodeConfig = NodeConfig

@typing_extensions.final
class GlConfig(google.protobuf.message.Message):
"""The `GlConfig` is used to pass greenlight-specific startup parameters
to the node. The `gl-plugin` will look for a serialized config object in
the node's datastore to load these values from. Please refer to the
individual fields to learn what they do.
"""

DESCRIPTOR: google.protobuf.descriptor.Descriptor

CLOSE_TO_ADDR_FIELD_NUMBER: builtins.int
close_to_addr: builtins.str
def __init__(
self,
*,
close_to_addr: builtins.str = ...,
) -> None: ...
def ClearField(self, field_name: typing_extensions.Literal["close_to_addr", b"close_to_addr"]) -> None: ...

global___GlConfig = GlConfig

@typing_extensions.final
class StartupMessage(google.protobuf.message.Message):
"""A message that we know will be requested by `lightningd` at
Expand Down
564 changes: 33 additions & 531 deletions libs/gl-client-py/glclient/greenlight_pb2_grpc.py

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions libs/gl-client-py/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,16 @@ impl Node {
self.cln_client.clone()
)
}

fn configure(&self, payload: &[u8]) -> PyResult<()> {
let req = pb::GlConfig::decode(payload).map_err(error_decoding_request)?;

exec(self.client.clone().configure(req))
.map(|x| x.into_inner())
.map_err(error_calling_remote_method)?;

return Ok(());
}
}

fn error_decoding_request<D: core::fmt::Display>(e: D) -> PyErr {
Expand Down
20 changes: 20 additions & 0 deletions libs/gl-client/src/signer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use vls_protocol::serde_bolt::Octets;
use vls_protocol_signer::approver::{Approve, MemoApprover};
use vls_protocol_signer::handler;
use vls_protocol_signer::handler::Handler;
use lightning_signer::bitcoin::secp256k1::PublicKey;

mod approver;
mod auth;
Expand Down Expand Up @@ -376,6 +377,25 @@ impl Signer {
return Err(Error::Resolver(req.raw, ctxrequests));
};

// If present, add the close_to_addr to the allowlist
for parsed_request in ctxrequests.iter() {
match parsed_request {
model::Request::GlConfig(gl_config) => {
let pubkey = PublicKey::from_slice(&self.id);
match pubkey {
Ok(p) => {
let _ = self
.services
.persister
.update_node_allowlist(&p, vec![gl_config.close_to_addr.clone()]);
}
Err(e) => debug!("Could not parse public key {:?}: {:?}", self.id, e),
}
}
_ => {}
}
}

use auth::Authorizer;
let auth = auth::GreenlightAuthorizer {};
let approvals = auth.authorize(ctxrequests).map_err(|e| Error::Auth(e))?;
Expand Down
3 changes: 3 additions & 0 deletions libs/gl-client/src/signer/model/greenlight.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ pub fn decode_request(uri: &str, p: &[u8]) -> anyhow::Result<Request> {
"/greenlight.Node/ConnectPeer" => {
Request::GlConnectPeer(crate::pb::ConnectRequest::decode(p)?)
}
"/greenlight.Node/Configure" => {
Request::GlConfig(crate::pb::GlConfig::decode(p)?)
}
uri => return Err(anyhow!("Unknown URI {}, can't decode payload", uri)),
})
}
1 change: 1 addition & 0 deletions libs/gl-client/src/signer/model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub enum Request {
GlListPayments(greenlight::ListPaymentsRequest),
GlListInvoices(greenlight::ListInvoicesRequest),
GlConnectPeer(greenlight::ConnectRequest),
GlConfig(greenlight::GlConfig),
Getinfo(cln::GetinfoRequest),
ListPeers(cln::ListpeersRequest),
ListFunds(cln::ListfundsRequest),
Expand Down
2 changes: 1 addition & 1 deletion libs/gl-plugin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ path = "src/bin/plugin.rs"
[dependencies]
anyhow = "*"
async-stream = "*"
bytes = "1"
bytes = { version = "1", features = ["serde"] }
clightningrpc = "*"
cln-grpc = {git = "https://github.com/ElementsProject/lightning.git", branch = "master", features = ["server"] }
cln-rpc = {git = "https://github.com/ElementsProject/lightning.git", branch = "master"}
Expand Down
3 changes: 2 additions & 1 deletion libs/gl-plugin/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
use std::sync::Arc;
use tokio::sync::Mutex;
use serde::{Serialize, Deserialize};

#[derive(Clone, Debug)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Request {
// The caller's mTLS public key
pubkey: Vec<u8>,
Expand Down
31 changes: 31 additions & 0 deletions libs/gl-plugin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ pub async fn init(
.hook("htlc_accepted", lsp::on_htlc_accepted)
.hook("invoice_payment", on_invoice_payment)
.hook("peer_connected", on_peer_connected)
.hook("openchannel", on_openchannel)
.hook("custommsg", on_custommsg);
Ok(Builder {
state,
Expand Down Expand Up @@ -154,6 +155,36 @@ async fn on_peer_connected(plugin: Plugin, v: serde_json::Value) -> Result<serde
Ok(json!({"result": "continue"}))
}

async fn on_openchannel(plugin: Plugin, v: serde_json::Value) -> Result<serde_json::Value> {
debug!("Received an openchannel request: {:?}", v);
let mut rpc = cln_rpc::ClnRpc::new(plugin.configuration().rpc_file).await?;

let req = cln_rpc::model::requests::ListdatastoreRequest{
key: Some(vec![
"glconf".to_string(),
"request".to_string(),
])
};

let res = rpc.call_typed(req).await;
debug!("ListdatastoreRequest response: {:?}", res);

match res {
Ok(r) => {
if r.datastore.is_empty() {
return Ok(json!({"result": "continue"}))
}

Ok(json!({"result": "continue", "close_to": r.datastore[0].string}))
},
Err(e) => {
log::debug!("An error occurred while searching for a custom close_to address: {}", e);
Ok(json!({"result": "continue"}))
}
}
}


/// Notification handler that receives notifications on incoming
/// payments, then looks up the invoice in the DB, and forwards the
/// full information to the GRPC interface.
Expand Down
121 changes: 120 additions & 1 deletion libs/gl-plugin/src/node/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ use tokio_stream::wrappers::ReceiverStream;
use tonic::{transport::ServerTlsConfig, Code, Request, Response, Status};
mod wrapper;
pub use wrapper::WrappedNodeServer;
use gl_client::bitcoin;
use std::str::FromStr;


static LIMITER: OnceCell<RateLimiter<NotKeyed, InMemoryState, MonotonicClock>> =
OnceCell::const_new();
Expand Down Expand Up @@ -254,7 +257,8 @@ impl Node for PluginNodeServer {
let mut stream = self.stage.mystream().await;
let signer_state = self.signer_state.clone();
let ctx = self.ctx.clone();

let rrpc = self.rpc.clone();

tokio::spawn(async move {
trace!("hsmd hsm_id={} request processor started", hsm_id);
loop {
Expand Down Expand Up @@ -289,6 +293,38 @@ impl Node for PluginNodeServer {

req.request.signer_state = state.into();
req.request.requests = ctx.snapshot().await.into_iter().map(|r| r.into()).collect();

let rpc = rrpc.lock().await;

let list_datastore_req = cln_rpc::model::requests::ListdatastoreRequest{
key: Some(vec![
"glconf".to_string(),
"request".to_string()
])
};

let res: Result<cln_rpc::model::responses::ListdatastoreResponse, crate::rpc::Error> =
rpc.call("listdatastore", list_datastore_req).await;

match res {
Ok(list_datastore_res) => {
if list_datastore_res.datastore.len() > 0 {
let serialized_configure_request = list_datastore_res.datastore[0].string.clone();
match serialized_configure_request {
Some(serialized_configure_request) => {
let configure_request = serde_json::from_str::<crate::context::Request>(
&serialized_configure_request,
)
.unwrap();
req.request.requests.push(configure_request.into());
}
None => {}
}
}
}
Err(_) => {}
}

debug!(
"Sending signer requests with {} requests and {} state entries",
req.request.requests.len(),
Expand Down Expand Up @@ -387,6 +423,89 @@ impl Node for PluginNodeServer {

return Ok(Response::new(ReceiverStream::new(rx)));
}

async fn configure(&self, req: tonic::Request<pb::GlConfig>) -> Result<Response<pb::Empty>, Status> {
self.limit().await;
let gl_config = req.into_inner();
let rpc = self.get_rpc().await;

let res: Result<crate::responses::GetInfo, crate::rpc::Error> =
rpc.call("getinfo", json!({})).await;

let network = match res {
Ok(get_info_response) => match get_info_response.network.parse() {
Ok(v) => v,
Err(_) => Err(Status::new(
Code::Unknown,
format!("The network returned from the call to 'getinfo' could not be parsed"),
))?,
},
Err(e) => {
return Err(Status::new(
Code::Unknown,
format!("Failed to retrieve a response from 'getinfo' while setting the node's configuration: {}", e),
));
}
};

match bitcoin::Address::from_str(&gl_config.close_to_addr) {
Ok(address) => {
if address.network != network {
return Err(Status::new(
Code::Unknown,
format!(
"Network mismatch: \
Expected an address for {} but received an address for {}",
network,
address.network
),
));
}
}
Err(e) => {
return Err(Status::new(
Code::Unknown,
format!("The address {} is not valid: {}", gl_config.close_to_addr, e),
));
}
}

let res: Result<crate::cln_rpc::model::responses::DatastoreResponse, crate::rpc::Error> =
rpc.call("datastore", json!({
"key": vec![
"glconf".to_string(),
"channel".to_string(),
"close_to_addr".to_string()
],
"string": gl_config.close_to_addr,
})).await;

let requests: Vec<crate::context::Request> = self.ctx.snapshot().await.into_iter().map(|r| r.into()).collect();
let serialized_req = serde_json::to_string(&requests[0]).unwrap();
let datastore_res: Result<crate::cln_rpc::model::responses::DatastoreResponse, crate::rpc::Error> =
rpc.call("datastore", json!({
"key": vec![
"glconf".to_string(),
"request".to_string(),
],
"string": serialized_req,
})).await;

match datastore_res {
Ok(_) => {}
Err(e) => {
return Err(Status::new(
Code::Unknown,
format!("Failed to store the raw configure request in the datastore: {}", e),
));
}
}

match res {
Ok(_) => Ok(Response::new(pb::Empty::default())),
Err(e) => Err(Status::new(Code::Unknown, e.to_string())),
}
}
}

use cln_grpc::pb::node_server::NodeServer;
Expand Down
Loading

0 comments on commit 795467f

Please sign in to comment.