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
9 changes: 7 additions & 2 deletions plugins/examples/cln-plugin-startup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use cln_plugin::options::{
DefaultStringArrayConfigOption, IntegerArrayConfigOption, IntegerConfigOption,
StringArrayConfigOption,
};
use cln_plugin::{messages, Builder, Error, Plugin};
use cln_plugin::{messages, Builder, Error, HookBuilder, Plugin};

const TEST_NOTIF_TAG: &str = "test_custom_notification";

Expand Down Expand Up @@ -79,7 +79,12 @@ async fn main() -> Result<(), anyhow::Error> {
.rpcmethod("test-log-levels", "send on all log levels", test_log_levels)
.subscribe("connect", connect_handler)
.subscribe("test_custom_notification", test_receive_custom_notification)
.hook("peer_connected", peer_connected_handler)
.hook_from_builder(
HookBuilder::new("peer_connected", peer_connected_handler)
.after(Vec::new())
.before(Vec::new())
.filters(Vec::new()),
)
.notification(messages::NotificationTopic::new(TEST_NOTIF_TAG))
.start(state)
.await?
Expand Down
10 changes: 8 additions & 2 deletions plugins/lsps-plugin/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ use cln_lsps::jsonrpc::client::JsonRpcClient;
use cln_lsps::lsps0::primitives::Msat;
use cln_lsps::lsps0::{
self,
transport::{Bolt8Transport, CustomMessageHookManager, WithCustomMessageHookManager},
transport::{
Bolt8Transport, CustomMessageHookManager, WithCustomMessageHookManager, LSPS0_MESSAGE_TYPE,
},
};
use cln_lsps::lsps2::cln::tlv::encode_tu64;
use cln_lsps::lsps2::cln::{
Expand All @@ -19,6 +21,7 @@ use cln_lsps::lsps2::model::{
use cln_lsps::util;
use cln_lsps::LSP_FEATURE_BIT;
use cln_plugin::options;
use cln_plugin::{HookBuilder, HookFilter};
use cln_rpc::model::requests::{
DatastoreMode, DatastoreRequest, DeldatastoreRequest, DelinvoiceRequest, DelinvoiceStatus,
ListdatastoreRequest, ListinvoicesRequest, ListpeersRequest,
Expand Down Expand Up @@ -55,7 +58,10 @@ async fn main() -> Result<(), anyhow::Error> {
let state = State { hook_manager };

if let Some(plugin) = cln_plugin::Builder::new(tokio::io::stdin(), tokio::io::stdout())
.hook("custommsg", CustomMessageHookManager::on_custommsg::<State>)
.hook_from_builder(
HookBuilder::new("custommsg", CustomMessageHookManager::on_custommsg::<State>)
.filters(vec![HookFilter::Int(i64::from(LSPS0_MESSAGE_TYPE))]),
)
.option(OPTION_ENABLED)
.rpcmethod(
"lsps-listprotocols",
Expand Down
5 changes: 4 additions & 1 deletion plugins/lsps-plugin/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use cln_lsps::lsps2::model::{Lsps2BuyRequest, Lsps2GetInfoRequest};
use cln_lsps::util::wrap_payload_with_peer_id;
use cln_lsps::{lsps0, lsps2};
use cln_plugin::Plugin;
use cln_plugin::{HookBuilder, HookFilter};
use cln_rpc::notifications::CustomMsgNotification;
use cln_rpc::primitives::PublicKey;
use log::debug;
Expand Down Expand Up @@ -42,7 +43,9 @@ async fn main() -> Result<(), anyhow::Error> {
// cln_plugin::FeatureBitsKind::Init,
// util::feature_bit_to_hex(LSP_FEATURE_BIT),
// )
.hook("custommsg", on_custommsg)
.hook_from_builder(HookBuilder::new("custommsg", on_custommsg).filters(vec![
HookFilter::Int(i64::from(lsps0::transport::LSPS0_MESSAGE_TYPE)),
]))
.hook("htlc_accepted", on_htlc_accepted)
.configure()
.await?
Expand Down
95 changes: 94 additions & 1 deletion plugins/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::codec::{JsonCodec, JsonRpcCodec};
pub use anyhow::anyhow;
use anyhow::{Context, Result};
use futures::sink::SinkExt;
use serde::Serialize;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
extern crate log;
use log::trace;
Expand Down Expand Up @@ -204,12 +205,21 @@ where
self.hooks.insert(
hookname.to_string(),
Hook {
name: hookname.to_string(),
callback: Box::new(move |p, r| Box::pin(callback(p, r))),
before: Vec::new(),
after: Vec::new(),
filters: None,
},
);
self
}

pub fn hook_from_builder(mut self, hook: HookBuilder<S>) -> Builder<S, I, O> {
Copy link
Member

Choose a reason for hiding this comment

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

Is there a point in keeping the HookBuilder around? I quite like using mut HookBuilder as argument type, because it means we own the instance, and we can disect it however we like, so in this case I think we can skip the hook.name.clone() invokation and directly use hook.name, saving us a clone in normal operations 🤔

Very minor thing, but I was wondering what you think of such an optimization.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Afaik we don't keep it around since hook.build() consumes it. Since we need the String in name here and to make the Hook in build() we need to clone() it somewhere, right?

Copy link
Member

Choose a reason for hiding this comment

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

That's the thing that I'm wondering, if we consume the HookBuilder, the String is free to be reassigned, and the Builder could reuse the String. No idea if this is done by the compiler, and more of a curiosity to be honest, so this should absolutely not hold up progress. This can be merged as is.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think about every clone alot aswell 😄 . self.hooks has the hook.name as key and in the value struct. Both are owned so we must either

  • clone the name for the key or the value
  • use borrowed objects (then we probably need to start using lifetimes, which seems not worth it here imo)
  • refactor some stuff so we don't have the name in both key and value

self.hooks.insert(hook.name.clone(), hook.build());
self
}

/// Register a custom RPC method for the RPC passthrough from the
/// main daemon
pub fn rpcmethod<C, F>(mut self, name: &str, description: &str, callback: C) -> Builder<S, I, O>
Expand Down Expand Up @@ -411,10 +421,21 @@ where
.chain(self.wildcard_subscription.iter().map(|_| String::from("*")))
.collect();

let hooks: Vec<messages::Hook> = self
.hooks
.values()
.map(|v| messages::Hook {
name: v.name.clone(),
before: v.before.clone(),
after: v.after.clone(),
filters: v.filters.clone(),
})
.collect();

messages::GetManifestResponse {
options: self.options.values().cloned().collect(),
subscriptions,
hooks: self.hooks.keys().map(|s| s.clone()).collect(),
hooks,
rpcmethods,
notifications: self.notifications.clone(),
featurebits: self.featurebits.clone(),
Expand Down Expand Up @@ -458,6 +479,56 @@ where
}
}

impl<S> HookBuilder<S>
where
S: Send + Clone,
{
pub fn new<C, F>(name: &str, callback: C) -> Self
where
C: Send + Sync + 'static,
C: Fn(Plugin<S>, Request) -> F + 'static,
F: Future<Output = Response> + Send + 'static,
{
Self {
name: name.to_string(),
callback: Box::new(move |p, r| Box::pin(callback(p, r))),
before: Vec::new(),
after: Vec::new(),
filters: None,
}
}

pub fn before(mut self, before: Vec<String>) -> Self {
self.before = before;
self
}

pub fn after(mut self, after: Vec<String>) -> Self {
self.after = after;
self
}

pub fn filters(mut self, filters: Vec<HookFilter>) -> Self {
// Empty Vec would filter everything, must be None instead to not get serialized
if filters.is_empty() {
self.filters = None;
} else {
self.filters = Some(filters);
}
self
}

fn build(self) -> Hook<S> {
Hook {
callback: self.callback,
name: self.name,
before: self.before,
after: self.after,
filters: self.filters,
}
}
}

impl<S> RpcMethodBuilder<S>
where
S: Send + Clone,
Expand Down Expand Up @@ -542,7 +613,29 @@ struct Hook<S>
where
S: Clone + Send,
{
name: String,
callback: AsyncCallback<S>,
before: Vec<String>,
after: Vec<String>,
filters: Option<Vec<HookFilter>>,
}

pub struct HookBuilder<S>
where
S: Clone + Send,
{
name: String,
callback: AsyncCallback<S>,
before: Vec<String>,
after: Vec<String>,
filters: Option<Vec<HookFilter>>,
}

#[derive(Debug, Clone, Serialize)]
#[serde(untagged)]
pub enum HookFilter {
Str(String),
Int(i64),
}

impl<S> Plugin<S>
Expand Down
11 changes: 10 additions & 1 deletion plugins/src/messages.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::options::UntypedConfigOption;
use crate::HookFilter;
use serde::de::{self, Deserializer};
use serde::{Deserialize, Serialize};
use serde_json::Value;
Expand Down Expand Up @@ -150,6 +151,14 @@ pub(crate) struct RpcMethod {
pub(crate) usage: String,
}

#[derive(Serialize, Default, Debug)]
pub(crate) struct Hook {
pub(crate) name: String,
pub(crate) before: Vec<String>,
pub(crate) after: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) filters: Option<Vec<HookFilter>>,
}
#[derive(Serialize, Default, Debug, Clone)]
pub struct NotificationTopic {
pub method: String,
Expand All @@ -175,7 +184,7 @@ pub(crate) struct GetManifestResponse {
pub(crate) rpcmethods: Vec<RpcMethod>,
pub(crate) subscriptions: Vec<String>,
pub(crate) notifications: Vec<NotificationTopic>,
pub(crate) hooks: Vec<String>,
pub(crate) hooks: Vec<Hook>,
pub(crate) dynamic: bool,
pub(crate) featurebits: FeatureBits,
pub(crate) nonnumericids: bool,
Expand Down
Loading