Skip to content

Commit a8589ea

Browse files
benthecarmanclaude
andcommitted
Add PostgreSQL storage backend
Add a PostgresStore implementation behind the "postgres" feature flag, mirroring the existing SqliteStore. Uses tokio-postgres (async-native) with an internal tokio runtime for the sync KVStoreSync trait, following the VssStore pattern. Includes unit tests, integration tests (channel full cycle and node restart), and a CI workflow that runs both against a PostgreSQL service container. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 3aef2b3 commit a8589ea

File tree

7 files changed

+1525
-0
lines changed

7 files changed

+1525
-0
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
name: CI Checks - PostgreSQL Integration Tests
2+
3+
on: [ push, pull_request ]
4+
5+
concurrency:
6+
group: ${{ github.workflow }}-${{ github.ref }}
7+
cancel-in-progress: true
8+
9+
jobs:
10+
build-and-test:
11+
runs-on: ubuntu-latest
12+
13+
services:
14+
postgres:
15+
image: postgres:latest
16+
ports:
17+
- 5432:5432
18+
env:
19+
POSTGRES_DB: postgres
20+
POSTGRES_USER: postgres
21+
POSTGRES_PASSWORD: postgres
22+
options: >-
23+
--health-cmd pg_isready
24+
--health-interval 10s
25+
--health-timeout 5s
26+
--health-retries 5
27+
28+
steps:
29+
- name: Checkout code
30+
uses: actions/checkout@v6
31+
- name: Install Rust stable toolchain
32+
run: |
33+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain stable
34+
- name: Enable caching for bitcoind
35+
id: cache-bitcoind
36+
uses: actions/cache@v4
37+
with:
38+
path: bin/bitcoind-${{ runner.os }}-${{ runner.arch }}
39+
key: bitcoind-${{ runner.os }}-${{ runner.arch }}
40+
- name: Enable caching for electrs
41+
id: cache-electrs
42+
uses: actions/cache@v4
43+
with:
44+
path: bin/electrs-${{ runner.os }}-${{ runner.arch }}
45+
key: electrs-${{ runner.os }}-${{ runner.arch }}
46+
- name: Download bitcoind/electrs
47+
if: "steps.cache-bitcoind.outputs.cache-hit != 'true' || steps.cache-electrs.outputs.cache-hit != 'true'"
48+
run: |
49+
source ./scripts/download_bitcoind_electrs.sh
50+
mkdir bin
51+
mv "$BITCOIND_EXE" bin/bitcoind-${{ runner.os }}-${{ runner.arch }}
52+
mv "$ELECTRS_EXE" bin/electrs-${{ runner.os }}-${{ runner.arch }}
53+
- name: Set bitcoind/electrs environment variables
54+
run: |
55+
echo "BITCOIND_EXE=$( pwd )/bin/bitcoind-${{ runner.os }}-${{ runner.arch }}" >> "$GITHUB_ENV"
56+
echo "ELECTRS_EXE=$( pwd )/bin/electrs-${{ runner.os }}-${{ runner.arch }}" >> "$GITHUB_ENV"
57+
- name: Run PostgreSQL store tests
58+
env:
59+
TEST_POSTGRES_URL: "host=localhost user=postgres password=postgres"
60+
run: cargo test --features postgres io::postgres_store
61+
- name: Run PostgreSQL integration tests
62+
env:
63+
TEST_POSTGRES_URL: "host=localhost user=postgres password=postgres"
64+
run: |
65+
RUSTFLAGS="--cfg no_download --cfg cycle_tests" cargo test --features postgres --test integration_tests_postgres

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ panic = 'abort' # Abort on panic
2525

2626
[features]
2727
default = []
28+
postgres = ["dep:tokio-postgres", "dep:native-tls", "dep:postgres-native-tls"]
2829

2930
[dependencies]
3031
#lightning = { version = "0.2.0", features = ["std"] }
@@ -76,6 +77,9 @@ serde_json = { version = "1.0.128", default-features = false, features = ["std"]
7677
log = { version = "0.4.22", default-features = false, features = ["std"]}
7778

7879
async-trait = { version = "0.1", default-features = false }
80+
tokio-postgres = { version = "0.7", default-features = false, features = ["runtime"], optional = true }
81+
native-tls = { version = "0.2", default-features = false, optional = true }
82+
postgres-native-tls = { version = "0.5", default-features = false, features = ["runtime"], optional = true }
7983
vss-client = { package = "vss-client-ng", version = "0.5" }
8084
prost = { version = "0.11.6", default-features = false}
8185
#bitcoin-payment-instructions = { version = "0.6" }

src/builder.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,44 @@ impl NodeBuilder {
629629
self.build_with_store(node_entropy, kv_store)
630630
}
631631

632+
/// Builds a [`Node`] instance with a [PostgreSQL] backend and according to the options
633+
/// previously configured.
634+
///
635+
/// Connects to the PostgreSQL database at the given `connection_string`, e.g.,
636+
/// `"postgres://user:password@localhost/ldk_db"`.
637+
///
638+
/// The given `db_name` will be used or default to
639+
/// [`DEFAULT_DB_NAME`](io::postgres_store::DEFAULT_DB_NAME). The database will be created
640+
/// automatically if it doesn't already exist. The `connection_string` must not include a
641+
/// `dbname` when `db_name` is set, providing both is an error. When auto-creating the
642+
/// database, the initial connection is made using the database from the connection string,
643+
/// or the server's default if none is specified.
644+
///
645+
/// The given `kv_table_name` will be used or default to
646+
/// [`DEFAULT_KV_TABLE_NAME`](io::postgres_store::DEFAULT_KV_TABLE_NAME).
647+
///
648+
/// If `tls_config` is `Some`, TLS will be used for database connections. A custom CA
649+
/// certificate can be provided via
650+
/// [`PostgresTlsConfig::certificate_pem`](io::postgres_store::PostgresTlsConfig::certificate_pem),
651+
/// which will be added to the system's default root certificates (not replace them).
652+
/// If `tls_config` is `None`, connections will be unencrypted.
653+
///
654+
/// [PostgreSQL]: https://www.postgresql.org
655+
#[cfg(feature = "postgres")]
656+
pub fn build_with_postgres_store(
657+
&self, node_entropy: NodeEntropy, connection_string: String, db_name: Option<String>,
658+
kv_table_name: Option<String>, tls_config: Option<io::postgres_store::PostgresTlsConfig>,
659+
) -> Result<Node, BuildError> {
660+
let kv_store = io::postgres_store::PostgresStore::new(
661+
connection_string,
662+
db_name,
663+
kv_table_name,
664+
tls_config,
665+
)
666+
.map_err(|_| BuildError::KVStoreSetupFailed)?;
667+
self.build_with_store(node_entropy, kv_store)
668+
}
669+
632670
/// Builds a [`Node`] instance with a [`FilesystemStore`] backend and according to the options
633671
/// previously configured.
634672
pub fn build_with_fs_store(&self, node_entropy: NodeEntropy) -> Result<Node, BuildError> {
@@ -1087,6 +1125,28 @@ impl ArcedNodeBuilder {
10871125
self.inner.read().unwrap().build(*node_entropy).map(Arc::new)
10881126
}
10891127

1128+
/// Builds a [`Node`] instance with a [PostgreSQL] backend and according to the options
1129+
/// previously configured.
1130+
///
1131+
/// [PostgreSQL]: https://www.postgresql.org
1132+
#[cfg(feature = "postgres")]
1133+
pub fn build_with_postgres_store(
1134+
&self, node_entropy: Arc<NodeEntropy>, connection_string: String, db_name: Option<String>,
1135+
kv_table_name: Option<String>, tls_config: Option<io::postgres_store::PostgresTlsConfig>,
1136+
) -> Result<Arc<Node>, BuildError> {
1137+
self.inner
1138+
.read()
1139+
.unwrap()
1140+
.build_with_postgres_store(
1141+
*node_entropy,
1142+
connection_string,
1143+
db_name,
1144+
kv_table_name,
1145+
tls_config,
1146+
)
1147+
.map(Arc::new)
1148+
}
1149+
10901150
/// Builds a [`Node`] instance with a [`FilesystemStore`] backend and according to the options
10911151
/// previously configured.
10921152
pub fn build_with_fs_store(

src/io/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
//! Objects and traits for data persistence.
99
10+
#[cfg(feature = "postgres")]
11+
pub mod postgres_store;
1012
pub mod sqlite_store;
1113
#[cfg(test)]
1214
pub(crate) mod test_utils;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// This file is Copyright its original authors, visible in version control history.
2+
//
3+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5+
// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
6+
// accordance with one or both of these licenses.
7+
8+
use lightning::io;
9+
use tokio_postgres::Client;
10+
11+
pub(super) async fn migrate_schema(
12+
_client: &Client, _kv_table_name: &str, from_version: u16, to_version: u16,
13+
) -> io::Result<()> {
14+
assert!(from_version < to_version);
15+
// Future migrations go here, e.g.:
16+
// if from_version == 1 && to_version >= 2 {
17+
// migrate_v1_to_v2(client, kv_table_name).await?;
18+
// from_version = 2;
19+
// }
20+
Ok(())
21+
}

0 commit comments

Comments
 (0)