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
14 changes: 14 additions & 0 deletions lightning-liquidity/src/lsps1/service.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -174,6 +174,20 @@ where
&self.config
}

/// Returns whether the peer currently has any active LSPS1 order flows.
///
/// An order is considered active only after we have validated the client's
/// `CreateOrder` request and replied with a `CreateOrder` response containing
/// an `order_id`.
/// Pending requests that are still awaiting our response are deliberately NOT counted.
pub(crate) fn has_active_requests(&self, counterparty_node_id: &PublicKey) -> bool {
Comment thread
tnull marked this conversation as resolved.
let outer_state_lock = self.per_peer_state.read().unwrap();
outer_state_lock.get(counterparty_node_id).map_or(false, |inner| {
let peer_state = inner.lock().unwrap();
!peer_state.outbound_channels_by_order_id.is_empty()
})
}

fn handle_get_info_request(
&self, request_id: LSPSRequestId, counterparty_node_id: &PublicKey,
) -> Result<(), LightningError> {
Expand Down
9 changes: 9 additions & 0 deletions lightning-liquidity/src/lsps2/service.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -566,6 +566,15 @@ where
&self.config
}

/// Returns whether the peer has any active LSPS2 requests.
pub(crate) fn has_active_requests(&self, counterparty_node_id: &PublicKey) -> bool {
let outer_state_lock = self.per_peer_state.read().unwrap();
outer_state_lock.get(counterparty_node_id).map_or(false, |inner| {
let peer_state = inner.lock().unwrap();
!peer_state.outbound_channels_by_intercept_scid.is_empty()
})
}

/// Used by LSP to inform a client requesting a JIT Channel the token they used is invalid.
///
/// Should be called in response to receiving a [`LSPS2ServiceEvent::GetInfo`] event.
Expand Down
3 changes: 3 additions & 0 deletions lightning-liquidity/src/lsps5/client.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -185,6 +185,9 @@ where
/// Also ensure the URL is valid, has HTTPS protocol, its length does not exceed [`MAX_WEBHOOK_URL_LENGTH`]
/// and that the URL points to a public host.
///
/// Your request may fail if you recently opened a channel or started an LSPS1 / LSPS2 flow.
/// Please retry shortly.
///
/// [`MAX_WEBHOOK_URL_LENGTH`]: super::msgs::MAX_WEBHOOK_URL_LENGTH
/// [`MAX_APP_NAME_LENGTH`]: super::msgs::MAX_APP_NAME_LENGTH
/// [`WebhookRegistered`]: super::event::LSPS5ClientEvent::WebhookRegistered
Expand Down
19 changes: 19 additions & 0 deletions lightning-liquidity/src/lsps5/msgs.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,6 +53,8 @@ pub const LSPS5_UNKNOWN_ERROR_CODE: i32 = 1000;
pub const LSPS5_SERIALIZATION_ERROR_CODE: i32 = 1001;
/// A notification was sent too frequently.
pub const LSPS5_SLOW_DOWN_ERROR_CODE: i32 = 1002;
/// A request was rejected because the client has no prior activity with the LSP (no open channel and no active LSPS1 or LSPS2 flow). The client should first open a channel
pub const LSPS5_NO_PRIOR_ACTIVITY_ERROR_CODE: i32 = 1003;

pub(crate) const LSPS5_SET_WEBHOOK_METHOD_NAME: &str = "lsps5.set_webhook";
pub(crate) const LSPS5_LIST_WEBHOOKS_METHOD_NAME: &str = "lsps5.list_webhooks";
Expand DownExpand Up@@ -113,6 +115,10 @@ pub enum LSPS5ProtocolError {
///
/// [`NOTIFICATION_COOLDOWN_TIME`]: super::service::NOTIFICATION_COOLDOWN_TIME
SlowDownError,

/// Request rejected because the client has no prior activity with the LSP (no open channel and no active LSPS1 or LSPS2 flow). The client should first open a channel
/// or initiate an LSPS1/LSPS2 interaction before retrying.
NoPriorActivityError,
}

impl LSPS5ProtocolError {
Expand All@@ -129,6 +135,7 @@ impl LSPS5ProtocolError {
LSPS5ProtocolError::UnknownError => LSPS5_UNKNOWN_ERROR_CODE,
LSPS5ProtocolError::SerializationError => LSPS5_SERIALIZATION_ERROR_CODE,
LSPS5ProtocolError::SlowDownError => LSPS5_SLOW_DOWN_ERROR_CODE,
LSPS5ProtocolError::NoPriorActivityError => LSPS5_NO_PRIOR_ACTIVITY_ERROR_CODE,
}
}
/// The error message for the LSPS5 protocol error.
Expand All@@ -145,6 +152,9 @@ impl LSPS5ProtocolError {
"Error serializing LSPS5 webhook notification"
},
LSPS5ProtocolError::SlowDownError => "Notification sent too frequently",
LSPS5ProtocolError::NoPriorActivityError => {
"Request rejected due to no prior activity with the LSP"
},
}
}
}
Expand DownExpand Up@@ -249,6 +259,9 @@ impl From<LSPSResponseError> for LSPS5ProtocolError {
LSPS5_UNSUPPORTED_PROTOCOL_ERROR_CODE => LSPS5ProtocolError::UnsupportedProtocol,
LSPS5_TOO_MANY_WEBHOOKS_ERROR_CODE => LSPS5ProtocolError::TooManyWebhooks,
LSPS5_APP_NAME_NOT_FOUND_ERROR_CODE => LSPS5ProtocolError::AppNameNotFound,
LSPS5_SERIALIZATION_ERROR_CODE => LSPS5ProtocolError::SerializationError,
LSPS5_SLOW_DOWN_ERROR_CODE => LSPS5ProtocolError::SlowDownError,
LSPS5_NO_PRIOR_ACTIVITY_ERROR_CODE => LSPS5ProtocolError::NoPriorActivityError,
_ => LSPS5ProtocolError::UnknownError,
}
}
Expand DownExpand Up@@ -640,6 +653,12 @@ pub enum LSPS5Request {
RemoveWebhook(RemoveWebhookRequest),
}

impl LSPS5Request {
pub(crate) fn is_state_allocating(&self) -> bool {
matches!(self, LSPS5Request::SetWebhook(_))
}
}

/// An LSPS5 protocol response.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LSPS5Response {
Expand Down
28 changes: 28 additions & 0 deletions lightning-liquidity/src/lsps5/service.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -149,6 +149,34 @@ where
}
}

/// Enforces the prior-activity requirement for state-allocating LSPS5 requests (e.g.
/// `lsps5.set_webhook`), rejecting and replying with `NoPriorActivityError` if not met.
pub(crate) fn enforce_prior_activity_or_reject(
&self, client_id: &PublicKey, lsps2_has_active_requests: bool, lsps1_has_activity: bool,
request_id: LSPSRequestId,
) -> Result<(), LightningError> {
let can_accept = self.client_has_open_channel(client_id)
|| lsps2_has_active_requests
|| lsps1_has_activity;

let mut message_queue_notifier = self.pending_messages.notifier();
if !can_accept {
let error = LSPS5ProtocolError::NoPriorActivityError;
let msg = LSPS5Message::Response(
request_id,
LSPS5Response::SetWebhookError(error.clone().into()),
)
.into();
message_queue_notifier.enqueue(&client_id, msg);
return Err(LightningError {
err: error.message().into(),
action: ErrorAction::IgnoreAndLog(Level::Info),
});
} else {
Ok(())
}
}

fn check_prune_stale_webhooks<'a>(
&self, outer_state_lock: &mut RwLockWriteGuard<'a, HashMap<PublicKey, PeerState>>,
) {
Expand Down
23 changes: 23 additions & 0 deletions lightning-liquidity/src/manager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -568,6 +568,29 @@ where
LSPSMessage::LSPS5(msg @ LSPS5Message::Request(..)) => {
match &self.lsps5_service_handler {
Some(lsps5_service_handler) => {
if let LSPS5Message::Request(ref req_id, ref req) = msg {
if req.is_state_allocating() {
let lsps2_has_active_requests = self
.lsps2_service_handler
.as_ref()
.map_or(false, |h| h.has_active_requests(sender_node_id));
#[cfg(lsps1_service)]
let lsps1_has_active_requests = self
.lsps1_service_handler
.as_ref()
.map_or(false, |h| h.has_active_requests(sender_node_id));
#[cfg(not(lsps1_service))]
let lsps1_has_active_requests = false;

lsps5_service_handler.enforce_prior_activity_or_reject(
sender_node_id,
lsps2_has_active_requests,
lsps1_has_active_requests,
req_id.clone(),
)?
}
}

lsps5_service_handler.handle_message(msg, sender_node_id)?;
},
None => {
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
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
14 changes: 14 additions & 0 deletions lightning-liquidity/src/lsps1/service.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -174,6 +174,20 @@ where
&self.config
}

/// Returns whether the peer currently has any active LSPS1 order flows.
///
/// An order is considered active only after we have validated the client's
/// `CreateOrder` request and replied with a `CreateOrder` response containing
/// an `order_id`.
/// Pending requests that are still awaiting our response are deliberately NOT counted.
pub(crate) fn has_active_requests(&self, counterparty_node_id: &PublicKey) -> bool {
Comment thread
tnull marked this conversation as resolved.
let outer_state_lock = self.per_peer_state.read().unwrap();
outer_state_lock.get(counterparty_node_id).map_or(false, |inner| {
let peer_state = inner.lock().unwrap();
!peer_state.outbound_channels_by_order_id.is_empty()
})
}

fn handle_get_info_request(
&self, request_id: LSPSRequestId, counterparty_node_id: &PublicKey,
) -> Result<(), LightningError> {
Expand Down
9 changes: 9 additions & 0 deletions lightning-liquidity/src/lsps2/service.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -566,6 +566,15 @@ where
&self.config
}

/// Returns whether the peer has any active LSPS2 requests.
pub(crate) fn has_active_requests(&self, counterparty_node_id: &PublicKey) -> bool {
let outer_state_lock = self.per_peer_state.read().unwrap();
outer_state_lock.get(counterparty_node_id).map_or(false, |inner| {
let peer_state = inner.lock().unwrap();
!peer_state.outbound_channels_by_intercept_scid.is_empty()
})
}

/// Used by LSP to inform a client requesting a JIT Channel the token they used is invalid.
///
/// Should be called in response to receiving a [`LSPS2ServiceEvent::GetInfo`] event.
Expand Down
3 changes: 3 additions & 0 deletions lightning-liquidity/src/lsps5/client.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -185,6 +185,9 @@ where
/// Also ensure the URL is valid, has HTTPS protocol, its length does not exceed [`MAX_WEBHOOK_URL_LENGTH`]
/// and that the URL points to a public host.
///
/// Your request may fail if you recently opened a channel or started an LSPS1 / LSPS2 flow.
/// Please retry shortly.
///
/// [`MAX_WEBHOOK_URL_LENGTH`]: super::msgs::MAX_WEBHOOK_URL_LENGTH
/// [`MAX_APP_NAME_LENGTH`]: super::msgs::MAX_APP_NAME_LENGTH
/// [`WebhookRegistered`]: super::event::LSPS5ClientEvent::WebhookRegistered
Expand Down
19 changes: 19 additions & 0 deletions lightning-liquidity/src/lsps5/msgs.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,6 +53,8 @@ pub const LSPS5_UNKNOWN_ERROR_CODE: i32 = 1000;
pub const LSPS5_SERIALIZATION_ERROR_CODE: i32 = 1001;
/// A notification was sent too frequently.
pub const LSPS5_SLOW_DOWN_ERROR_CODE: i32 = 1002;
/// A request was rejected because the client has no prior activity with the LSP (no open channel and no active LSPS1 or LSPS2 flow). The client should first open a channel
pub const LSPS5_NO_PRIOR_ACTIVITY_ERROR_CODE: i32 = 1003;

pub(crate) const LSPS5_SET_WEBHOOK_METHOD_NAME: &str = "lsps5.set_webhook";
pub(crate) const LSPS5_LIST_WEBHOOKS_METHOD_NAME: &str = "lsps5.list_webhooks";
Expand DownExpand Up@@ -113,6 +115,10 @@ pub enum LSPS5ProtocolError {
///
/// [`NOTIFICATION_COOLDOWN_TIME`]: super::service::NOTIFICATION_COOLDOWN_TIME
SlowDownError,

/// Request rejected because the client has no prior activity with the LSP (no open channel and no active LSPS1 or LSPS2 flow). The client should first open a channel
/// or initiate an LSPS1/LSPS2 interaction before retrying.
NoPriorActivityError,
}

impl LSPS5ProtocolError {
Expand All@@ -129,6 +135,7 @@ impl LSPS5ProtocolError {
LSPS5ProtocolError::UnknownError => LSPS5_UNKNOWN_ERROR_CODE,
LSPS5ProtocolError::SerializationError => LSPS5_SERIALIZATION_ERROR_CODE,
LSPS5ProtocolError::SlowDownError => LSPS5_SLOW_DOWN_ERROR_CODE,
LSPS5ProtocolError::NoPriorActivityError => LSPS5_NO_PRIOR_ACTIVITY_ERROR_CODE,
}
}
/// The error message for the LSPS5 protocol error.
Expand All@@ -145,6 +152,9 @@ impl LSPS5ProtocolError {
"Error serializing LSPS5 webhook notification"
},
LSPS5ProtocolError::SlowDownError => "Notification sent too frequently",
LSPS5ProtocolError::NoPriorActivityError => {
"Request rejected due to no prior activity with the LSP"
},
}
}
}
Expand DownExpand Up@@ -249,6 +259,9 @@ impl From<LSPSResponseError> for LSPS5ProtocolError {
LSPS5_UNSUPPORTED_PROTOCOL_ERROR_CODE => LSPS5ProtocolError::UnsupportedProtocol,
LSPS5_TOO_MANY_WEBHOOKS_ERROR_CODE => LSPS5ProtocolError::TooManyWebhooks,
LSPS5_APP_NAME_NOT_FOUND_ERROR_CODE => LSPS5ProtocolError::AppNameNotFound,
LSPS5_SERIALIZATION_ERROR_CODE => LSPS5ProtocolError::SerializationError,
LSPS5_SLOW_DOWN_ERROR_CODE => LSPS5ProtocolError::SlowDownError,
LSPS5_NO_PRIOR_ACTIVITY_ERROR_CODE => LSPS5ProtocolError::NoPriorActivityError,
_ => LSPS5ProtocolError::UnknownError,
}
}
Expand DownExpand Up@@ -640,6 +653,12 @@ pub enum LSPS5Request {
RemoveWebhook(RemoveWebhookRequest),
}

impl LSPS5Request {
pub(crate) fn is_state_allocating(&self) -> bool {
matches!(self, LSPS5Request::SetWebhook(_))
}
}

/// An LSPS5 protocol response.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LSPS5Response {
Expand Down
28 changes: 28 additions & 0 deletions lightning-liquidity/src/lsps5/service.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -149,6 +149,34 @@ where
}
}

/// Enforces the prior-activity requirement for state-allocating LSPS5 requests (e.g.
/// `lsps5.set_webhook`), rejecting and replying with `NoPriorActivityError` if not met.
pub(crate) fn enforce_prior_activity_or_reject(
&self, client_id: &PublicKey, lsps2_has_active_requests: bool, lsps1_has_activity: bool,
request_id: LSPSRequestId,
) -> Result<(), LightningError> {
let can_accept = self.client_has_open_channel(client_id)
|| lsps2_has_active_requests
|| lsps1_has_activity;

let mut message_queue_notifier = self.pending_messages.notifier();
if !can_accept {
let error = LSPS5ProtocolError::NoPriorActivityError;
let msg = LSPS5Message::Response(
request_id,
LSPS5Response::SetWebhookError(error.clone().into()),
)
.into();
message_queue_notifier.enqueue(&client_id, msg);
return Err(LightningError {
err: error.message().into(),
action: ErrorAction::IgnoreAndLog(Level::Info),
});
} else {
Ok(())
}
}

fn check_prune_stale_webhooks<'a>(
&self, outer_state_lock: &mut RwLockWriteGuard<'a, HashMap<PublicKey, PeerState>>,
) {
Expand Down
23 changes: 23 additions & 0 deletions lightning-liquidity/src/manager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -568,6 +568,29 @@ where
LSPSMessage::LSPS5(msg @ LSPS5Message::Request(..)) => {
match &self.lsps5_service_handler {
Some(lsps5_service_handler) => {
if let LSPS5Message::Request(ref req_id, ref req) = msg {
if req.is_state_allocating() {
let lsps2_has_active_requests = self
.lsps2_service_handler
.as_ref()
.map_or(false, |h| h.has_active_requests(sender_node_id));
#[cfg(lsps1_service)]
let lsps1_has_active_requests = self
.lsps1_service_handler
.as_ref()
.map_or(false, |h| h.has_active_requests(sender_node_id));
#[cfg(not(lsps1_service))]
let lsps1_has_active_requests = false;

lsps5_service_handler.enforce_prior_activity_or_reject(
sender_node_id,
lsps2_has_active_requests,
lsps1_has_active_requests,
req_id.clone(),
)?
}
}

lsps5_service_handler.handle_message(msg, sender_node_id)?;
},
None => {
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
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
14 changes: 14 additions & 0 deletions lightning-liquidity/src/lsps1/service.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -174,6 +174,20 @@ where
&self.config
}

/// Returns whether the peer currently has any active LSPS1 order flows.
///
/// An order is considered active only after we have validated the client's
/// `CreateOrder` request and replied with a `CreateOrder` response containing
/// an `order_id`.
/// Pending requests that are still awaiting our response are deliberately NOT counted.
pub(crate) fn has_active_requests(&self, counterparty_node_id: &PublicKey) -> bool {
Comment thread
tnull marked this conversation as resolved.
let outer_state_lock = self.per_peer_state.read().unwrap();
outer_state_lock.get(counterparty_node_id).map_or(false, |inner| {
let peer_state = inner.lock().unwrap();
!peer_state.outbound_channels_by_order_id.is_empty()
})
}

fn handle_get_info_request(
&self, request_id: LSPSRequestId, counterparty_node_id: &PublicKey,
) -> Result<(), LightningError> {
Expand Down
9 changes: 9 additions & 0 deletions lightning-liquidity/src/lsps2/service.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -566,6 +566,15 @@ where
&self.config
}

/// Returns whether the peer has any active LSPS2 requests.
pub(crate) fn has_active_requests(&self, counterparty_node_id: &PublicKey) -> bool {
let outer_state_lock = self.per_peer_state.read().unwrap();
outer_state_lock.get(counterparty_node_id).map_or(false, |inner| {
let peer_state = inner.lock().unwrap();
!peer_state.outbound_channels_by_intercept_scid.is_empty()
})
}

/// Used by LSP to inform a client requesting a JIT Channel the token they used is invalid.
///
/// Should be called in response to receiving a [`LSPS2ServiceEvent::GetInfo`] event.
Expand Down
3 changes: 3 additions & 0 deletions lightning-liquidity/src/lsps5/client.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -185,6 +185,9 @@ where
/// Also ensure the URL is valid, has HTTPS protocol, its length does not exceed [`MAX_WEBHOOK_URL_LENGTH`]
/// and that the URL points to a public host.
///
/// Your request may fail if you recently opened a channel or started an LSPS1 / LSPS2 flow.
/// Please retry shortly.
///
/// [`MAX_WEBHOOK_URL_LENGTH`]: super::msgs::MAX_WEBHOOK_URL_LENGTH
/// [`MAX_APP_NAME_LENGTH`]: super::msgs::MAX_APP_NAME_LENGTH
/// [`WebhookRegistered`]: super::event::LSPS5ClientEvent::WebhookRegistered
Expand Down
19 changes: 19 additions & 0 deletions lightning-liquidity/src/lsps5/msgs.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,6 +53,8 @@ pub const LSPS5_UNKNOWN_ERROR_CODE: i32 = 1000;
pub const LSPS5_SERIALIZATION_ERROR_CODE: i32 = 1001;
/// A notification was sent too frequently.
pub const LSPS5_SLOW_DOWN_ERROR_CODE: i32 = 1002;
/// A request was rejected because the client has no prior activity with the LSP (no open channel and no active LSPS1 or LSPS2 flow). The client should first open a channel
pub const LSPS5_NO_PRIOR_ACTIVITY_ERROR_CODE: i32 = 1003;

pub(crate) const LSPS5_SET_WEBHOOK_METHOD_NAME: &str = "lsps5.set_webhook";
pub(crate) const LSPS5_LIST_WEBHOOKS_METHOD_NAME: &str = "lsps5.list_webhooks";
Expand DownExpand Up@@ -113,6 +115,10 @@ pub enum LSPS5ProtocolError {
///
/// [`NOTIFICATION_COOLDOWN_TIME`]: super::service::NOTIFICATION_COOLDOWN_TIME
SlowDownError,

/// Request rejected because the client has no prior activity with the LSP (no open channel and no active LSPS1 or LSPS2 flow). The client should first open a channel
/// or initiate an LSPS1/LSPS2 interaction before retrying.
NoPriorActivityError,
}

impl LSPS5ProtocolError {
Expand All@@ -129,6 +135,7 @@ impl LSPS5ProtocolError {
LSPS5ProtocolError::UnknownError => LSPS5_UNKNOWN_ERROR_CODE,
LSPS5ProtocolError::SerializationError => LSPS5_SERIALIZATION_ERROR_CODE,
LSPS5ProtocolError::SlowDownError => LSPS5_SLOW_DOWN_ERROR_CODE,
LSPS5ProtocolError::NoPriorActivityError => LSPS5_NO_PRIOR_ACTIVITY_ERROR_CODE,
}
}
/// The error message for the LSPS5 protocol error.
Expand All@@ -145,6 +152,9 @@ impl LSPS5ProtocolError {
"Error serializing LSPS5 webhook notification"
},
LSPS5ProtocolError::SlowDownError => "Notification sent too frequently",
LSPS5ProtocolError::NoPriorActivityError => {
"Request rejected due to no prior activity with the LSP"
},
}
}
}
Expand DownExpand Up@@ -249,6 +259,9 @@ impl From<LSPSResponseError> for LSPS5ProtocolError {
LSPS5_UNSUPPORTED_PROTOCOL_ERROR_CODE => LSPS5ProtocolError::UnsupportedProtocol,
LSPS5_TOO_MANY_WEBHOOKS_ERROR_CODE => LSPS5ProtocolError::TooManyWebhooks,
LSPS5_APP_NAME_NOT_FOUND_ERROR_CODE => LSPS5ProtocolError::AppNameNotFound,
LSPS5_SERIALIZATION_ERROR_CODE => LSPS5ProtocolError::SerializationError,
LSPS5_SLOW_DOWN_ERROR_CODE => LSPS5ProtocolError::SlowDownError,
LSPS5_NO_PRIOR_ACTIVITY_ERROR_CODE => LSPS5ProtocolError::NoPriorActivityError,
_ => LSPS5ProtocolError::UnknownError,
}
}
Expand DownExpand Up@@ -640,6 +653,12 @@ pub enum LSPS5Request {
RemoveWebhook(RemoveWebhookRequest),
}

impl LSPS5Request {
pub(crate) fn is_state_allocating(&self) -> bool {
matches!(self, LSPS5Request::SetWebhook(_))
}
}

/// An LSPS5 protocol response.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LSPS5Response {
Expand Down
28 changes: 28 additions & 0 deletions lightning-liquidity/src/lsps5/service.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -149,6 +149,34 @@ where
}
}

/// Enforces the prior-activity requirement for state-allocating LSPS5 requests (e.g.
/// `lsps5.set_webhook`), rejecting and replying with `NoPriorActivityError` if not met.
pub(crate) fn enforce_prior_activity_or_reject(
&self, client_id: &PublicKey, lsps2_has_active_requests: bool, lsps1_has_activity: bool,
request_id: LSPSRequestId,
) -> Result<(), LightningError> {
let can_accept = self.client_has_open_channel(client_id)
|| lsps2_has_active_requests
|| lsps1_has_activity;

let mut message_queue_notifier = self.pending_messages.notifier();
if !can_accept {
let error = LSPS5ProtocolError::NoPriorActivityError;
let msg = LSPS5Message::Response(
request_id,
LSPS5Response::SetWebhookError(error.clone().into()),
)
.into();
message_queue_notifier.enqueue(&client_id, msg);
return Err(LightningError {
err: error.message().into(),
action: ErrorAction::IgnoreAndLog(Level::Info),
});
} else {
Ok(())
}
}

fn check_prune_stale_webhooks<'a>(
&self, outer_state_lock: &mut RwLockWriteGuard<'a, HashMap<PublicKey, PeerState>>,
) {
Expand Down
23 changes: 23 additions & 0 deletions lightning-liquidity/src/manager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -568,6 +568,29 @@ where
LSPSMessage::LSPS5(msg @ LSPS5Message::Request(..)) => {
match &self.lsps5_service_handler {
Some(lsps5_service_handler) => {
if let LSPS5Message::Request(ref req_id, ref req) = msg {
if req.is_state_allocating() {
let lsps2_has_active_requests = self
.lsps2_service_handler
.as_ref()
.map_or(false, |h| h.has_active_requests(sender_node_id));
#[cfg(lsps1_service)]
let lsps1_has_active_requests = self
.lsps1_service_handler
.as_ref()
.map_or(false, |h| h.has_active_requests(sender_node_id));
#[cfg(not(lsps1_service))]
let lsps1_has_active_requests = false;

lsps5_service_handler.enforce_prior_activity_or_reject(
sender_node_id,
lsps2_has_active_requests,
lsps1_has_active_requests,
req_id.clone(),
)?
}
}

lsps5_service_handler.handle_message(msg, sender_node_id)?;
},
None => {
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
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
14 changes: 14 additions & 0 deletions lightning-liquidity/src/lsps1/service.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -174,6 +174,20 @@ where
&self.config
}

/// Returns whether the peer currently has any active LSPS1 order flows.
///
/// An order is considered active only after we have validated the client's
/// `CreateOrder` request and replied with a `CreateOrder` response containing
/// an `order_id`.
/// Pending requests that are still awaiting our response are deliberately NOT counted.
pub(crate) fn has_active_requests(&self, counterparty_node_id: &PublicKey) -> bool {
Comment thread
tnull marked this conversation as resolved.
let outer_state_lock = self.per_peer_state.read().unwrap();
outer_state_lock.get(counterparty_node_id).map_or(false, |inner| {
let peer_state = inner.lock().unwrap();
!peer_state.outbound_channels_by_order_id.is_empty()
})
}

fn handle_get_info_request(
&self, request_id: LSPSRequestId, counterparty_node_id: &PublicKey,
) -> Result<(), LightningError> {
Expand Down
9 changes: 9 additions & 0 deletions lightning-liquidity/src/lsps2/service.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -566,6 +566,15 @@ where
&self.config
}

/// Returns whether the peer has any active LSPS2 requests.
pub(crate) fn has_active_requests(&self, counterparty_node_id: &PublicKey) -> bool {
let outer_state_lock = self.per_peer_state.read().unwrap();
outer_state_lock.get(counterparty_node_id).map_or(false, |inner| {
let peer_state = inner.lock().unwrap();
!peer_state.outbound_channels_by_intercept_scid.is_empty()
})
}

/// Used by LSP to inform a client requesting a JIT Channel the token they used is invalid.
///
/// Should be called in response to receiving a [`LSPS2ServiceEvent::GetInfo`] event.
Expand Down
3 changes: 3 additions & 0 deletions lightning-liquidity/src/lsps5/client.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -185,6 +185,9 @@ where
/// Also ensure the URL is valid, has HTTPS protocol, its length does not exceed [`MAX_WEBHOOK_URL_LENGTH`]
/// and that the URL points to a public host.
///
/// Your request may fail if you recently opened a channel or started an LSPS1 / LSPS2 flow.
/// Please retry shortly.
///
/// [`MAX_WEBHOOK_URL_LENGTH`]: super::msgs::MAX_WEBHOOK_URL_LENGTH
/// [`MAX_APP_NAME_LENGTH`]: super::msgs::MAX_APP_NAME_LENGTH
/// [`WebhookRegistered`]: super::event::LSPS5ClientEvent::WebhookRegistered
Expand Down
19 changes: 19 additions & 0 deletions lightning-liquidity/src/lsps5/msgs.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,6 +53,8 @@ pub const LSPS5_UNKNOWN_ERROR_CODE: i32 = 1000;
pub const LSPS5_SERIALIZATION_ERROR_CODE: i32 = 1001;
/// A notification was sent too frequently.
pub const LSPS5_SLOW_DOWN_ERROR_CODE: i32 = 1002;
/// A request was rejected because the client has no prior activity with the LSP (no open channel and no active LSPS1 or LSPS2 flow). The client should first open a channel
pub const LSPS5_NO_PRIOR_ACTIVITY_ERROR_CODE: i32 = 1003;

pub(crate) const LSPS5_SET_WEBHOOK_METHOD_NAME: &str = "lsps5.set_webhook";
pub(crate) const LSPS5_LIST_WEBHOOKS_METHOD_NAME: &str = "lsps5.list_webhooks";
Expand DownExpand Up@@ -113,6 +115,10 @@ pub enum LSPS5ProtocolError {
///
/// [`NOTIFICATION_COOLDOWN_TIME`]: super::service::NOTIFICATION_COOLDOWN_TIME
SlowDownError,

/// Request rejected because the client has no prior activity with the LSP (no open channel and no active LSPS1 or LSPS2 flow). The client should first open a channel
/// or initiate an LSPS1/LSPS2 interaction before retrying.
NoPriorActivityError,
}

impl LSPS5ProtocolError {
Expand All@@ -129,6 +135,7 @@ impl LSPS5ProtocolError {
LSPS5ProtocolError::UnknownError => LSPS5_UNKNOWN_ERROR_CODE,
LSPS5ProtocolError::SerializationError => LSPS5_SERIALIZATION_ERROR_CODE,
LSPS5ProtocolError::SlowDownError => LSPS5_SLOW_DOWN_ERROR_CODE,
LSPS5ProtocolError::NoPriorActivityError => LSPS5_NO_PRIOR_ACTIVITY_ERROR_CODE,
}
}
/// The error message for the LSPS5 protocol error.
Expand All@@ -145,6 +152,9 @@ impl LSPS5ProtocolError {
"Error serializing LSPS5 webhook notification"
},
LSPS5ProtocolError::SlowDownError => "Notification sent too frequently",
LSPS5ProtocolError::NoPriorActivityError => {
"Request rejected due to no prior activity with the LSP"
},
}
}
}
Expand DownExpand Up@@ -249,6 +259,9 @@ impl From<LSPSResponseError> for LSPS5ProtocolError {
LSPS5_UNSUPPORTED_PROTOCOL_ERROR_CODE => LSPS5ProtocolError::UnsupportedProtocol,
LSPS5_TOO_MANY_WEBHOOKS_ERROR_CODE => LSPS5ProtocolError::TooManyWebhooks,
LSPS5_APP_NAME_NOT_FOUND_ERROR_CODE => LSPS5ProtocolError::AppNameNotFound,
LSPS5_SERIALIZATION_ERROR_CODE => LSPS5ProtocolError::SerializationError,
LSPS5_SLOW_DOWN_ERROR_CODE => LSPS5ProtocolError::SlowDownError,
LSPS5_NO_PRIOR_ACTIVITY_ERROR_CODE => LSPS5ProtocolError::NoPriorActivityError,
_ => LSPS5ProtocolError::UnknownError,
}
}
Expand DownExpand Up@@ -640,6 +653,12 @@ pub enum LSPS5Request {
RemoveWebhook(RemoveWebhookRequest),
}

impl LSPS5Request {
pub(crate) fn is_state_allocating(&self) -> bool {
matches!(self, LSPS5Request::SetWebhook(_))
}
}

/// An LSPS5 protocol response.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LSPS5Response {
Expand Down
28 changes: 28 additions & 0 deletions lightning-liquidity/src/lsps5/service.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -149,6 +149,34 @@ where
}
}

/// Enforces the prior-activity requirement for state-allocating LSPS5 requests (e.g.
/// `lsps5.set_webhook`), rejecting and replying with `NoPriorActivityError` if not met.
pub(crate) fn enforce_prior_activity_or_reject(
&self, client_id: &PublicKey, lsps2_has_active_requests: bool, lsps1_has_activity: bool,
request_id: LSPSRequestId,
) -> Result<(), LightningError> {
let can_accept = self.client_has_open_channel(client_id)
|| lsps2_has_active_requests
|| lsps1_has_activity;

let mut message_queue_notifier = self.pending_messages.notifier();
if !can_accept {
let error = LSPS5ProtocolError::NoPriorActivityError;
let msg = LSPS5Message::Response(
request_id,
LSPS5Response::SetWebhookError(error.clone().into()),
)
.into();
message_queue_notifier.enqueue(&client_id, msg);
return Err(LightningError {
err: error.message().into(),
action: ErrorAction::IgnoreAndLog(Level::Info),
});
} else {
Ok(())
}
}

fn check_prune_stale_webhooks<'a>(
&self, outer_state_lock: &mut RwLockWriteGuard<'a, HashMap<PublicKey, PeerState>>,
) {
Expand Down
23 changes: 23 additions & 0 deletions lightning-liquidity/src/manager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -568,6 +568,29 @@ where
LSPSMessage::LSPS5(msg @ LSPS5Message::Request(..)) => {
match &self.lsps5_service_handler {
Some(lsps5_service_handler) => {
if let LSPS5Message::Request(ref req_id, ref req) = msg {
if req.is_state_allocating() {
let lsps2_has_active_requests = self
.lsps2_service_handler
.as_ref()
.map_or(false, |h| h.has_active_requests(sender_node_id));
#[cfg(lsps1_service)]
let lsps1_has_active_requests = self
.lsps1_service_handler
.as_ref()
.map_or(false, |h| h.has_active_requests(sender_node_id));
#[cfg(not(lsps1_service))]
let lsps1_has_active_requests = false;

lsps5_service_handler.enforce_prior_activity_or_reject(
sender_node_id,
lsps2_has_active_requests,
lsps1_has_active_requests,
req_id.clone(),
)?
}
}

lsps5_service_handler.handle_message(msg, sender_node_id)?;
},
None => {
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
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
14 changes: 14 additions & 0 deletions lightning-liquidity/src/lsps1/service.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -174,6 +174,20 @@ where
&self.config
}

/// Returns whether the peer currently has any active LSPS1 order flows.
///
/// An order is considered active only after we have validated the client's
/// `CreateOrder` request and replied with a `CreateOrder` response containing
/// an `order_id`.
/// Pending requests that are still awaiting our response are deliberately NOT counted.
pub(crate) fn has_active_requests(&self, counterparty_node_id: &PublicKey) -> bool {
Comment thread
tnull marked this conversation as resolved.
let outer_state_lock = self.per_peer_state.read().unwrap();
outer_state_lock.get(counterparty_node_id).map_or(false, |inner| {
let peer_state = inner.lock().unwrap();
!peer_state.outbound_channels_by_order_id.is_empty()
})
}

fn handle_get_info_request(
&self, request_id: LSPSRequestId, counterparty_node_id: &PublicKey,
) -> Result<(), LightningError> {
Expand Down
9 changes: 9 additions & 0 deletions lightning-liquidity/src/lsps2/service.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -566,6 +566,15 @@ where
&self.config
}

/// Returns whether the peer has any active LSPS2 requests.
pub(crate) fn has_active_requests(&self, counterparty_node_id: &PublicKey) -> bool {
let outer_state_lock = self.per_peer_state.read().unwrap();
outer_state_lock.get(counterparty_node_id).map_or(false, |inner| {
let peer_state = inner.lock().unwrap();
!peer_state.outbound_channels_by_intercept_scid.is_empty()
})
}

/// Used by LSP to inform a client requesting a JIT Channel the token they used is invalid.
///
/// Should be called in response to receiving a [`LSPS2ServiceEvent::GetInfo`] event.
Expand Down
3 changes: 3 additions & 0 deletions lightning-liquidity/src/lsps5/client.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -185,6 +185,9 @@ where
/// Also ensure the URL is valid, has HTTPS protocol, its length does not exceed [`MAX_WEBHOOK_URL_LENGTH`]
/// and that the URL points to a public host.
///
/// Your request may fail if you recently opened a channel or started an LSPS1 / LSPS2 flow.
/// Please retry shortly.
///
/// [`MAX_WEBHOOK_URL_LENGTH`]: super::msgs::MAX_WEBHOOK_URL_LENGTH
/// [`MAX_APP_NAME_LENGTH`]: super::msgs::MAX_APP_NAME_LENGTH
/// [`WebhookRegistered`]: super::event::LSPS5ClientEvent::WebhookRegistered
Expand Down
19 changes: 19 additions & 0 deletions lightning-liquidity/src/lsps5/msgs.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,6 +53,8 @@ pub const LSPS5_UNKNOWN_ERROR_CODE: i32 = 1000;
pub const LSPS5_SERIALIZATION_ERROR_CODE: i32 = 1001;
/// A notification was sent too frequently.
pub const LSPS5_SLOW_DOWN_ERROR_CODE: i32 = 1002;
/// A request was rejected because the client has no prior activity with the LSP (no open channel and no active LSPS1 or LSPS2 flow). The client should first open a channel
pub const LSPS5_NO_PRIOR_ACTIVITY_ERROR_CODE: i32 = 1003;

pub(crate) const LSPS5_SET_WEBHOOK_METHOD_NAME: &str = "lsps5.set_webhook";
pub(crate) const LSPS5_LIST_WEBHOOKS_METHOD_NAME: &str = "lsps5.list_webhooks";
Expand DownExpand Up@@ -113,6 +115,10 @@ pub enum LSPS5ProtocolError {
///
/// [`NOTIFICATION_COOLDOWN_TIME`]: super::service::NOTIFICATION_COOLDOWN_TIME
SlowDownError,

/// Request rejected because the client has no prior activity with the LSP (no open channel and no active LSPS1 or LSPS2 flow). The client should first open a channel
/// or initiate an LSPS1/LSPS2 interaction before retrying.
NoPriorActivityError,
}

impl LSPS5ProtocolError {
Expand All@@ -129,6 +135,7 @@ impl LSPS5ProtocolError {
LSPS5ProtocolError::UnknownError => LSPS5_UNKNOWN_ERROR_CODE,
LSPS5ProtocolError::SerializationError => LSPS5_SERIALIZATION_ERROR_CODE,
LSPS5ProtocolError::SlowDownError => LSPS5_SLOW_DOWN_ERROR_CODE,
LSPS5ProtocolError::NoPriorActivityError => LSPS5_NO_PRIOR_ACTIVITY_ERROR_CODE,
}
}
/// The error message for the LSPS5 protocol error.
Expand All@@ -145,6 +152,9 @@ impl LSPS5ProtocolError {
"Error serializing LSPS5 webhook notification"
},
LSPS5ProtocolError::SlowDownError => "Notification sent too frequently",
LSPS5ProtocolError::NoPriorActivityError => {
"Request rejected due to no prior activity with the LSP"
},
}
}
}
Expand DownExpand Up@@ -249,6 +259,9 @@ impl From<LSPSResponseError> for LSPS5ProtocolError {
LSPS5_UNSUPPORTED_PROTOCOL_ERROR_CODE => LSPS5ProtocolError::UnsupportedProtocol,
LSPS5_TOO_MANY_WEBHOOKS_ERROR_CODE => LSPS5ProtocolError::TooManyWebhooks,
LSPS5_APP_NAME_NOT_FOUND_ERROR_CODE => LSPS5ProtocolError::AppNameNotFound,
LSPS5_SERIALIZATION_ERROR_CODE => LSPS5ProtocolError::SerializationError,
LSPS5_SLOW_DOWN_ERROR_CODE => LSPS5ProtocolError::SlowDownError,
LSPS5_NO_PRIOR_ACTIVITY_ERROR_CODE => LSPS5ProtocolError::NoPriorActivityError,
_ => LSPS5ProtocolError::UnknownError,
}
}
Expand DownExpand Up@@ -640,6 +653,12 @@ pub enum LSPS5Request {
RemoveWebhook(RemoveWebhookRequest),
}

impl LSPS5Request {
pub(crate) fn is_state_allocating(&self) -> bool {
matches!(self, LSPS5Request::SetWebhook(_))
}
}

/// An LSPS5 protocol response.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LSPS5Response {
Expand Down
28 changes: 28 additions & 0 deletions lightning-liquidity/src/lsps5/service.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -149,6 +149,34 @@ where
}
}

/// Enforces the prior-activity requirement for state-allocating LSPS5 requests (e.g.
/// `lsps5.set_webhook`), rejecting and replying with `NoPriorActivityError` if not met.
pub(crate) fn enforce_prior_activity_or_reject(
&self, client_id: &PublicKey, lsps2_has_active_requests: bool, lsps1_has_activity: bool,
request_id: LSPSRequestId,
) -> Result<(), LightningError> {
let can_accept = self.client_has_open_channel(client_id)
|| lsps2_has_active_requests
|| lsps1_has_activity;

let mut message_queue_notifier = self.pending_messages.notifier();
if !can_accept {
let error = LSPS5ProtocolError::NoPriorActivityError;
let msg = LSPS5Message::Response(
request_id,
LSPS5Response::SetWebhookError(error.clone().into()),
)
.into();
message_queue_notifier.enqueue(&client_id, msg);
return Err(LightningError {
err: error.message().into(),
action: ErrorAction::IgnoreAndLog(Level::Info),
});
} else {
Ok(())
}
}

fn check_prune_stale_webhooks<'a>(
&self, outer_state_lock: &mut RwLockWriteGuard<'a, HashMap<PublicKey, PeerState>>,
) {
Expand Down
23 changes: 23 additions & 0 deletions lightning-liquidity/src/manager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -568,6 +568,29 @@ where
LSPSMessage::LSPS5(msg @ LSPS5Message::Request(..)) => {
match &self.lsps5_service_handler {
Some(lsps5_service_handler) => {
if let LSPS5Message::Request(ref req_id, ref req) = msg {
if req.is_state_allocating() {
let lsps2_has_active_requests = self
.lsps2_service_handler
.as_ref()
.map_or(false, |h| h.has_active_requests(sender_node_id));
#[cfg(lsps1_service)]
let lsps1_has_active_requests = self
.lsps1_service_handler
.as_ref()
.map_or(false, |h| h.has_active_requests(sender_node_id));
#[cfg(not(lsps1_service))]
let lsps1_has_active_requests = false;

lsps5_service_handler.enforce_prior_activity_or_reject(
sender_node_id,
lsps2_has_active_requests,
lsps1_has_active_requests,
req_id.clone(),
)?
}
}

lsps5_service_handler.handle_message(msg, sender_node_id)?;
},
None => {
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
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
14 changes: 14 additions & 0 deletions lightning-liquidity/src/lsps1/service.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -174,6 +174,20 @@ where
&self.config
}

/// Returns whether the peer currently has any active LSPS1 order flows.
///
/// An order is considered active only after we have validated the client's
/// `CreateOrder` request and replied with a `CreateOrder` response containing
/// an `order_id`.
/// Pending requests that are still awaiting our response are deliberately NOT counted.
pub(crate) fn has_active_requests(&self, counterparty_node_id: &PublicKey) -> bool {
Comment thread
tnull marked this conversation as resolved.
let outer_state_lock = self.per_peer_state.read().unwrap();
outer_state_lock.get(counterparty_node_id).map_or(false, |inner| {
let peer_state = inner.lock().unwrap();
!peer_state.outbound_channels_by_order_id.is_empty()
})
}

fn handle_get_info_request(
&self, request_id: LSPSRequestId, counterparty_node_id: &PublicKey,
) -> Result<(), LightningError> {
Expand Down
9 changes: 9 additions & 0 deletions lightning-liquidity/src/lsps2/service.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -566,6 +566,15 @@ where
&self.config
}

/// Returns whether the peer has any active LSPS2 requests.
pub(crate) fn has_active_requests(&self, counterparty_node_id: &PublicKey) -> bool {
let outer_state_lock = self.per_peer_state.read().unwrap();
outer_state_lock.get(counterparty_node_id).map_or(false, |inner| {
let peer_state = inner.lock().unwrap();
!peer_state.outbound_channels_by_intercept_scid.is_empty()
})
}

/// Used by LSP to inform a client requesting a JIT Channel the token they used is invalid.
///
/// Should be called in response to receiving a [`LSPS2ServiceEvent::GetInfo`] event.
Expand Down
3 changes: 3 additions & 0 deletions lightning-liquidity/src/lsps5/client.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -185,6 +185,9 @@ where
/// Also ensure the URL is valid, has HTTPS protocol, its length does not exceed [`MAX_WEBHOOK_URL_LENGTH`]
/// and that the URL points to a public host.
///
/// Your request may fail if you recently opened a channel or started an LSPS1 / LSPS2 flow.
/// Please retry shortly.
///
/// [`MAX_WEBHOOK_URL_LENGTH`]: super::msgs::MAX_WEBHOOK_URL_LENGTH
/// [`MAX_APP_NAME_LENGTH`]: super::msgs::MAX_APP_NAME_LENGTH
/// [`WebhookRegistered`]: super::event::LSPS5ClientEvent::WebhookRegistered
Expand Down
19 changes: 19 additions & 0 deletions lightning-liquidity/src/lsps5/msgs.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,6 +53,8 @@ pub const LSPS5_UNKNOWN_ERROR_CODE: i32 = 1000;
pub const LSPS5_SERIALIZATION_ERROR_CODE: i32 = 1001;
/// A notification was sent too frequently.
pub const LSPS5_SLOW_DOWN_ERROR_CODE: i32 = 1002;
/// A request was rejected because the client has no prior activity with the LSP (no open channel and no active LSPS1 or LSPS2 flow). The client should first open a channel
pub const LSPS5_NO_PRIOR_ACTIVITY_ERROR_CODE: i32 = 1003;

pub(crate) const LSPS5_SET_WEBHOOK_METHOD_NAME: &str = "lsps5.set_webhook";
pub(crate) const LSPS5_LIST_WEBHOOKS_METHOD_NAME: &str = "lsps5.list_webhooks";
Expand DownExpand Up@@ -113,6 +115,10 @@ pub enum LSPS5ProtocolError {
///
/// [`NOTIFICATION_COOLDOWN_TIME`]: super::service::NOTIFICATION_COOLDOWN_TIME
SlowDownError,

/// Request rejected because the client has no prior activity with the LSP (no open channel and no active LSPS1 or LSPS2 flow). The client should first open a channel
/// or initiate an LSPS1/LSPS2 interaction before retrying.
NoPriorActivityError,
}

impl LSPS5ProtocolError {
Expand All@@ -129,6 +135,7 @@ impl LSPS5ProtocolError {
LSPS5ProtocolError::UnknownError => LSPS5_UNKNOWN_ERROR_CODE,
LSPS5ProtocolError::SerializationError => LSPS5_SERIALIZATION_ERROR_CODE,
LSPS5ProtocolError::SlowDownError => LSPS5_SLOW_DOWN_ERROR_CODE,
LSPS5ProtocolError::NoPriorActivityError => LSPS5_NO_PRIOR_ACTIVITY_ERROR_CODE,
}
}
/// The error message for the LSPS5 protocol error.
Expand All@@ -145,6 +152,9 @@ impl LSPS5ProtocolError {
"Error serializing LSPS5 webhook notification"
},
LSPS5ProtocolError::SlowDownError => "Notification sent too frequently",
LSPS5ProtocolError::NoPriorActivityError => {
"Request rejected due to no prior activity with the LSP"
},
}
}
}
Expand DownExpand Up@@ -249,6 +259,9 @@ impl From<LSPSResponseError> for LSPS5ProtocolError {
LSPS5_UNSUPPORTED_PROTOCOL_ERROR_CODE => LSPS5ProtocolError::UnsupportedProtocol,
LSPS5_TOO_MANY_WEBHOOKS_ERROR_CODE => LSPS5ProtocolError::TooManyWebhooks,
LSPS5_APP_NAME_NOT_FOUND_ERROR_CODE => LSPS5ProtocolError::AppNameNotFound,
LSPS5_SERIALIZATION_ERROR_CODE => LSPS5ProtocolError::SerializationError,
LSPS5_SLOW_DOWN_ERROR_CODE => LSPS5ProtocolError::SlowDownError,
LSPS5_NO_PRIOR_ACTIVITY_ERROR_CODE => LSPS5ProtocolError::NoPriorActivityError,
_ => LSPS5ProtocolError::UnknownError,
}
}
Expand DownExpand Up@@ -640,6 +653,12 @@ pub enum LSPS5Request {
RemoveWebhook(RemoveWebhookRequest),
}

impl LSPS5Request {
pub(crate) fn is_state_allocating(&self) -> bool {
matches!(self, LSPS5Request::SetWebhook(_))
}
}

/// An LSPS5 protocol response.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LSPS5Response {
Expand Down
28 changes: 28 additions & 0 deletions lightning-liquidity/src/lsps5/service.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -149,6 +149,34 @@ where
}
}

/// Enforces the prior-activity requirement for state-allocating LSPS5 requests (e.g.
/// `lsps5.set_webhook`), rejecting and replying with `NoPriorActivityError` if not met.
pub(crate) fn enforce_prior_activity_or_reject(
&self, client_id: &PublicKey, lsps2_has_active_requests: bool, lsps1_has_activity: bool,
request_id: LSPSRequestId,
) -> Result<(), LightningError> {
let can_accept = self.client_has_open_channel(client_id)
|| lsps2_has_active_requests
|| lsps1_has_activity;

let mut message_queue_notifier = self.pending_messages.notifier();
if !can_accept {
let error = LSPS5ProtocolError::NoPriorActivityError;
let msg = LSPS5Message::Response(
request_id,
LSPS5Response::SetWebhookError(error.clone().into()),
)
.into();
message_queue_notifier.enqueue(&client_id, msg);
return Err(LightningError {
err: error.message().into(),
action: ErrorAction::IgnoreAndLog(Level::Info),
});
} else {
Ok(())
}
}

fn check_prune_stale_webhooks<'a>(
&self, outer_state_lock: &mut RwLockWriteGuard<'a, HashMap<PublicKey, PeerState>>,
) {
Expand Down
23 changes: 23 additions & 0 deletions lightning-liquidity/src/manager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -568,6 +568,29 @@ where
LSPSMessage::LSPS5(msg @ LSPS5Message::Request(..)) => {
match &self.lsps5_service_handler {
Some(lsps5_service_handler) => {
if let LSPS5Message::Request(ref req_id, ref req) = msg {
if req.is_state_allocating() {
let lsps2_has_active_requests = self
.lsps2_service_handler
.as_ref()
.map_or(false, |h| h.has_active_requests(sender_node_id));
#[cfg(lsps1_service)]
let lsps1_has_active_requests = self
.lsps1_service_handler
.as_ref()
.map_or(false, |h| h.has_active_requests(sender_node_id));
#[cfg(not(lsps1_service))]
let lsps1_has_active_requests = false;

lsps5_service_handler.enforce_prior_activity_or_reject(
sender_node_id,
lsps2_has_active_requests,
lsps1_has_active_requests,
req_id.clone(),
)?
}
}

lsps5_service_handler.handle_message(msg, sender_node_id)?;
},
None => {
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
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
14 changes: 14 additions & 0 deletions lightning-liquidity/src/lsps1/service.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -174,6 +174,20 @@ where
&self.config
}

/// Returns whether the peer currently has any active LSPS1 order flows.
///
/// An order is considered active only after we have validated the client's
/// `CreateOrder` request and replied with a `CreateOrder` response containing
/// an `order_id`.
/// Pending requests that are still awaiting our response are deliberately NOT counted.
pub(crate) fn has_active_requests(&self, counterparty_node_id: &PublicKey) -> bool {
Comment thread
tnull marked this conversation as resolved.
let outer_state_lock = self.per_peer_state.read().unwrap();
outer_state_lock.get(counterparty_node_id).map_or(false, |inner| {
let peer_state = inner.lock().unwrap();
!peer_state.outbound_channels_by_order_id.is_empty()
})
}

fn handle_get_info_request(
&self, request_id: LSPSRequestId, counterparty_node_id: &PublicKey,
) -> Result<(), LightningError> {
Expand Down
9 changes: 9 additions & 0 deletions lightning-liquidity/src/lsps2/service.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -566,6 +566,15 @@ where
&self.config
}

/// Returns whether the peer has any active LSPS2 requests.
pub(crate) fn has_active_requests(&self, counterparty_node_id: &PublicKey) -> bool {
let outer_state_lock = self.per_peer_state.read().unwrap();
outer_state_lock.get(counterparty_node_id).map_or(false, |inner| {
let peer_state = inner.lock().unwrap();
!peer_state.outbound_channels_by_intercept_scid.is_empty()
})
}

/// Used by LSP to inform a client requesting a JIT Channel the token they used is invalid.
///
/// Should be called in response to receiving a [`LSPS2ServiceEvent::GetInfo`] event.
Expand Down
3 changes: 3 additions & 0 deletions lightning-liquidity/src/lsps5/client.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -185,6 +185,9 @@ where
/// Also ensure the URL is valid, has HTTPS protocol, its length does not exceed [`MAX_WEBHOOK_URL_LENGTH`]
/// and that the URL points to a public host.
///
/// Your request may fail if you recently opened a channel or started an LSPS1 / LSPS2 flow.
/// Please retry shortly.
///
/// [`MAX_WEBHOOK_URL_LENGTH`]: super::msgs::MAX_WEBHOOK_URL_LENGTH
/// [`MAX_APP_NAME_LENGTH`]: super::msgs::MAX_APP_NAME_LENGTH
/// [`WebhookRegistered`]: super::event::LSPS5ClientEvent::WebhookRegistered
Expand Down
19 changes: 19 additions & 0 deletions lightning-liquidity/src/lsps5/msgs.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,6 +53,8 @@ pub const LSPS5_UNKNOWN_ERROR_CODE: i32 = 1000;
pub const LSPS5_SERIALIZATION_ERROR_CODE: i32 = 1001;
/// A notification was sent too frequently.
pub const LSPS5_SLOW_DOWN_ERROR_CODE: i32 = 1002;
/// A request was rejected because the client has no prior activity with the LSP (no open channel and no active LSPS1 or LSPS2 flow). The client should first open a channel
pub const LSPS5_NO_PRIOR_ACTIVITY_ERROR_CODE: i32 = 1003;

pub(crate) const LSPS5_SET_WEBHOOK_METHOD_NAME: &str = "lsps5.set_webhook";
pub(crate) const LSPS5_LIST_WEBHOOKS_METHOD_NAME: &str = "lsps5.list_webhooks";
Expand DownExpand Up@@ -113,6 +115,10 @@ pub enum LSPS5ProtocolError {
///
/// [`NOTIFICATION_COOLDOWN_TIME`]: super::service::NOTIFICATION_COOLDOWN_TIME
SlowDownError,

/// Request rejected because the client has no prior activity with the LSP (no open channel and no active LSPS1 or LSPS2 flow). The client should first open a channel
/// or initiate an LSPS1/LSPS2 interaction before retrying.
NoPriorActivityError,
}

impl LSPS5ProtocolError {
Expand All@@ -129,6 +135,7 @@ impl LSPS5ProtocolError {
LSPS5ProtocolError::UnknownError => LSPS5_UNKNOWN_ERROR_CODE,
LSPS5ProtocolError::SerializationError => LSPS5_SERIALIZATION_ERROR_CODE,
LSPS5ProtocolError::SlowDownError => LSPS5_SLOW_DOWN_ERROR_CODE,
LSPS5ProtocolError::NoPriorActivityError => LSPS5_NO_PRIOR_ACTIVITY_ERROR_CODE,
}
}
/// The error message for the LSPS5 protocol error.
Expand All@@ -145,6 +152,9 @@ impl LSPS5ProtocolError {
"Error serializing LSPS5 webhook notification"
},
LSPS5ProtocolError::SlowDownError => "Notification sent too frequently",
LSPS5ProtocolError::NoPriorActivityError => {
"Request rejected due to no prior activity with the LSP"
},
}
}
}
Expand DownExpand Up@@ -249,6 +259,9 @@ impl From<LSPSResponseError> for LSPS5ProtocolError {
LSPS5_UNSUPPORTED_PROTOCOL_ERROR_CODE => LSPS5ProtocolError::UnsupportedProtocol,
LSPS5_TOO_MANY_WEBHOOKS_ERROR_CODE => LSPS5ProtocolError::TooManyWebhooks,
LSPS5_APP_NAME_NOT_FOUND_ERROR_CODE => LSPS5ProtocolError::AppNameNotFound,
LSPS5_SERIALIZATION_ERROR_CODE => LSPS5ProtocolError::SerializationError,
LSPS5_SLOW_DOWN_ERROR_CODE => LSPS5ProtocolError::SlowDownError,
LSPS5_NO_PRIOR_ACTIVITY_ERROR_CODE => LSPS5ProtocolError::NoPriorActivityError,
_ => LSPS5ProtocolError::UnknownError,
}
}
Expand DownExpand Up@@ -640,6 +653,12 @@ pub enum LSPS5Request {
RemoveWebhook(RemoveWebhookRequest),
}

impl LSPS5Request {
pub(crate) fn is_state_allocating(&self) -> bool {
matches!(self, LSPS5Request::SetWebhook(_))
}
}

/// An LSPS5 protocol response.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LSPS5Response {
Expand Down
28 changes: 28 additions & 0 deletions lightning-liquidity/src/lsps5/service.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -149,6 +149,34 @@ where
}
}

/// Enforces the prior-activity requirement for state-allocating LSPS5 requests (e.g.
/// `lsps5.set_webhook`), rejecting and replying with `NoPriorActivityError` if not met.
pub(crate) fn enforce_prior_activity_or_reject(
&self, client_id: &PublicKey, lsps2_has_active_requests: bool, lsps1_has_activity: bool,
request_id: LSPSRequestId,
) -> Result<(), LightningError> {
let can_accept = self.client_has_open_channel(client_id)
|| lsps2_has_active_requests
|| lsps1_has_activity;

let mut message_queue_notifier = self.pending_messages.notifier();
if !can_accept {
let error = LSPS5ProtocolError::NoPriorActivityError;
let msg = LSPS5Message::Response(
request_id,
LSPS5Response::SetWebhookError(error.clone().into()),
)
.into();
message_queue_notifier.enqueue(&client_id, msg);
return Err(LightningError {
err: error.message().into(),
action: ErrorAction::IgnoreAndLog(Level::Info),
});
} else {
Ok(())
}
}

fn check_prune_stale_webhooks<'a>(
&self, outer_state_lock: &mut RwLockWriteGuard<'a, HashMap<PublicKey, PeerState>>,
) {
Expand Down
23 changes: 23 additions & 0 deletions lightning-liquidity/src/manager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -568,6 +568,29 @@ where
LSPSMessage::LSPS5(msg @ LSPS5Message::Request(..)) => {
match &self.lsps5_service_handler {
Some(lsps5_service_handler) => {
if let LSPS5Message::Request(ref req_id, ref req) = msg {
if req.is_state_allocating() {
let lsps2_has_active_requests = self
.lsps2_service_handler
.as_ref()
.map_or(false, |h| h.has_active_requests(sender_node_id));
#[cfg(lsps1_service)]
let lsps1_has_active_requests = self
.lsps1_service_handler
.as_ref()
.map_or(false, |h| h.has_active_requests(sender_node_id));
#[cfg(not(lsps1_service))]
let lsps1_has_active_requests = false;

lsps5_service_handler.enforce_prior_activity_or_reject(
sender_node_id,
lsps2_has_active_requests,
lsps1_has_active_requests,
req_id.clone(),
)?
}
}

lsps5_service_handler.handle_message(msg, sender_node_id)?;
},
None => {
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
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
14 changes: 14 additions & 0 deletions lightning-liquidity/src/lsps1/service.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -174,6 +174,20 @@ where
&self.config
}

/// Returns whether the peer currently has any active LSPS1 order flows.
///
/// An order is considered active only after we have validated the client's
/// `CreateOrder` request and replied with a `CreateOrder` response containing
/// an `order_id`.
/// Pending requests that are still awaiting our response are deliberately NOT counted.
pub(crate) fn has_active_requests(&self, counterparty_node_id: &PublicKey) -> bool {
Comment thread
tnull marked this conversation as resolved.
let outer_state_lock = self.per_peer_state.read().unwrap();
outer_state_lock.get(counterparty_node_id).map_or(false, |inner| {
let peer_state = inner.lock().unwrap();
!peer_state.outbound_channels_by_order_id.is_empty()
})
}

fn handle_get_info_request(
&self, request_id: LSPSRequestId, counterparty_node_id: &PublicKey,
) -> Result<(), LightningError> {
Expand Down
9 changes: 9 additions & 0 deletions lightning-liquidity/src/lsps2/service.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -566,6 +566,15 @@ where
&self.config
}

/// Returns whether the peer has any active LSPS2 requests.
pub(crate) fn has_active_requests(&self, counterparty_node_id: &PublicKey) -> bool {
let outer_state_lock = self.per_peer_state.read().unwrap();
outer_state_lock.get(counterparty_node_id).map_or(false, |inner| {
let peer_state = inner.lock().unwrap();
!peer_state.outbound_channels_by_intercept_scid.is_empty()
})
}

/// Used by LSP to inform a client requesting a JIT Channel the token they used is invalid.
///
/// Should be called in response to receiving a [`LSPS2ServiceEvent::GetInfo`] event.
Expand Down
3 changes: 3 additions & 0 deletions lightning-liquidity/src/lsps5/client.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -185,6 +185,9 @@ where
/// Also ensure the URL is valid, has HTTPS protocol, its length does not exceed [`MAX_WEBHOOK_URL_LENGTH`]
/// and that the URL points to a public host.
///
/// Your request may fail if you recently opened a channel or started an LSPS1 / LSPS2 flow.
/// Please retry shortly.
///
/// [`MAX_WEBHOOK_URL_LENGTH`]: super::msgs::MAX_WEBHOOK_URL_LENGTH
/// [`MAX_APP_NAME_LENGTH`]: super::msgs::MAX_APP_NAME_LENGTH
/// [`WebhookRegistered`]: super::event::LSPS5ClientEvent::WebhookRegistered
Expand Down
19 changes: 19 additions & 0 deletions lightning-liquidity/src/lsps5/msgs.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,6 +53,8 @@ pub const LSPS5_UNKNOWN_ERROR_CODE: i32 = 1000;
pub const LSPS5_SERIALIZATION_ERROR_CODE: i32 = 1001;
/// A notification was sent too frequently.
pub const LSPS5_SLOW_DOWN_ERROR_CODE: i32 = 1002;
/// A request was rejected because the client has no prior activity with the LSP (no open channel and no active LSPS1 or LSPS2 flow). The client should first open a channel
pub const LSPS5_NO_PRIOR_ACTIVITY_ERROR_CODE: i32 = 1003;

pub(crate) const LSPS5_SET_WEBHOOK_METHOD_NAME: &str = "lsps5.set_webhook";
pub(crate) const LSPS5_LIST_WEBHOOKS_METHOD_NAME: &str = "lsps5.list_webhooks";
Expand DownExpand Up@@ -113,6 +115,10 @@ pub enum LSPS5ProtocolError {
///
/// [`NOTIFICATION_COOLDOWN_TIME`]: super::service::NOTIFICATION_COOLDOWN_TIME
SlowDownError,

/// Request rejected because the client has no prior activity with the LSP (no open channel and no active LSPS1 or LSPS2 flow). The client should first open a channel
/// or initiate an LSPS1/LSPS2 interaction before retrying.
NoPriorActivityError,
}

impl LSPS5ProtocolError {
Expand All@@ -129,6 +135,7 @@ impl LSPS5ProtocolError {
LSPS5ProtocolError::UnknownError => LSPS5_UNKNOWN_ERROR_CODE,
LSPS5ProtocolError::SerializationError => LSPS5_SERIALIZATION_ERROR_CODE,
LSPS5ProtocolError::SlowDownError => LSPS5_SLOW_DOWN_ERROR_CODE,
LSPS5ProtocolError::NoPriorActivityError => LSPS5_NO_PRIOR_ACTIVITY_ERROR_CODE,
}
}
/// The error message for the LSPS5 protocol error.
Expand All@@ -145,6 +152,9 @@ impl LSPS5ProtocolError {
"Error serializing LSPS5 webhook notification"
},
LSPS5ProtocolError::SlowDownError => "Notification sent too frequently",
LSPS5ProtocolError::NoPriorActivityError => {
"Request rejected due to no prior activity with the LSP"
},
}
}
}
Expand DownExpand Up@@ -249,6 +259,9 @@ impl From<LSPSResponseError> for LSPS5ProtocolError {
LSPS5_UNSUPPORTED_PROTOCOL_ERROR_CODE => LSPS5ProtocolError::UnsupportedProtocol,
LSPS5_TOO_MANY_WEBHOOKS_ERROR_CODE => LSPS5ProtocolError::TooManyWebhooks,
LSPS5_APP_NAME_NOT_FOUND_ERROR_CODE => LSPS5ProtocolError::AppNameNotFound,
LSPS5_SERIALIZATION_ERROR_CODE => LSPS5ProtocolError::SerializationError,
LSPS5_SLOW_DOWN_ERROR_CODE => LSPS5ProtocolError::SlowDownError,
LSPS5_NO_PRIOR_ACTIVITY_ERROR_CODE => LSPS5ProtocolError::NoPriorActivityError,
_ => LSPS5ProtocolError::UnknownError,
}
}
Expand DownExpand Up@@ -640,6 +653,12 @@ pub enum LSPS5Request {
RemoveWebhook(RemoveWebhookRequest),
}

impl LSPS5Request {
pub(crate) fn is_state_allocating(&self) -> bool {
matches!(self, LSPS5Request::SetWebhook(_))
}
}

/// An LSPS5 protocol response.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LSPS5Response {
Expand Down
28 changes: 28 additions & 0 deletions lightning-liquidity/src/lsps5/service.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -149,6 +149,34 @@ where
}
}

/// Enforces the prior-activity requirement for state-allocating LSPS5 requests (e.g.
/// `lsps5.set_webhook`), rejecting and replying with `NoPriorActivityError` if not met.
pub(crate) fn enforce_prior_activity_or_reject(
&self, client_id: &PublicKey, lsps2_has_active_requests: bool, lsps1_has_activity: bool,
request_id: LSPSRequestId,
) -> Result<(), LightningError> {
let can_accept = self.client_has_open_channel(client_id)
|| lsps2_has_active_requests
|| lsps1_has_activity;

let mut message_queue_notifier = self.pending_messages.notifier();
if !can_accept {
let error = LSPS5ProtocolError::NoPriorActivityError;
let msg = LSPS5Message::Response(
request_id,
LSPS5Response::SetWebhookError(error.clone().into()),
)
.into();
message_queue_notifier.enqueue(&client_id, msg);
return Err(LightningError {
err: error.message().into(),
action: ErrorAction::IgnoreAndLog(Level::Info),
});
} else {
Ok(())
}
}

fn check_prune_stale_webhooks<'a>(
&self, outer_state_lock: &mut RwLockWriteGuard<'a, HashMap<PublicKey, PeerState>>,
) {
Expand Down
23 changes: 23 additions & 0 deletions lightning-liquidity/src/manager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -568,6 +568,29 @@ where
LSPSMessage::LSPS5(msg @ LSPS5Message::Request(..)) => {
match &self.lsps5_service_handler {
Some(lsps5_service_handler) => {
if let LSPS5Message::Request(ref req_id, ref req) = msg {
if req.is_state_allocating() {
let lsps2_has_active_requests = self
.lsps2_service_handler
.as_ref()
.map_or(false, |h| h.has_active_requests(sender_node_id));
#[cfg(lsps1_service)]
let lsps1_has_active_requests = self
.lsps1_service_handler
.as_ref()
.map_or(false, |h| h.has_active_requests(sender_node_id));
#[cfg(not(lsps1_service))]
let lsps1_has_active_requests = false;

lsps5_service_handler.enforce_prior_activity_or_reject(
sender_node_id,
lsps2_has_active_requests,
lsps1_has_active_requests,
req_id.clone(),
)?
}
}

lsps5_service_handler.handle_message(msg, sender_node_id)?;
},
None => {
Expand Down
Loading
Loading