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.
  • Loading branch information
Randy808 authored and cdecker committed Sep 29, 2023
1 parent b669f68 commit e46fbfd
Show file tree
Hide file tree
Showing 13 changed files with 395 additions and 878 deletions.
6 changes: 6 additions & 0 deletions libs/gl-client-py/glclient/__init__.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,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
828 changes: 149 additions & 679 deletions libs/gl-client-py/glclient/greenlight_pb2.py

Large diffs are not rendered by default.

15 changes: 15 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,21 @@ class NodeConfig(google.protobuf.message.Message):

global___NodeConfig = NodeConfig

@typing_extensions.final
class GlConfig(google.protobuf.message.Message):
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
33 changes: 33 additions & 0 deletions libs/gl-client-py/glclient/greenlight_pb2_grpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ def __init__(self, channel):
request_serializer=greenlight__pb2.HsmResponse.SerializeToString,
response_deserializer=greenlight__pb2.Empty.FromString,
)
self.Configure = channel.unary_unary(
'/greenlight.Node/Configure',
request_serializer=greenlight__pb2.GlConfig.SerializeToString,
response_deserializer=greenlight__pb2.Empty.FromString,
)


class NodeServicer(object):
Expand Down Expand Up @@ -329,6 +334,12 @@ def RespondHsmRequest(self, request, context):
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')

def Configure(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')


def add_NodeServicer_to_server(servicer, server):
rpc_method_handlers = {
Expand Down Expand Up @@ -432,6 +443,11 @@ def add_NodeServicer_to_server(servicer, server):
request_deserializer=greenlight__pb2.HsmResponse.FromString,
response_serializer=greenlight__pb2.Empty.SerializeToString,
),
'Configure': grpc.unary_unary_rpc_method_handler(
servicer.Configure,
request_deserializer=greenlight__pb2.GlConfig.FromString,
response_serializer=greenlight__pb2.Empty.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'greenlight.Node', rpc_method_handlers)
Expand Down Expand Up @@ -796,6 +812,23 @@ def RespondHsmRequest(request,
options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)

@staticmethod
def Configure(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(request, target, '/greenlight.Node/Configure',
greenlight__pb2.GlConfig.SerializeToString,
greenlight__pb2.Empty.FromString,
options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)


class HsmStub(object):
"""Missing associated documentation comment in .proto file."""
Expand Down
245 changes: 47 additions & 198 deletions libs/gl-client-py/glclient/scheduler_pb2.py

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion libs/gl-client-py/glclient/scheduler_pb2.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -435,17 +435,20 @@ class SignerRejection(google.protobuf.message.Message):

MSG_FIELD_NUMBER: builtins.int
REQUEST_FIELD_NUMBER: builtins.int
GIT_VERSION_FIELD_NUMBER: builtins.int
msg: builtins.str
"""A human-readable description of what went wrong"""
@property
def request(self) -> greenlight_pb2.HsmRequest: ...
git_version: builtins.str
def __init__(
self,
*,
msg: builtins.str = ...,
request: greenlight_pb2.HsmRequest | None = ...,
git_version: builtins.str = ...,
) -> None: ...
def HasField(self, field_name: typing_extensions.Literal["request", b"request"]) -> builtins.bool: ...
def ClearField(self, field_name: typing_extensions.Literal["msg", b"msg", "request", b"request"]) -> None: ...
def ClearField(self, field_name: typing_extensions.Literal["git_version", b"git_version", "msg", b"msg", "request", b"request"]) -> None: ...

global___SignerRejection = SignerRejection
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
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(),
"channel".to_string(),
"close_to_addr".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
63 changes: 63 additions & 0 deletions libs/gl-plugin/src/node/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ 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 @@ -880,6 +882,67 @@ impl Node for PluginNodeServer {
Err(e) => Err(Status::new(Code::Internal, e.to_string())),
}
}

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::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;

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
8 changes: 8 additions & 0 deletions libs/gl-plugin/src/responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,14 @@ pub struct ListFunds {
pub channels: Vec<ListFundsChannel>,
}

#[derive(Debug, Clone, Deserialize)]
pub struct DatastoreResponse {
pub key: Vec<String>,
pub generation: Option<u64>,
pub hex: Option<String>,
pub string: Option<String>,
}

#[cfg(test)]
mod test {
use super::*;
Expand Down
Empty file modified libs/gl-testing/.gitignore
100644 → 100755
Empty file.
23 changes: 23 additions & 0 deletions libs/gl-testing/tests/test_gl_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,26 @@ def check(gl1):
return len(res.peers) == 1 and res.peers[0].connected

wait_for(lambda: check(gl1))

def test_configure_close_to_addr(node_factory, clients, bitcoind):
l1, l2 = node_factory.line_graph(2)
l2.fundwallet(sats=2*10**6)

c = clients.new()
c.register(configure=True)
gl1 = c.node()

s = c.signer().run_in_thread()
gl1.connect_peer(l2.info['id'], f'127.0.0.1:{l2.daemon.port}')

close_to_addr = bitcoind.getnewaddress()
gl1.configure(close_to_addr)

l2.rpc.fundchannel(c.node_id.hex(), 'all')
bitcoind.generate_block(1, wait_for_mempool=1)

wait_for(lambda:
gl1.list_peers().peers[0].channels[0].state == 'CHANNELD_NORMAL'
)

assert gl1.list_peers().peers[0].channels[0].close_to_addr == close_to_addr
6 changes: 6 additions & 0 deletions libs/proto/greenlight.proto
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ service Node {

rpc RespondHsmRequest(HsmResponse) returns (Empty) {}

rpc Configure(GlConfig) returns (Empty) {}

}

message HsmRequestContext {
Expand Down Expand Up @@ -605,6 +607,10 @@ message NodeConfig {
repeated StartupMessage startupmsgs = 1;
}

message GlConfig {
string close_to_addr = 1;
}

// A message that we know will be requested by `lightningd` at
// startup, and that we stash a response to on the scheduler. This
// allows the scheduler to start a node without requiring the signer
Expand Down

0 comments on commit e46fbfd

Please sign in to comment.