Skip to content

cuprated: embeddable node library with Node::launch()#592

Open
redsh4de wants to merge 20 commits intoCuprate:mainfrom
redsh4de:feat/embeddable-cuprate
Open

cuprated: embeddable node library with Node::launch()#592
redsh4de wants to merge 20 commits intoCuprate:mainfrom
redsh4de:feat/embeddable-cuprate

Conversation

@redsh4de
Copy link
Copy Markdown
Contributor

@redsh4de redsh4de commented Mar 16, 2026

What

Extracts cuprated into an embeddable library (lib.rs/main.rs pattern). Takes inspiration from the API proposed in #554

Closes #516

Note to reviewers: This does not implement graceful shutdown / panic removal yet, those have been deferred to #585. For the final state with all changes, skip to #590

Why

To enable embedding the node in other applications

Where

  • cuprated:
    • Cargo.toml - explicit [lib] + [[bin]] targets
    • lib.rs - new: Node struct, NodeContext for shared state, Node::launch(), public service handles
    • main.rs - simplified to CLI wrapper calling Node::launch()
    • commands.rs - actor pattern rewrite: CommandHandle with mpsc/oneshot, send_command() API
    • config.rs - decoupled from CLI args, find_config() returns Result<Option<Config>>, removed process::exit from library code
    • blockchain/syncer.rs - SyncNotify renamed to SyncState
    • blockchain/interface.rs - free functions + statics (COMMAND_TX, BLOCKS_BEING_HANDLED) converted to BlockchainManagerHandle struct with owned channel + lock
    • version.rs - add Default impl (thanks clippy)
    • Removed mutable statics in favor of NodeContext for multi-instance support
  • consensus/fast-sync:
    • Removed global FAST_SYNC_HASHES static
    • set_fast_sync_hashes removed; fast_sync_stop_height and validate_entries now take hashes as a parameter from NodeContext

How

Node::launch(config) in lib.rs handles all initialization and returns a Node with service handles that serve as the API to the embeder:

pub struct Node {
    pub context: BlockchainContextService,
    pub blockchain: BlockchainReadHandle,
    pub txpool: TxpoolReadHandle,
    pub clearnet: NetworkInterface<ClearNet>,
    pub tor: Option<oneshot::Receiver<NetworkInterface<Tor>>>,
    pub syncer: SyncerHandle,
    pub command: CommandHandle,
}

The previous mutable statics were moved into NodeContext, making the node safe to run multiple times in a single process.

pub struct NodeContext {
    pub network: Network,
    pub fast_sync_hashes: &'static [[u8; 32]], /// was static
    pub reorg_lock: Arc<RwLock<()>>, // was static
    pub blockchain_manager: BlockchainManagerHandle, // was composed of statics
    pub blockchain_read: BlockchainReadHandle,
    pub blockchain_context: BlockchainContextService,
    pub txpool_read: TxpoolReadHandle,
    pub syncer: SyncerHandle,
    pub start_instant: std::time::SystemTime,
    pub start_instant_unix: u64,
}

BlockchainManagerHandle replaces the COMMAND_TX and BLOCKS_BEING_HANDLED statics from interface.rs, wrapping them into a struct with new(), is_block_being_handled(), and handle_incoming_block() methods.

The Tor interface arrives via oneshot after sync, similar to the dandelion.rs pattern.

@github-actions github-actions bot added the A-binaries Area: Related to binaries. label Mar 16, 2026
@redsh4de redsh4de marked this pull request as draft March 16, 2026 16:38
@redsh4de redsh4de marked this pull request as ready for review March 16, 2026 17:45
@github-actions github-actions bot added the A-dependency Area: Related to dependencies, or changes to a Cargo.{toml,lock} file. label Mar 17, 2026
@redsh4de redsh4de force-pushed the feat/embeddable-cuprate branch from c680c1c to 6542272 Compare March 17, 2026 12:59
@redsh4de redsh4de force-pushed the feat/embeddable-cuprate branch from bdbba2c to 81d85e1 Compare March 17, 2026 22:57
@redsh4de redsh4de force-pushed the feat/embeddable-cuprate branch from 81d85e1 to 618b162 Compare March 18, 2026 03:38
@github-actions github-actions bot added the A-consensus Area: Related to consensus. label Mar 18, 2026
/// Panics if the database is corrupt or critical services fail to start.
pub async fn launch(config: Config) -> Self {
// Initialize global static `LazyLock` data.
statics::init_lazylock_statics();
Copy link
Copy Markdown

@sneurlax sneurlax Mar 19, 2026

Choose a reason for hiding this comment

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

statics::init_lazylock_statics() twice--once in main.rs, once here. What should actually be responsible for this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ideally this should be called by the embedder imo.

However, it's kept twice because if it's called a second time, it's a no-op, and if the embedder doesnt do it the library will take care of it

Comment on lines +163 to +169
let (mut blockchain_read_handle, mut blockchain_write_handle, _) =
cuprate_blockchain::service::init_with_pool(
config.blockchain_config(),
Arc::clone(&db_thread_pool),
)
.inspect_err(|e| error!("Blockchain database error: {e}"))
.expect(DATABASE_CORRUPT_MSG);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

won't this panic if the db is corrupt, misconfigured, etc? maybe locked?

inspect_err logs it then panics anyways?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It will - the graceful shutdown PRs that are dependant on this branch gradually introduce error propagation and get rid of the expects

They are split into parts so that its easier to review, as the changes there are quite verbose

Comment on lines +171 to +174
let (txpool_read_handle, txpool_write_handle, _) =
cuprate_txpool::service::init_with_pool(&config.txpool_config(), db_thread_pool)
.inspect_err(|e| error!("Txpool database error: {e}"))
.expect(DATABASE_CORRUPT_MSG);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

same thing here, will this panic if the db is corrup tor locked?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same as prior, deferred to the other three graceful shutdown PRs

Comment on lines +194 to +197
let context_svc =
blockchain::init_consensus(blockchain_read_handle.clone(), config.context_config())
.await
.unwrap();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

this naked unwrap can cause a panic w no useful msg if consensus init fails

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Deferred to PR #586

@sneurlax
Copy link
Copy Markdown

you should try and return something like Result<Self, NodeInitError> rather than panicking

I don't get the difference between or relative purposes of Node and NodeContext. Which should be used for what?

with dry_run_check calling std::process::exit(code), won't an embedder calling config.dry_run_check() get their process killed?

nit but SyncState renamed unnecessarily imo

there's no way to shut it down?

I like main.rs after this tho. I like CommandHandle, get_fast_sync_hashes, BlockchainManagerHandle. not an in depth review but these things stood out to me in a once-over and some tangential reading to try and understand the changes

@redsh4de
Copy link
Copy Markdown
Contributor Author

redsh4de commented Mar 19, 2026

you should try and return something like Result<Self, NodeInitError> rather than panicking

Deferred this to the shutdown PRs - they depend on this branch so they carry everything over (https://github.com/Cuprate/cuprate/pull/585/changes#diff-d448bb1addb0153e0d62b48a7e6531f9988ea8f1846ab783540eaf9f2c87d87fR159)

I don't get the difference between or relative purposes of Node and NodeContext. Which should be used for what?

Node is meant to be the public facing API that the embedder receives and can interact with the node
NodeContext is meant to be the internal global context, replaces statics. Some things in each struct overlap but i prefer the granularity for the two seperate domains

Maybe there could be a better name for each struct so its self-documenting?

with dry_run_check calling std::process::exit(code), won't an embedder calling config.dry_run_check() get their process killed?

Yeah, good call

nit but SyncState renamed unnecessarily imo

It was a transitive change as i was also planning to add current sync target height to the struct, so the embedder can potentially create progress UIs/bars etc, got sidetracked though

there's no way to shut it down?

Ty, added shutdown function to #585 (https://github.com/Cuprate/cuprate/pull/585/changes#diff-d448bb1addb0153e0d62b48a7e6531f9988ea8f1846ab783540eaf9f2c87d87fR342-R344)

@sneurlax
Copy link
Copy Markdown

you should try and return something like Result<Self, NodeInitError> rather than panicking

Deferred this to the shutdown PRs - they depend on this branch so they carry everything over (https://github.com/Cuprate/cuprate/pull/585/changes#diff-d448bb1addb0153e0d62b48a7e6531f9988ea8f1846ab783540eaf9f2c87d87fR159)

I don't get the difference between or relative purposes of Node and NodeContext. Which should be used for what?

Node is meant to be the public facing API that the embedder receives and can interact with the node NodeContext is meant to be the internal global context, replaces statics. Some things in each struct overlap but i prefer the granularity for the two seperate domains

Maybe there could be a better name for each struct so its self-documenting?

with dry_run_check calling std::process::exit(code), won't an embedder calling config.dry_run_check() get their process killed?

Yeah, good call

nit but SyncState renamed unnecessarily imo

It was a transitive change as i was also planning to add current sync target height to the struct, so the embedder can potentially create progress UIs/bars etc, got sidetracked though

there's no way to shut it down?

Ty, added shutdown function to #585 (https://github.com/Cuprate/cuprate/pull/585/changes#diff-d448bb1addb0153e0d62b48a7e6531f9988ea8f1846ab783540eaf9f2c87d87fR342-R344)

Gotcha, OK :) sorry for the incomplete review then, glad to hear most of those issues are resolved by later PRs

@SyntheticBird45
Copy link
Copy Markdown
Member

cancelling workflow because this is some doc nits, let's think a little about our carbon footprint.

@redsh4de
Copy link
Copy Markdown
Contributor Author

Think this is the last change i want to make here, didn't like how SyncNotify/State effectively functions as a handle without it being named such, reduces the mental load imo

Also added target_height to the handle so that embedders can create their own progress tracking items and whatnots

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

Labels

A-binaries Area: Related to binaries. A-consensus Area: Related to consensus. A-dependency Area: Related to dependencies, or changes to a Cargo.{toml,lock} file.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make cuprate usable as a library

3 participants