Skip to main content

Commit Modules

Commit-Boost provides an open platform for developers to create and distribute commitment protocol sidecars. Commit Modules are the primary way to add custom logic: they run as sidecar processes alongside the PBS and Signer services, and can request signatures from the proposer.

For system context, see the Architecture Overview.

Config entry​

Each commit module is declared in the cb-config.toml file under a [[modules]] entry:

[[modules]]
id = "DA_COMMIT"
type = "commit"
docker_image = "my-module-image"
signing_id = "0x6a33a23ef26a4836979edff86c493a69b26ccf0b4a16491a815a13787657431b"
FieldDescription
idA unique identifier for the module (used for JWT scoping and container naming).
typeMust be "commit".
docker_imageThe Docker image to run for this module.
signing_idA 32-byte identifier used to scope signatures to this module (see Signing structure).
envOptional map of environment variables for the module.
env_fileOptional path to an environment file for the module.
(custom)Additional fields are passed through as opaque config to the module's runtime.
warning

Setting type = "pbs" in a [[modules]] entry is not a supported path. The configuration parser will reject it at parse time. If you want to extend the PBS binary itself, see Extending PBS.

Rust SDK usage​

While a module can be written in any language, we provide Rust utilities to simplify loading and running modules. Add to your Cargo.toml:

commit-boost = { git = "https://github.com/Commit-Boost/commit-boost-client", version = "..." }

Import the prelude:

use commit_boost::prelude::*;

Loading module config​

Your module will likely need a configuration section for the Node Operator to customize. Define it as a struct and pass it to load_commit_module_config:

#[derive(Debug, Deserialize)]
struct ExtraConfig {
sleep_secs: u64,
}

let mut config = load_commit_module_config::<ExtraConfig>().unwrap();
let to_sleep = config.extra.sleep_secs;

The returned StartCommitModuleConfig also provides:

  • id: unique module ID
  • chain: chain spec
  • signer_client: a pre-configured SignerClient to call the SignerAPI

Requesting signatures​

At its core, the Signer service provides a signature on a 32-byte data digest. Signatures are provided using either the validator keys (BLS) or a proxy key (BLS or ECDSA), both on the Commit-Boost domain.

Use TreeHash to create a digest from a custom struct:

#[derive(TreeHash)]
struct Datagram {
data: u64,
}

To request a signature, you need a public key. get_pubkeys returns a GetPubkeysResponse whose keys field maps each consensus key to its proxy keys; pick the consensus pubkey to sign with:

let pubkeys = config.signer_client.get_pubkeys().await.unwrap();
let pubkey = pubkeys.keys.first().unwrap().consensus.clone();

JWT tokens are created and refreshed internally by SignerClient: each method generates a fresh token with the correct route, exp, and payload_hash claims automatically. No manual token management is needed.

Consensus key signatures​

let datagram = Datagram { data: 1 };
let request = SignConsensusRequest::builder(pubkey.clone()).with_msg(&datagram);
let response = config.signer_client.request_consensus_signature(request).await.unwrap();
let signature = response.signature;

The response also carries the nonce and module_signing_id needed to verify the signature.

Proxy key signatures​

First, generate a proxy key for a given consensus key. We support BLS and ECDSA:

// BLS proxy
let proxy_delegation = config.signer_client.generate_proxy_key_bls(pubkey.clone()).await?;
let proxy_pubkey = proxy_delegation.message.proxy;

// ECDSA proxy
let proxy_delegation = config.signer_client.generate_proxy_key_ecdsa(pubkey.clone()).await?;
let proxy_address = proxy_delegation.message.proxy;

Then request a signature using the proxy key:

// BLS proxy
let datagram = Datagram { data: 1 };
let request = SignProxyRequest::builder(proxy_pubkey).with_msg(&datagram);
let response = config.signer_client.request_proxy_signature_bls(request).await.unwrap();
let signature = response.signature;

// ECDSA proxy
let datagram = Datagram { data: 1 };
let request = SignProxyRequest::builder(proxy_address).with_msg(&datagram);
let response = config.signer_client.request_proxy_signature_ecdsa(request).await.unwrap();
let signature = response.signature;

Signing structure​

For details on the signing structure, including domain separation, nonces, SSZ Merkle tree construction, and the signing ID format, see Requesting Proposer Commitment Signatures.

Metrics​

Modules can record custom metrics that are automatically scraped by Prometheus.

Define metrics​

Use the prometheus crate, with the statics wrapped in lazy_static! (from the lazy_static crate):

lazy_static! {
static ref MY_CUSTOM_REGISTRY: Registry = Registry::new_custom(Some("da_commit".to_string()), None).unwrap();
static ref SIG_RECEIVED_COUNTER: IntCounter = IntCounter::new("signature_received", "successful signature requests received").unwrap();
}

Start the metrics provider​

MY_CUSTOM_REGISTRY.register(Box::new(SIG_RECEIVED_COUNTER.clone())).unwrap();
MetricsProvider::load_and_run(config.chain, MY_CUSTOM_REGISTRY.clone()).unwrap();

This starts a server with a /metrics endpoint on the port from CB_METRICS_PORT (see Running with binary).

Record metrics​

SIG_RECEIVED_COUNTER.inc();

For a full reference of available metrics, see the Metrics catalog. When [metrics] is enabled, commit-boost init prints the scrape targets for each service; add them to your own Prometheus config (see Metrics).