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

pytest: Add grpc based CLN client to test grpc coverage #5362

Merged
merged 18 commits into from
Jul 21, 2022
Merged
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
22 changes: 22 additions & 0 deletions .msggen.json
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,28 @@
"Feerates.perkw": 3,
"Feerates.warning_missing_feerates": 1
},
"FundchannelRequest": {
"FundChannel.amount": 1,
"FundChannel.announce": 3,
"FundChannel.close_to": 6,
"FundChannel.compact_lease": 8,
"FundChannel.feerate": 2,
"FundChannel.id": 9,
"FundChannel.minconf": 10,
"FundChannel.minconf[]": 4,
"FundChannel.mindepth": 12,
"FundChannel.push_msat": 5,
"FundChannel.request_amt": 7,
"FundChannel.utxos[]": 11
},
"FundchannelResponse": {
"FundChannel.channel_id": 4,
"FundChannel.close_to": 5,
"FundChannel.mindepth": 6,
"FundChannel.outnum": 3,
"FundChannel.tx": 1,
"FundChannel.txid": 2
},
"FundpsbtRequest": {
"FundPsbt.excess_as_change": 8,
"FundPsbt.feerate": 2,
Expand Down
20 changes: 14 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,11 @@ endif
PYTEST_OPTS := -v -p no:logging $(PYTEST_OPTS)
MY_CHECK_PYTHONPATH=$${PYTHONPATH}$${PYTHONPATH:+:}$(shell pwd)/contrib/pyln-client:$(shell pwd)/contrib/pyln-testing:$(shell pwd)/contrib/pyln-proto/:$(shell pwd)/external/lnprototest:$(shell pwd)/contrib/pyln-spec/bolt1:$(shell pwd)/contrib/pyln-spec/bolt2:$(shell pwd)/contrib/pyln-spec/bolt4:$(shell pwd)/contrib/pyln-spec/bolt7
# Collect generated python files to be excluded from lint checks
PYTHON_GENERATED=
PYTHON_GENERATED= \
contrib/pyln-testing/pyln/testing/primitives_pb2.py \
contrib/pyln-testing/pyln/testing/node_pb2_grpc.py \
contrib/pyln-testing/pyln/testing/node_pb2.py \
contrib/pyln-testing/pyln/testing/grpc2py.py

# Options to pass to cppcheck. Mostly used to exclude files that are
# generated with external tools that we don't have control over
Expand Down Expand Up @@ -383,15 +387,19 @@ ifneq ($(RUST),0)
include cln-rpc/Makefile
include cln-grpc/Makefile

GRPC_GEN = tests/node_pb2.py \
tests/node_pb2_grpc.py \
tests/primitives_pb2.py
GRPC_GEN = contrib/pyln-testing/pyln/testing/node_pb2.py \
contrib/pyln-testing/pyln/testing/node_pb2_grpc.py \
contrib/pyln-testing/pyln/testing/primitives_pb2.py

ALL_TEST_GEN += $(GRPC_GEN)

$(GRPC_GEN): cln-grpc/proto/node.proto cln-grpc/proto/primitives.proto
python -m grpc_tools.protoc -I cln-grpc/proto cln-grpc/proto/node.proto --python_out=tests/ --grpc_python_out=tests/ --experimental_allow_proto3_optional
python -m grpc_tools.protoc -I cln-grpc/proto cln-grpc/proto/primitives.proto --python_out=tests/ --grpc_python_out=tests/ --experimental_allow_proto3_optional
python -m grpc_tools.protoc -I cln-grpc/proto cln-grpc/proto/node.proto --python_out=contrib/pyln-testing/pyln/testing/ --grpc_python_out=contrib/pyln-testing/pyln/testing/ --experimental_allow_proto3_optional
python -m grpc_tools.protoc -I cln-grpc/proto cln-grpc/proto/primitives.proto --python_out=contrib/pyln-testing/pyln/testing/ --experimental_allow_proto3_optional
# The compiler assumes that the proto files are in the same
# directory structure as the generated files will be. Since we
# don't do that we need to path the files up.
find contrib/pyln-testing/pyln/testing/ -type f -name "*.py" -print0 | xargs -0 sed -i 's/^import \(.*\)_pb2 as .*__pb2/from . import \1_pb2 as \1__pb2/g'

endif

Expand Down
31 changes: 31 additions & 0 deletions cln-grpc/proto/node.proto
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,13 @@ service Node {
rpc TxSend(TxsendRequest) returns (TxsendResponse) {}
rpc Disconnect(DisconnectRequest) returns (DisconnectResponse) {}
rpc Feerates(FeeratesRequest) returns (FeeratesResponse) {}
rpc FundChannel(FundchannelRequest) returns (FundchannelResponse) {}
rpc GetRoute(GetrouteRequest) returns (GetrouteResponse) {}
rpc ListForwards(ListforwardsRequest) returns (ListforwardsResponse) {}
rpc ListPays(ListpaysRequest) returns (ListpaysResponse) {}
rpc Ping(PingRequest) returns (PingResponse) {}
rpc SignMessage(SignmessageRequest) returns (SignmessageResponse) {}
rpc Stop(StopRequest) returns (StopResponse) {}
}

message GetinfoRequest {
Expand Down Expand Up @@ -1138,6 +1140,29 @@ message FeeratesOnchain_fee_estimates {
uint64 htlc_success_satoshis = 5;
}

message FundchannelRequest {
bytes id = 9;
AmountOrAll amount = 1;
optional Feerate feerate = 2;
optional bool announce = 3;
optional uint32 minconf = 10;
optional Amount push_msat = 5;
optional string close_to = 6;
optional Amount request_amt = 7;
optional string compact_lease = 8;
repeated Outpoint utxos = 11;
optional uint32 mindepth = 12;
}

message FundchannelResponse {
bytes tx = 1;
bytes txid = 2;
uint32 outnum = 3;
bytes channel_id = 4;
optional bytes close_to = 5;
optional uint32 mindepth = 6;
}

message GetrouteRequest {
bytes id = 1;
Amount amount_msat = 9;
Expand Down Expand Up @@ -1262,3 +1287,9 @@ message SignmessageResponse {
bytes recid = 2;
string zbase = 3;
}

message StopRequest {
}

message StopResponse {
}
49 changes: 49 additions & 0 deletions cln-grpc/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,20 @@ impl From<&responses::FeeratesResponse> for pb::FeeratesResponse {
}
}

#[allow(unused_variables)]
impl From<&responses::FundchannelResponse> for pb::FundchannelResponse {
fn from(c: &responses::FundchannelResponse) -> Self {
Self {
tx: hex::decode(&c.tx).unwrap(), // Rule #2 for type hex
txid: hex::decode(&c.txid).unwrap(), // Rule #2 for type txid
outnum: c.outnum.clone(), // Rule #2 for type u32
channel_id: hex::decode(&c.channel_id).unwrap(), // Rule #2 for type hex
close_to: c.close_to.as_ref().map(|v| hex::decode(&v).unwrap()), // Rule #2 for type hex?
mindepth: c.mindepth.clone(), // Rule #2 for type u32?
}
}
}

#[allow(unused_variables)]
impl From<&responses::GetrouteRoute> for pb::GetrouteRoute {
fn from(c: &responses::GetrouteRoute) -> Self {
Expand Down Expand Up @@ -950,6 +964,14 @@ impl From<&responses::SignmessageResponse> for pb::SignmessageResponse {
}
}

#[allow(unused_variables)]
impl From<&responses::StopResponse> for pb::StopResponse {
fn from(c: &responses::StopResponse) -> Self {
Self {
}
}
}

#[allow(unused_variables)]
impl From<&pb::GetinfoRequest> for requests::GetinfoRequest {
fn from(c: &pb::GetinfoRequest) -> Self {
Expand Down Expand Up @@ -1424,6 +1446,25 @@ impl From<&pb::FeeratesRequest> for requests::FeeratesRequest {
}
}

#[allow(unused_variables)]
impl From<&pb::FundchannelRequest> for requests::FundchannelRequest {
fn from(c: &pb::FundchannelRequest) -> Self {
Self {
id: cln_rpc::primitives::Pubkey::from_slice(&c.id).unwrap(), // Rule #1 for type pubkey
amount: c.amount.as_ref().unwrap().into(), // Rule #1 for type msat_or_all
feerate: c.feerate.as_ref().map(|a| a.into()), // Rule #1 for type feerate?
announce: c.announce.clone(), // Rule #1 for type boolean?
minconf: c.minconf.clone(), // Rule #1 for type u32?
push_msat: c.push_msat.as_ref().map(|a| a.into()), // Rule #1 for type msat?
close_to: c.close_to.clone(), // Rule #1 for type string?
request_amt: c.request_amt.as_ref().map(|a| a.into()), // Rule #1 for type msat?
compact_lease: c.compact_lease.clone(), // Rule #1 for type string?
utxos: Some(c.utxos.iter().map(|s| s.into()).collect()), // Rule #4
mindepth: c.mindepth.clone(), // Rule #1 for type u32?
}
}
}

#[allow(unused_variables)]
impl From<&pb::GetrouteRequest> for requests::GetrouteRequest {
fn from(c: &pb::GetrouteRequest) -> Self {
Expand Down Expand Up @@ -1482,3 +1523,11 @@ impl From<&pb::SignmessageRequest> for requests::SignmessageRequest {
}
}

#[allow(unused_variables)]
impl From<&pb::StopRequest> for requests::StopRequest {
fn from(c: &pb::StopRequest) -> Self {
Self {
}
}
}

72 changes: 68 additions & 4 deletions cln-grpc/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,20 +325,20 @@ async fn connect_peer(
let mut rpc = ClnRpc::new(&self.rpc_path)
.await
.map_err(|e| Status::new(Code::Internal, e.to_string()))?;
let result = rpc.call(Request::ConnectPeer(req))
let result = rpc.call(Request::Connect(req))
.await
.map_err(|e| Status::new(
Code::Unknown,
format!("Error calling method ConnectPeer: {:?}", e)))?;
format!("Error calling method Connect: {:?}", e)))?;
match result {
Response::ConnectPeer(r) => {
Response::Connect(r) => {
trace!("connect_peer response: {:?}", r);
Ok(tonic::Response::new((&r).into()))
},
r => Err(Status::new(
Code::Internal,
format!(
"Unexpected result {:?} to method call ConnectPeer",
"Unexpected result {:?} to method call Connect",
r
)
)),
Expand Down Expand Up @@ -1274,6 +1274,38 @@ async fn feerates(

}

async fn fund_channel(
&self,
request: tonic::Request<pb::FundchannelRequest>,
) -> Result<tonic::Response<pb::FundchannelResponse>, tonic::Status> {
let req = request.into_inner();
let req: requests::FundchannelRequest = (&req).into();
debug!("Client asked for fund_channel");
trace!("fund_channel request: {:?}", req);
let mut rpc = ClnRpc::new(&self.rpc_path)
.await
.map_err(|e| Status::new(Code::Internal, e.to_string()))?;
let result = rpc.call(Request::FundChannel(req))
.await
.map_err(|e| Status::new(
Code::Unknown,
format!("Error calling method FundChannel: {:?}", e)))?;
match result {
Response::FundChannel(r) => {
trace!("fund_channel response: {:?}", r);
Ok(tonic::Response::new((&r).into()))
},
r => Err(Status::new(
Code::Internal,
format!(
"Unexpected result {:?} to method call FundChannel",
r
)
)),
}

}

async fn get_route(
&self,
request: tonic::Request<pb::GetrouteRequest>,
Expand Down Expand Up @@ -1434,4 +1466,36 @@ async fn sign_message(

}

async fn stop(
&self,
request: tonic::Request<pb::StopRequest>,
) -> Result<tonic::Response<pb::StopResponse>, tonic::Status> {
let req = request.into_inner();
let req: requests::StopRequest = (&req).into();
debug!("Client asked for stop");
trace!("stop request: {:?}", req);
let mut rpc = ClnRpc::new(&self.rpc_path)
.await
.map_err(|e| Status::new(Code::Internal, e.to_string()))?;
let result = rpc.call(Request::Stop(req))
.await
.map_err(|e| Status::new(
Code::Unknown,
format!("Error calling method Stop: {:?}", e)))?;
match result {
Response::Stop(r) => {
trace!("stop response: {:?}", r);
Ok(tonic::Response::new((&r).into()))
},
r => Err(Status::new(
Code::Internal,
format!(
"Unexpected result {:?} to method call Stop",
r
)
)),
}

}

}
10 changes: 10 additions & 0 deletions cln-rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,16 @@ impl ClnRpc {
}
}

/// Used to skip optional arrays when serializing requests.
fn is_none_or_empty<T>(f: &Option<Vec<T>>) -> bool
where
T: Clone,
{
// TODO Find a better way to check, possibly without cloning
let f = f.clone();
f.is_none() || f.unwrap().is_empty()
}

#[cfg(test)]
mod test {
use super::*;
Expand Down
Loading