-
Notifications
You must be signed in to change notification settings - Fork 143
Add Postgres database #863
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| name: CI Checks - PostgreSQL Integration Tests | ||
|
|
||
| on: [ push, pull_request ] | ||
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| build-and-test: | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 60 | ||
|
|
||
| services: | ||
| postgres: | ||
| image: postgres:latest | ||
| ports: | ||
| - 5432:5432 | ||
| env: | ||
| POSTGRES_DB: postgres | ||
| POSTGRES_USER: postgres | ||
| POSTGRES_PASSWORD: postgres | ||
| options: >- | ||
| --health-cmd pg_isready | ||
| --health-interval 10s | ||
| --health-timeout 5s | ||
| --health-retries 5 | ||
|
|
||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v6 | ||
| - name: Install Rust stable toolchain | ||
| run: | | ||
| curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain stable | ||
| - name: Enable caching for bitcoind | ||
| id: cache-bitcoind | ||
| uses: actions/cache@v4 | ||
| with: | ||
| path: bin/bitcoind-${{ runner.os }}-${{ runner.arch }} | ||
| key: bitcoind-27_2-${{ runner.os }}-${{ runner.arch }} | ||
| - name: Enable caching for electrs | ||
| id: cache-electrs | ||
| uses: actions/cache@v4 | ||
| with: | ||
| path: bin/electrs-${{ runner.os }}-${{ runner.arch }} | ||
| key: electrs-esplora_a33e97e1-${{ runner.os }}-${{ runner.arch }} | ||
| - name: Download bitcoind/electrs | ||
| if: "steps.cache-bitcoind.outputs.cache-hit != 'true' || steps.cache-electrs.outputs.cache-hit != 'true'" | ||
| run: | | ||
| source ./scripts/download_bitcoind_electrs.sh | ||
| mkdir -p bin | ||
| mv "$BITCOIND_EXE" bin/bitcoind-${{ runner.os }}-${{ runner.arch }} | ||
| mv "$ELECTRS_EXE" bin/electrs-${{ runner.os }}-${{ runner.arch }} | ||
| - name: Set bitcoind/electrs environment variables | ||
| run: | | ||
| echo "BITCOIND_EXE=$( pwd )/bin/bitcoind-${{ runner.os }}-${{ runner.arch }}" >> "$GITHUB_ENV" | ||
| echo "ELECTRS_EXE=$( pwd )/bin/electrs-${{ runner.os }}-${{ runner.arch }}" >> "$GITHUB_ENV" | ||
| - name: Run PostgreSQL store tests | ||
| env: | ||
| TEST_POSTGRES_URL: "host=localhost user=postgres password=postgres" | ||
| run: cargo test --features postgres io::postgres_store | ||
| - name: Run PostgreSQL integration tests | ||
| env: | ||
| TEST_POSTGRES_URL: "host=localhost user=postgres password=postgres" | ||
| run: | | ||
| RUSTFLAGS="--cfg no_download --cfg cycle_tests" cargo test --features postgres --test integration_tests_postgres |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -644,6 +644,50 @@ impl NodeBuilder { | |
| self.build_with_store_and_logger(node_entropy, kv_store, logger) | ||
| } | ||
|
|
||
| /// Builds a [`Node`] instance with a [PostgreSQL] backend and according to the options | ||
| /// previously configured. | ||
| /// | ||
| /// Connects to the PostgreSQL database at the given `connection_string`, e.g., | ||
| /// `"postgres://user:password@localhost/ldk_db"`. | ||
| /// | ||
| /// The given `db_name` will be used or default to | ||
| /// [`DEFAULT_DB_NAME`](io::postgres_store::DEFAULT_DB_NAME). The `connection_string` must | ||
| /// not include a `dbname` when `db_name` is set, providing both is an error. The database | ||
| /// will be created automatically if it doesn't already exist. The initial connection is | ||
| /// made to the target database, and if it fails we fall back to the default `postgres` | ||
| /// database to create it. | ||
| /// | ||
| /// The given `kv_table_name` will be used or default to | ||
| /// [`DEFAULT_KV_TABLE_NAME`](io::postgres_store::DEFAULT_KV_TABLE_NAME). | ||
| /// | ||
| /// If `certificate_pem` is `Some`, TLS will be used for database connections and the | ||
| /// provided PEM-encoded CA certificate will be added to the system's default root | ||
| /// certificates (it does not replace them). If `certificate_pem` is `None`, connections | ||
| /// will be unencrypted. | ||
| /// | ||
| /// [PostgreSQL]: https://www.postgresql.org | ||
| #[cfg(feature = "postgres")] | ||
| pub fn build_with_postgres_store( | ||
| &self, node_entropy: NodeEntropy, connection_string: String, db_name: Option<String>, | ||
|
benthecarman marked this conversation as resolved.
|
||
| kv_table_name: Option<String>, certificate_pem: Option<String>, | ||
| ) -> Result<Node, BuildError> { | ||
| let logger = setup_logger(&self.log_writer_config, &self.config)?; | ||
| let runtime = self.setup_runtime(&logger)?; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we should just use this |
||
| let kv_store = runtime | ||
| .block_on(io::postgres_store::PostgresStore::new_with_logger( | ||
| connection_string, | ||
| db_name, | ||
| kv_table_name, | ||
| certificate_pem, | ||
| Some(Arc::clone(&logger)), | ||
| )) | ||
| .map_err(|e| { | ||
| log_error!(logger, "Failed to set up Postgres store: {e}"); | ||
| BuildError::KVStoreSetupFailed | ||
| })?; | ||
| self.build_with_store_runtime_and_logger(node_entropy, kv_store, runtime, logger) | ||
| } | ||
|
|
||
| /// Builds a [`Node`] instance with a [`FilesystemStoreV2`] backend and according to the options | ||
| /// previously configured. | ||
| /// | ||
|
|
@@ -789,18 +833,27 @@ impl NodeBuilder { | |
| self.build_with_store_and_logger(node_entropy, kv_store, logger) | ||
| } | ||
|
|
||
| fn build_with_store_and_logger<S: SyncAndAsyncKVStore + Send + Sync + 'static>( | ||
| &self, node_entropy: NodeEntropy, kv_store: S, logger: Arc<Logger>, | ||
| ) -> Result<Node, BuildError> { | ||
| let runtime = if let Some(handle) = self.runtime_handle.as_ref() { | ||
| Arc::new(Runtime::with_handle(handle.clone(), Arc::clone(&logger))) | ||
| fn setup_runtime(&self, logger: &Arc<Logger>) -> Result<Arc<Runtime>, BuildError> { | ||
| if let Some(handle) = self.runtime_handle.as_ref() { | ||
| Ok(Arc::new(Runtime::with_handle(handle.clone(), Arc::clone(logger)))) | ||
| } else { | ||
| Arc::new(Runtime::new(Arc::clone(&logger)).map_err(|e| { | ||
| Ok(Arc::new(Runtime::new(Arc::clone(logger)).map_err(|e| { | ||
| log_error!(logger, "Failed to setup tokio runtime: {}", e); | ||
| BuildError::RuntimeSetupFailed | ||
| })?) | ||
| }; | ||
| })?)) | ||
| } | ||
| } | ||
|
|
||
| fn build_with_store_and_logger<S: SyncAndAsyncKVStore + Send + Sync + 'static>( | ||
| &self, node_entropy: NodeEntropy, kv_store: S, logger: Arc<Logger>, | ||
| ) -> Result<Node, BuildError> { | ||
| let runtime = self.setup_runtime(&logger)?; | ||
| self.build_with_store_runtime_and_logger(node_entropy, kv_store, runtime, logger) | ||
| } | ||
|
|
||
| fn build_with_store_runtime_and_logger<S: SyncAndAsyncKVStore + Send + Sync + 'static>( | ||
| &self, node_entropy: NodeEntropy, kv_store: S, runtime: Arc<Runtime>, logger: Arc<Logger>, | ||
| ) -> Result<Node, BuildError> { | ||
| let seed_bytes = node_entropy.to_seed_bytes(); | ||
| let config = Arc::new(self.config.clone()); | ||
|
|
||
|
|
@@ -1116,6 +1169,58 @@ impl ArcedNodeBuilder { | |
| self.inner.read().expect("lock").build(*node_entropy).map(Arc::new) | ||
| } | ||
|
|
||
| /// Builds a [`Node`] instance with a [PostgreSQL] backend and according to the options | ||
| /// previously configured. | ||
| /// | ||
| /// Connects to the PostgreSQL database at the given `connection_string`, e.g., | ||
| /// `"postgres://user:password@localhost/ldk_db"`. | ||
| /// | ||
| /// The given `db_name` will be used or default to | ||
| /// [`DEFAULT_DB_NAME`](io::postgres_store::DEFAULT_DB_NAME). The `connection_string` must | ||
| /// not include a `dbname` when `db_name` is set, providing both is an error. The database | ||
| /// will be created automatically if it doesn't already exist. The initial connection is | ||
| /// made to the target database, and if it fails we fall back to the default `postgres` | ||
| /// database to create it. | ||
| /// | ||
| /// The given `kv_table_name` will be used or default to | ||
| /// [`DEFAULT_KV_TABLE_NAME`](io::postgres_store::DEFAULT_KV_TABLE_NAME). | ||
| /// | ||
| /// If `certificate_pem` is `Some`, TLS will be used for database connections and the | ||
| /// provided PEM-encoded CA certificate will be added to the system's default root | ||
| /// certificates (it does not replace them). If `certificate_pem` is `None`, connections | ||
| /// will be unencrypted. | ||
| /// | ||
| /// [PostgreSQL]: https://www.postgresql.org | ||
|
benthecarman marked this conversation as resolved.
|
||
| #[cfg(feature = "postgres")] | ||
| pub fn build_with_postgres_store( | ||
|
benthecarman marked this conversation as resolved.
|
||
| &self, node_entropy: Arc<NodeEntropy>, connection_string: String, db_name: Option<String>, | ||
| kv_table_name: Option<String>, certificate_pem: Option<String>, | ||
| ) -> Result<Arc<Node>, BuildError> { | ||
| self.inner | ||
| .read() | ||
| .unwrap() | ||
| .build_with_postgres_store( | ||
| *node_entropy, | ||
| connection_string, | ||
| db_name, | ||
| kv_table_name, | ||
| certificate_pem, | ||
| ) | ||
| .map(Arc::new) | ||
| } | ||
|
|
||
| /// Builds a [`Node`] instance with a [PostgreSQL] backend and according to the options | ||
| /// previously configured. | ||
| /// | ||
| /// This requires the `postgres` crate feature. | ||
| #[cfg(not(feature = "postgres"))] | ||
| pub fn build_with_postgres_store( | ||
| &self, _node_entropy: Arc<NodeEntropy>, _connection_string: String, | ||
| _db_name: Option<String>, _kv_table_name: Option<String>, _certificate_pem: Option<String>, | ||
| ) -> Result<Arc<Node>, BuildError> { | ||
| Err(BuildError::KVStoreSetupFailed) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We really need to find a way to avoid this. Just failing here is an impossible API, so we need to fix it before the next release. |
||
| } | ||
|
|
||
| /// Builds a [`Node`] instance with a [`FilesystemStoreV2`] backend and according to the options | ||
| /// previously configured. | ||
| pub fn build_with_fs_store( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| // This file is Copyright its original authors, visible in version control history. | ||
| // | ||
| // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
| // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or | ||
| // http://opensource.org/licenses/MIT>, at your option. You may not use this file except in | ||
| // accordance with one or both of these licenses. | ||
|
|
||
| use lightning::io; | ||
| use tokio_postgres::Client; | ||
|
|
||
| pub(super) async fn migrate_schema( | ||
| _client: &Client, _kv_table_name: &str, from_version: u16, to_version: u16, | ||
| ) -> io::Result<()> { | ||
| assert!(from_version < to_version); | ||
| // Future migrations go here, e.g.: | ||
| // if from_version == 1 && to_version >= 2 { | ||
| // migrate_v1_to_v2(client, kv_table_name).await?; | ||
| // from_version = 2; | ||
| // } | ||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, we probably want to introduce features even before the next release, but still not a fan of just adding a one-off feature for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reconsidered offline. Fine to leave for now, for the release we'll change it anyways as part of #900