Skip to content

Expose the node's KV store to applications - #1174

Open
benthecarman wants to merge 1 commit into
lightningdevkit:mainfrom
benthecarman:node-kv-store-accessor
Open

benthecarman wants to merge 1 commit into
lightningdevkit:mainfrom
benthecarman:node-kv-store-accessor

Conversation

@benthecarman

@benthecarman benthecarman commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Applications built on LDK Node may want to persist their own data alongside the node's state, so it lives in the same database and is backed up together. Right now nothing can reach the node's store once the node is built. Opening a second handle doesn't work with the Postgres backend either, because the store takes an exclusive lease on its table.

This adds Node::kv_store, which returns a handle to the store the node persists its state to. It also makes DynStoreRef public, re-exported as ldk_node::io::DynStoreRef, so applications can keep the handle. The docs say applications must only use namespaces of their own.

The motivating use case is LDK Server's SubscribeEvents (lightningdevkit/ldk-server#245). We want to write each event to a durable, indexed log in the node's store before calling event_handled(), so subscribers can resume from an event index and get every event at least once.

Open to renaming DynStoreRef now that it's part of the public API.

Applications built on LDK Node may want to persist their own data
alongside the node's state, e.g., so it lives in the same database and
is backed up together. Currently the node's store isn't reachable once
the node is built, and opening a second handle to it isn't an option
with the Postgres backend, as the store takes an exclusive lease on its
table.

Add `Node::kv_store`, returning a handle to the store the node persists
its state to, and make `DynStoreRef` public so applications can hold on
to it. Applications must only use namespaces of their own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ldk-reviews-bot

ldk-reviews-bot commented Oct 16, 2026 •

Copy link
Copy Markdown

I've assigned @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@Camillarhi Camillarhi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank for looking into this. Left a few comments

Comment thread src/lib.rs
Comment on lines +994 to +996
pub fn kv_store(&self) -> DynStoreRef {
DynStoreRef(Arc::clone(&self.kv_store))
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since this hands out a clone of the Arc, the store now lives as long as the last handle does. For Postgres, that means the lease is also held until then, so if an app keeps this handle around after dropping the node, building a new node on the same table fails with KVStoreAlreadyInUse.

Could we document that all handles need to be dropped before building a new node on the same store? Probably also worth mentioning that lease-loss panics can now surface in app tasks when they write through this.

Comment thread src/lib.rs
Comment on lines +990 to +991
/// **Note:** Applications must only read and write namespaces of their own. Modifying or
/// removing any data written by the node itself may lead to loss of funds.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

How would an app know which namespaces are its own? The node and LDK use "", monitors, payments, bdk_wallet, etc., but those constants are all pub(crate). There's also nothing stopping a future release from adding a namespace an app already uses.

Given the loss-of-funds risk, could we enforce this instead of only documenting it? E.g. return a small wrapper that rejects the empty primary namespace and the ones we use internally, or require some prefix like app_. At minimum it'd be good to list the reserved ones here. What do you think?

let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
let chain_source = random_chain_source(&bitcoind, &electrsd);
let mut config = random_config();
config.store_type = TestStoreType::Sqlite;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Postgres is a motivating case here, but the test only covers SQLite, where the lease behavior doesn't apply. Would be nice to have a test that checks what happens when a handle is still held across a rebuild.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants