Skip to content

Commit 9f2292f

Browse files
committed
Expose node ids as UniFFI objects
Reject malformed binding inputs in a fallible constructor before they reach graph lookups. Also expose safe conversion to and from public keys while keeping Rust graph APIs on the upstream type. Co-Authored-By: HAL 9000
1 parent 337d625 commit 9f2292f

6 files changed

Lines changed: 112 additions & 22 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
- The language bindings now expose `NodeAlias` as an object instead of a string alias. Node aliases
1515
must be passed through its fallible constructor, which returns `NodeError::InvalidNodeAlias` for
1616
invalid input.
17+
- The language bindings now expose `NodeId` as an object instead of a string alias. Node ids must
18+
be passed through its fallible constructor, which returns `NodeError::InvalidNodeId` for invalid
19+
input.
1720
- `generate_entropy_mnemonic` has been removed. Use `bip39::Mnemonic::generate` in Rust and
1821
`Mnemonic::generate` in the language bindings instead.
1922
- Migrating between storage backends does not preserve the relative creation order of

bindings/ldk_node.udl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -388,8 +388,7 @@ typedef interface SocketAddress;
388388

389389
typedef interface PublicKey;
390390

391-
[Custom]
392-
typedef string NodeId;
391+
typedef interface NodeId;
393392

394393
[Custom]
395394
typedef string Address;

bindings/python/src/ldk_node/test_ldk_node.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,38 @@ def test_node_alias_object(self):
309309
self.assertIsInstance(error.exception, NodeError.InvalidNodeAlias)
310310

311311

312+
class TestNodeId(unittest.TestCase):
313+
def test_node_id_object(self):
314+
self.assertTrue(
315+
hasattr(bindings.NodeId, "from_str"),
316+
"NodeId should be exposed as an object",
317+
)
318+
319+
node_id_str = (
320+
"02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"
321+
)
322+
node_id = bindings.NodeId.from_str(node_id_str)
323+
324+
self.assertIsInstance(node_id, bindings.NodeId)
325+
self.assertEqual(str(node_id), node_id_str)
326+
self.assertEqual(node_id.as_bytes(), bytes.fromhex(node_id_str))
327+
328+
public_key = bindings.PublicKey.from_str(node_id_str)
329+
self.assertEqual(bindings.NodeId.from_public_key(public_key), node_id)
330+
self.assertEqual(node_id.as_public_key(), public_key)
331+
332+
with self.assertRaises(NodeError) as error:
333+
bindings.NodeId.from_str("invalid")
334+
335+
self.assertIsInstance(error.exception, NodeError.InvalidNodeId)
336+
337+
invalid_public_key_node_id = bindings.NodeId.from_str("00" * 33)
338+
with self.assertRaises(NodeError) as error:
339+
invalid_public_key_node_id.as_public_key()
340+
341+
self.assertIsInstance(error.exception, NodeError.InvalidNodeId)
342+
343+
312344
class TestLdkNode(unittest.TestCase):
313345
def setUp(self):
314346
bitcoin_cli("createwallet ldk_node_test")

src/ffi/types.rs

Lines changed: 66 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ use lightning::offers::payer_proof::{
3838
use lightning::offers::refund::Refund as LdkRefund;
3939
use lightning::offers::static_invoice::StaticInvoice as LdkStaticInvoice;
4040
use lightning::onion_message::dns_resolution::HumanReadableName as LdkHumanReadableName;
41-
use lightning::routing::gossip::NodeAlias as LdkNodeAlias;
42-
pub use lightning::routing::gossip::{NodeId, RoutingFees};
41+
pub use lightning::routing::gossip::RoutingFees;
42+
use lightning::routing::gossip::{NodeAlias as LdkNodeAlias, NodeId as LdkNodeId};
4343
pub use lightning::routing::router::RouteParametersConfig;
4444
use lightning::util::persist::PageToken as LdkPageToken;
4545
use lightning::util::ser::{Readable, RequiredWrapper, Writeable, Writer};
@@ -242,19 +242,71 @@ impl ReadablePublicKey for RequiredWrapper<Arc<PublicKey>> {
242242
}
243243
}
244244

245-
uniffi::custom_type!(NodeId, String, {
246-
remote,
247-
try_lift: |val| {
248-
if let Ok(key) = NodeId::from_str(&val) {
249-
return Ok(key);
250-
}
245+
/// A compressed public key identifying a node in the network graph.
246+
#[derive(Debug, Clone, PartialEq, Eq, uniffi::Object)]
247+
#[uniffi::export(Debug, Display, Eq)]
248+
pub struct NodeId {
249+
pub(crate) inner: LdkNodeId,
250+
}
251251

252-
Err(Error::InvalidNodeId.into())
253-
},
254-
lower: |obj| {
255-
obj.to_string()
256-
},
257-
});
252+
#[uniffi::export]
253+
impl NodeId {
254+
/// Constructs a node id from its serialized representation.
255+
#[uniffi::constructor]
256+
pub fn from_str(node_id_str: &str) -> Result<Self, Error> {
257+
node_id_str.parse()
258+
}
259+
260+
/// Constructs a node id from a public key.
261+
#[uniffi::constructor]
262+
pub fn from_public_key(public_key: Arc<PublicKey>) -> Self {
263+
LdkNodeId::from_pubkey(public_key.as_ref().as_ref()).into()
264+
}
265+
266+
/// Returns the serialized node id.
267+
pub fn as_bytes(&self) -> Vec<u8> {
268+
self.inner.as_slice().to_vec()
269+
}
270+
271+
/// Returns the node id as a public key.
272+
pub fn as_public_key(&self) -> Result<Arc<PublicKey>, Error> {
273+
self.inner.as_pubkey().map(PublicKey::from).map(Arc::new).map_err(|_| Error::InvalidNodeId)
274+
}
275+
}
276+
277+
impl FromStr for NodeId {
278+
type Err = Error;
279+
280+
fn from_str(node_id_str: &str) -> Result<Self, Self::Err> {
281+
node_id_str.parse::<LdkNodeId>().map(Self::from).map_err(|_| Error::InvalidNodeId)
282+
}
283+
}
284+
285+
impl From<LdkNodeId> for NodeId {
286+
fn from(inner: LdkNodeId) -> Self {
287+
Self { inner }
288+
}
289+
}
290+
291+
impl Deref for NodeId {
292+
type Target = LdkNodeId;
293+
294+
fn deref(&self) -> &Self::Target {
295+
&self.inner
296+
}
297+
}
298+
299+
impl AsRef<LdkNodeId> for NodeId {
300+
fn as_ref(&self) -> &LdkNodeId {
301+
self.deref()
302+
}
303+
}
304+
305+
impl std::fmt::Display for NodeId {
306+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
307+
write!(f, "{}", self.inner)
308+
}
309+
}
258310

259311
uniffi::custom_type!(Address, String, {
260312
remote,

src/graph.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@
99
1010
use std::sync::Arc;
1111

12-
use lightning::routing::gossip::NodeId;
1312
#[cfg(feature = "uniffi")]
1413
use lightning::routing::gossip::RoutingFees;
1514
#[cfg(not(feature = "uniffi"))]
1615
use lightning::routing::gossip::{ChannelInfo, NodeInfo};
1716

18-
use crate::types::Graph;
17+
use crate::ffi::{maybe_deref, maybe_wrap};
1918
#[cfg(feature = "uniffi")]
2019
use crate::types::SocketAddress;
20+
use crate::types::{Graph, NodeId};
2121

2222
/// Represents the network as nodes and channels between them.
2323
#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
@@ -45,12 +45,12 @@ impl NetworkGraph {
4545

4646
/// Returns the list of nodes in the graph
4747
pub fn list_nodes(&self) -> Vec<NodeId> {
48-
self.inner.read_only().nodes().unordered_keys().map(|n| *n).collect()
48+
self.inner.read_only().nodes().unordered_keys().map(|n| maybe_wrap(*n)).collect()
4949
}
5050

5151
/// Returns information on a node with the given id.
5252
pub fn node(&self, node_id: &NodeId) -> Option<NodeInfo> {
53-
self.inner.read_only().nodes().get(node_id).cloned().map(|n| n.into())
53+
self.inner.read_only().nodes().get(maybe_deref(node_id)).cloned().map(|n| n.into())
5454
}
5555
}
5656

@@ -78,9 +78,9 @@ pub struct ChannelInfo {
7878
impl From<lightning::routing::gossip::ChannelInfo> for ChannelInfo {
7979
fn from(value: lightning::routing::gossip::ChannelInfo) -> Self {
8080
Self {
81-
node_one: value.node_one,
81+
node_one: maybe_wrap(value.node_one),
8282
one_to_two: value.one_to_two.map(|u| u.into()),
83-
node_two: value.node_two,
83+
node_two: maybe_wrap(value.node_two),
8484
two_to_one: value.two_to_one.map(|u| u.into()),
8585
capacity_sats: value.capacity_sats,
8686
}

src/types.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ pub(crate) type SocketAddress = Arc<crate::ffi::SocketAddress>;
2222
pub(crate) use lightning::routing::gossip::NodeAlias;
2323
#[cfg(feature = "uniffi")]
2424
pub(crate) type NodeAlias = Arc<crate::ffi::NodeAlias>;
25+
#[cfg(not(feature = "uniffi"))]
26+
pub(crate) use lightning::routing::gossip::NodeId;
27+
#[cfg(feature = "uniffi")]
28+
pub(crate) type NodeId = Arc<crate::ffi::NodeId>;
2529
use bitcoin::{OutPoint, ScriptBuf};
2630
use bitcoin_payment_instructions::amount::Amount as BPIAmount;
2731
use bitcoin_payment_instructions::dns_resolver::DNSHrnResolver;

0 commit comments

Comments
 (0)