Skip to content
Open
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
32 changes: 32 additions & 0 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,19 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/NodeInfoResponse'
/nodestate:
get:
tags:
- Other
summary: Get node state
description: Get the current state of the RGB Lightning Node
responses:
'200':
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/NodeStateResponse'
/openchannel:
post:
tags:
Expand Down Expand Up @@ -1824,6 +1837,25 @@ components:
network_channels:
type: integer
example: 7812821
NodeState:
type: string
enum:
- None
- Locked
- Running
- Changing
example: Running
description: |
The current state of the RGB Lightning Node:
* `None` - Node is not initialized (no mnemonic file exists)
* `Locked` - Node is initialized but locked (needs to be unlocked)
* `Running` - Node is unlocked and running (can perform all operations)
* `Changing` - Node is in the process of changing state (wait for completion)
Comment on lines +1848 to +1853
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
description: |
The current state of the RGB Lightning Node:
* `None` - Node is not initialized (no mnemonic file exists)
* `Locked` - Node is initialized but locked (needs to be unlocked)
* `Running` - Node is unlocked and running (can perform all operations)
* `Changing` - Node is in the process of changing state (wait for completion)

names seem sufficiently descriptive

NodeStateResponse:
type: object
properties:
state:
$ref: '#/components/schemas/NodeState'
OpenChannelRequest:
type: object
properties:
Expand Down
3 changes: 2 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ use crate::routes::{
get_asset_media, get_channel_id, get_payment, get_swap, init, invoice_status, issue_asset_cfa,
issue_asset_nia, issue_asset_uda, keysend, list_assets, list_channels, list_payments,
list_peers, list_swaps, list_transactions, list_transfers, list_unspents, ln_invoice, lock,
maker_execute, maker_init, network_info, node_info, open_channel, post_asset_media,
maker_execute, maker_init, network_info, node_info, node_state, open_channel, post_asset_media,
refresh_transfers, restore, rgb_invoice, send_asset, send_btc, send_onion_message,
send_payment, shutdown, sign_message, sync, taker, unlock,
};
Expand Down Expand Up @@ -141,6 +141,7 @@ pub(crate) async fn app(args: LdkUserInfo) -> Result<(Router, Arc<AppState>), Ap
.route("/makerinit", post(maker_init))
.route("/networkinfo", get(network_info))
.route("/nodeinfo", get(node_info))
.route("/nodestate", get(node_state))
.route("/openchannel", post(open_channel))
.route("/refreshtransfers", post(refresh_transfers))
.route("/restore", post(restore))
Expand Down
40 changes: 40 additions & 0 deletions src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,19 @@
pub(crate) network_channels: usize,
}

#[derive(Deserialize, Serialize)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[derive(Deserialize, Serialize)]
#[derive(Debug, PartialEq, Deserialize, Serialize)]

pub(crate) enum NodeState {
None,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
None,
Uninitialized,

please rename also in openapi

Locked,
Running,
Changing,
}

#[derive(Deserialize, Serialize)]
pub(crate) struct NodeStateResponse {
pub(crate) state: NodeState,
}

#[derive(Deserialize, Serialize)]
pub(crate) struct OpenChannelRequest {
pub(crate) peer_pubkey_and_opt_addr: String,
Expand Down Expand Up @@ -1137,7 +1150,7 @@
}

async fn check_locked(
&self,

Check warning on line 1153 in src/routes.rs

View workflow job for this annotation

GitHub Actions / build (nightly, windows)

lifetime flowing from input to output with different syntax can be confusing

Check warning on line 1153 in src/routes.rs

View workflow job for this annotation

GitHub Actions / build (nightly, macos)

lifetime flowing from input to output with different syntax can be confusing

Check warning on line 1153 in src/routes.rs

View workflow job for this annotation

GitHub Actions / build (nightly, linux)

lifetime flowing from input to output with different syntax can be confusing
) -> Result<TokioMutexGuard<Option<Arc<UnlockedAppState>>>, APIError> {
let unlocked_app_state = self.get_unlocked_app_state().await;
if unlocked_app_state.is_some() {
Expand All @@ -1149,7 +1162,7 @@
}

async fn check_unlocked(
&self,

Check warning on line 1165 in src/routes.rs

View workflow job for this annotation

GitHub Actions / build (nightly, windows)

lifetime flowing from input to output with different syntax can be confusing

Check warning on line 1165 in src/routes.rs

View workflow job for this annotation

GitHub Actions / build (nightly, macos)

lifetime flowing from input to output with different syntax can be confusing

Check warning on line 1165 in src/routes.rs

View workflow job for this annotation

GitHub Actions / build (nightly, linux)

lifetime flowing from input to output with different syntax can be confusing
) -> Result<TokioMutexGuard<Option<Arc<UnlockedAppState>>>, APIError> {
let unlocked_app_state = self.get_unlocked_app_state().await;
if unlocked_app_state.is_none() {
Expand Down Expand Up @@ -2821,6 +2834,33 @@
}))
}

pub(crate) async fn node_state(
State(state): State<Arc<AppState>>,
) -> Result<Json<NodeStateResponse>, APIError> {
let mnemonic_path = get_mnemonic_path(&state.static_state.storage_dir_path);
if check_already_initialized(&mnemonic_path).is_ok() {
return Ok(Json(NodeStateResponse {
state: NodeState::None,
}));
}

if *state.get_changing_state() {
return Ok(Json(NodeStateResponse {
state: NodeState::Changing,
}));
}

if (state.check_locked().await).is_ok() {
return Ok(Json(NodeStateResponse {
state: NodeState::Locked,
}));
}

Ok(Json(NodeStateResponse {
state: NodeState::Running,
}))
}

pub(crate) async fn open_channel(
State(state): State<Arc<AppState>>,
WithRejection(Json(payload), _): WithRejection<Json<OpenChannelRequest>, APIError>,
Expand Down
25 changes: 20 additions & 5 deletions src/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ use crate::routes::{
ListPaymentsResponse, ListPeersResponse, ListSwapsResponse, ListTransactionsRequest,
ListTransactionsResponse, ListTransfersRequest, ListTransfersResponse, ListUnspentsRequest,
ListUnspentsResponse, MakerExecuteRequest, MakerInitRequest, MakerInitResponse,
NetworkInfoResponse, NodeInfoResponse, OpenChannelRequest, OpenChannelResponse, Payment, Peer,
PostAssetMediaResponse, RefreshRequest, RestoreRequest, RgbInvoiceRequest, RgbInvoiceResponse,
SendAssetRequest, SendAssetResponse, SendBtcRequest, SendBtcResponse, SendPaymentRequest,
SendPaymentResponse, Swap, SwapStatus, TakerRequest, Transaction, Transfer, UnlockRequest,
Unspent,
NetworkInfoResponse, NodeInfoResponse, NodeStateResponse, OpenChannelRequest,
OpenChannelResponse, Payment, Peer, PostAssetMediaResponse, RefreshRequest, RestoreRequest,
RgbInvoiceRequest, RgbInvoiceResponse, SendAssetRequest, SendAssetResponse, SendBtcRequest,
SendBtcResponse, SendPaymentRequest, SendPaymentResponse, Swap, SwapStatus, TakerRequest,
Transaction, Transfer, UnlockRequest, Unspent,
};
use crate::utils::{hex_str_to_vec, ELECTRUM_URL_REGTEST, PROXY_ENDPOINT_LOCAL};

Expand Down Expand Up @@ -1019,6 +1019,20 @@ async fn node_info(node_address: SocketAddr) -> NodeInfoResponse {
.unwrap()
}

async fn node_state(node_address: SocketAddr) -> NodeStateResponse {
println!("getting node state for {node_address}");
let res = reqwest::Client::new()
.get(format!("http://{}/nodestate", node_address))
.send()
.await
.unwrap();
_check_response_is_ok(res)
.await
.json::<NodeStateResponse>()
.await
.unwrap()
}

async fn open_channel(
node_address: SocketAddr,
dest_peer_pubkey: &str,
Expand Down Expand Up @@ -1676,6 +1690,7 @@ mod issue;
mod lock_unlock_changepassword;
mod multi_hop;
mod multi_open_close;
mod node_state_test;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
mod node_state_test;
mod node_state;

mod open_after_double_send;
mod openchannel_fail;
mod openchannel_optional_addr;
Expand Down
62 changes: 62 additions & 0 deletions src/test/node_state_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use crate::routes::NodeState;

use super::*;

const TEST_DIR_BASE: &str = "tmp/node_state_test/";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const TEST_DIR_BASE: &str = "tmp/node_state_test/";
const TEST_DIR_BASE: &str = "tmp/node_state/";


#[serial_test::serial]
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[traced_test]
async fn success() {
initialize();

let test_dir_node1 = format!("{TEST_DIR_BASE}node1");

if std::path::Path::new(&test_dir_node1).exists() {
std::fs::remove_dir_all(&test_dir_node1).unwrap();
}

let node1_addr = start_daemon(&test_dir_node1, NODE1_PEER_PORT).await;
let state_response = node_state(node1_addr).await;
assert!(matches!(state_response.state, NodeState::None));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use assert_eq! (here and in the rest of this test) since in case of failure it shows also the actual value that is not matching.


let password = format!("{test_dir_node1}.{NODE1_PEER_PORT}");
let payload = InitRequest {
password: password.clone(),
};
let res = reqwest::Client::new()
.post(format!("http://{}/init", node1_addr))
.json(&payload)
.send()
.await
.unwrap();
_check_response_is_ok(res)
.await
.json::<InitResponse>()
.await
.unwrap();

let state_response = node_state(node1_addr).await;
assert!(matches!(state_response.state, NodeState::Locked));

unlock(node1_addr, &password).await;

let state_response = node_state(node1_addr).await;
assert!(matches!(state_response.state, NodeState::Running));

lock(node1_addr).await;

let state_response = node_state(node1_addr).await;
assert!(matches!(state_response.state, NodeState::Locked));

unlock(node1_addr, &password).await;
let state_response = node_state(node1_addr).await;
assert!(matches!(state_response.state, NodeState::Running));

let node_info_response = node_info(node1_addr).await;
assert!(!node_info_response.pubkey.is_empty());
assert_eq!(node_info_response.num_channels, 0);
assert_eq!(node_info_response.num_peers, 0);

println!("Node state test completed successfully");
Comment on lines +52 to +61
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
unlock(node1_addr, &password).await;
let state_response = node_state(node1_addr).await;
assert!(matches!(state_response.state, NodeState::Running));
let node_info_response = node_info(node1_addr).await;
assert!(!node_info_response.pubkey.is_empty());
assert_eq!(node_info_response.num_channels, 0);
assert_eq!(node_info_response.num_peers, 0);
println!("Node state test completed successfully");
let handle = tokio::task::spawn(async move {
unlock(node1_addr, &password).await;
});
let state_response = node_state(node1_addr).await;
assert_eq!(state_response.state, NodeState::Changing);
let _ = handle.await;
let state_response = node_state(node1_addr).await;
assert_eq!(state_response.state, NodeState::Running);

}
Loading