Skip to content
Merged
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@
logs

**/*/CLAUDE.md
/.claude
/.claude
backups/
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ async fn main() -> Result<(), ddk::error::Error> {
DDK is designed with a pluggable architecture, allowing you to choose or implement your own components:

- **Transport**: Communication layer for DLC messages between peers. Implementations include Lightning Network gossip and Nostr protocol messaging.
- **Storage**: Persistence backend for contracts and wallet data. Implementations include Sled (embedded) and PostgreSQL.
- **Storage**: Persistence backend for contracts and wallet data. Implementations include Sled (embedded) and PostgreSQL. Databases written by releases up to 2.0 are moved to the columnar contract layout on upgrade; see [docs/postgres-contract-migration.md](./docs/postgres-contract-migration.md).
- **Oracle**: External data source for contract attestations. Implementations include HTTP and Nostr-based oracle clients.

You can create a custom DDK instance by implementing the required traits defined in [`ddk/src/lib.rs`](./ddk/src/lib.rs).
Expand Down
5 changes: 5 additions & 0 deletions ddk-manager/src/contract/offered_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,11 @@ impl OfferedContract {
})
}

/// The id of the keys this contract signs with.
pub fn keys_id(&self) -> KeysId {
self.keys_id
}

/// The chain hash to put on offer messages for this contract.
///
/// Contracts stored before ddk tracked the chain hash have none to
Expand Down
7 changes: 5 additions & 2 deletions ddk-node/src/bin/node.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use clap::Parser;
use ddk_node::opts::NodeOpts;
use ddk_node::opts::{NodeCommand, NodeOpts};
use ddk_node::DdkNode;
use std::str::FromStr;
use tracing::level_filters::LevelFilter;
Expand Down Expand Up @@ -34,7 +34,10 @@ async fn main() -> anyhow::Result<()> {

tracing::subscriber::set_global_default(subscriber).unwrap();

DdkNode::serve(opts).await?;
match opts.command {
Some(NodeCommand::Migrate) => DdkNode::migrate(opts).await?,
None => DdkNode::serve(opts).await?,
}

Ok(())
}
32 changes: 32 additions & 0 deletions ddk-node/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,38 @@ impl DdkNode {
}
}

/// Applies the schema migrations and moves every contract still stored
/// in the legacy blob layout to the columnar layout, then returns.
///
/// `PostgresStore::new` with migrations on does the same work, so a node
/// that starts normally migrates as well. This is for operators who want
/// to run the migration ahead of an upgrade, or against a restored
/// backup first.
pub async fn migrate(opts: NodeOpts) -> anyhow::Result<()> {
let logger = Arc::new(Logger::console(
"console_logger".to_string(),
LogLevel::from(opts.log),
));
let storage =
PostgresStore::new(&opts.postgres_url, false, logger.clone(), opts.name).await?;
let report = storage.run_migrations().await?;
println!(
"Migrated {} contract(s) to the columnar layout. {} could not be moved.",
report.migrated,
report.failed.len()
);
for (id, error) in &report.failed {
println!(" {id}: {error}");
}
if !report.is_complete() {
anyhow::bail!(
"{} contract(s) are still in the legacy blob layout",
report.failed.len()
);
}
Ok(())
}

pub async fn serve(opts: NodeOpts) -> anyhow::Result<()> {
let logger = Arc::new(Logger::console(
"console_logger".to_string(),
Expand Down
13 changes: 12 additions & 1 deletion ddk-node/src/opts.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use clap::Parser;
use clap::{Parser, Subcommand};
use std::path::PathBuf;

#[derive(Parser, Clone, Debug)]
Expand Down Expand Up @@ -60,4 +60,15 @@ pub struct NodeOpts {
#[arg(long)]
#[arg(help = "Endpoint for bitcoind ZeroMQ blockhash notifications")]
pub zmq_blockhash_endpoint: Option<String>,
#[command(subcommand)]
pub command: Option<NodeCommand>,
}

/// A one-shot task to run instead of serving the node.
#[derive(Subcommand, Clone, Debug)]
pub enum NodeCommand {
/// Apply the schema migrations and move every contract still stored in
/// the legacy blob layout to the columnar layout, then exit. Safe to run
/// again. Takes a backup of the database first.
Migrate,
}
Loading
Loading