Skip to content
This repository was archived by the owner on Jan 16, 2026. It is now read-only.
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
24 changes: 24 additions & 0 deletions bin/node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,10 @@ kona-node \
Many configuration options can be set via environment variables:

- `KONA_NODE_L1_ETH_RPC` - L1 execution client RPC URL
- `KONA_NODE_L1_TRUST_RPC` - Whether to trust the L1 RPC without verification (default: true)
- `KONA_NODE_L1_BEACON` - L1 beacon API URL
- `KONA_NODE_L2_ENGINE_RPC` - L2 engine API URL
- `KONA_NODE_L2_TRUST_RPC` - Whether to trust the L2 RPC without verification (default: true)
- `KONA_NODE_L2_ENGINE_AUTH` - Path to L2 engine JWT secret file
- `KONA_NODE_MODE` - Node operation mode (default: validator)
- `RUST_LOG` - Logging configuration
Expand Down Expand Up @@ -143,4 +145,26 @@ kona-node info --help

## Advanced Configuration

### RPC Trust Configuration

By default, Kona trusts RPC providers and does not perform additional block hash verification, optimizing for performance. This can be configured using trust flags:

```bash
# For untrusted/public RPC providers (adds verification)
kona-node node \
--l1 https://public-rpc-endpoint.com \
--l1-trust-rpc false \
--l2 https://another-public-rpc.com \
--l2-trust-rpc false \
# ... other options
```

**Security Considerations:**
- Default behavior (`true`): No additional verification, assumes RPC is trustworthy
- Verification mode (`false`): All block hashes are verified against requested hashes
- Use verification (`false`) for public or third-party RPC endpoints
- Default trust (`true`) is suitable for local nodes and trusted infrastructure

### Production Deployments

For production deployments and advanced configurations, refer to the docker recipe in the main repository at `docker/recipes/kona-node/` which provides a complete setup example with monitoring and multiple services.
22 changes: 22 additions & 0 deletions bin/node/src/commands/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,30 @@ pub struct NodeCommand {
/// URL of the L1 execution client RPC API.
#[arg(long, visible_alias = "l1", env = "KONA_NODE_L1_ETH_RPC")]
pub l1_eth_rpc: Url,
/// Whether to trust the L1 RPC.
/// If false, block hash verification is performed for all retrieved blocks.
#[arg(
long,
visible_alias = "l1.trust-rpc",
env = "KONA_NODE_L1_TRUST_RPC",
default_value = "true"
)]
pub l1_trust_rpc: bool,
/// URL of the L1 beacon API.
#[arg(long, visible_alias = "l1.beacon", env = "KONA_NODE_L1_BEACON")]
pub l1_beacon: Url,
/// URL of the engine API endpoint of an L2 execution client.
#[arg(long, visible_alias = "l2", env = "KONA_NODE_L2_ENGINE_RPC")]
pub l2_engine_rpc: Url,
/// Whether to trust the L2 RPC.
/// If false, block hash verification is performed for all retrieved blocks.
#[arg(
long,
visible_alias = "l2.trust-rpc",
env = "KONA_NODE_L2_TRUST_RPC",
default_value = "true"
)]
pub l2_trust_rpc: bool,
/// JWT secret for the auth-rpc endpoint of the execution client.
/// This MUST be a valid path to a file containing the hex-encoded JWT secret.
#[arg(long, visible_alias = "l2.jwt-secret", env = "KONA_NODE_L2_ENGINE_AUTH")]
Expand All @@ -111,8 +129,10 @@ impl Default for NodeCommand {
fn default() -> Self {
Self {
l1_eth_rpc: Url::parse("http://localhost:8545").unwrap(),
l1_trust_rpc: true,
l1_beacon: Url::parse("http://localhost:5052").unwrap(),
l2_engine_rpc: Url::parse("http://localhost:8551").unwrap(),
l2_trust_rpc: true,
l2_engine_jwt_secret: None,
l2_config_file: None,
node_mode: NodeMode::Validator,
Expand Down Expand Up @@ -264,8 +284,10 @@ impl NodeCommand {
.with_mode(self.node_mode)
.with_jwt_secret(jwt_secret)
.with_l1_provider_rpc_url(self.l1_eth_rpc)
.with_l1_trust_rpc(self.l1_trust_rpc)
.with_l1_beacon_api_url(self.l1_beacon)
.with_l2_engine_rpc_url(self.l2_engine_rpc)
.with_l2_trust_rpc(self.l2_trust_rpc)
.with_p2p_config(p2p_config)
.with_rpc_config(rpc_config)
.with_sequencer_config(self.sequencer_flags.config())
Expand Down
14 changes: 11 additions & 3 deletions crates/node/service/src/actors/derivation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,14 @@ pub trait PipelineBuilder: Send + Sync + 'static {
pub struct DerivationBuilder {
/// The L1 provider.
pub l1_provider: RootProvider,
/// Whether to trust the L1 RPC.
pub l1_trust_rpc: bool,
/// The L1 beacon client.
pub l1_beacon: OnlineBeaconClient,
/// The L2 provider.
pub l2_provider: RootProvider<Optimism>,
/// Whether to trust the L2 RPC.
pub l2_trust_rpc: bool,
/// The rollup config.
pub rollup_config: Arc<RollupConfig>,
/// The interop mode.
Expand All @@ -114,12 +118,16 @@ impl PipelineBuilder for DerivationBuilder {

async fn build(self) -> DerivationState<OnlinePipeline> {
// Create the caching L1/L2 EL providers for derivation.
let l1_derivation_provider =
AlloyChainProvider::new(self.l1_provider.clone(), DERIVATION_PROVIDER_CACHE_SIZE);
let l2_derivation_provider = AlloyL2ChainProvider::new(
let l1_derivation_provider = AlloyChainProvider::new_with_trust(
self.l1_provider.clone(),
DERIVATION_PROVIDER_CACHE_SIZE,
self.l1_trust_rpc,
);
let l2_derivation_provider = AlloyL2ChainProvider::new_with_trust(
self.l2_provider.clone(),
self.rollup_config.clone(),
DERIVATION_PROVIDER_CACHE_SIZE,
self.l2_trust_rpc,
);

let pipeline = match self.interop_mode {
Expand Down
14 changes: 11 additions & 3 deletions crates/node/service/src/actors/sequencer/actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,20 +119,28 @@ pub struct SequencerBuilder {
pub rollup_cfg: Arc<RollupConfig>,
/// The L1 provider.
pub l1_provider: RootProvider,
/// Whether to trust the L1 RPC.
pub l1_trust_rpc: bool,
/// The L2 provider.
pub l2_provider: RootProvider<Optimism>,
/// Whether to trust the L2 RPC.
pub l2_trust_rpc: bool,
}

impl AttributesBuilderConfig for SequencerBuilder {
type AB = StatefulAttributesBuilder<AlloyChainProvider, AlloyL2ChainProvider>;

fn build(self) -> Self::AB {
let l1_derivation_provider =
AlloyChainProvider::new(self.l1_provider.clone(), DERIVATION_PROVIDER_CACHE_SIZE);
let l2_derivation_provider = AlloyL2ChainProvider::new(
let l1_derivation_provider = AlloyChainProvider::new_with_trust(
self.l1_provider.clone(),
DERIVATION_PROVIDER_CACHE_SIZE,
self.l1_trust_rpc,
);
let l2_derivation_provider = AlloyL2ChainProvider::new_with_trust(
self.l2_provider.clone(),
self.rollup_cfg.clone(),
DERIVATION_PROVIDER_CACHE_SIZE,
self.l2_trust_rpc,
);
StatefulAttributesBuilder::new(
self.rollup_cfg,
Expand Down
16 changes: 16 additions & 0 deletions crates/node/service/src/service/standard/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,14 @@ pub struct RollupNodeBuilder {
config: RollupConfig,
/// The L1 EL provider RPC URL.
l1_provider_rpc_url: Option<Url>,
/// Whether to trust the L1 RPC.
l1_trust_rpc: bool,
/// The L1 beacon API URL.
l1_beacon_api_url: Option<Url>,
/// The L2 engine RPC URL.
l2_engine_rpc_url: Option<Url>,
/// Whether to trust the L2 RPC.
l2_trust_rpc: bool,
/// The JWT secret.
jwt_secret: Option<JwtSecret>,
/// The [`NetworkConfig`].
Expand Down Expand Up @@ -60,6 +64,11 @@ impl RollupNodeBuilder {
Self { l1_provider_rpc_url: Some(l1_provider_rpc_url), ..self }
}

/// Sets whether to trust the L1 RPC.
pub fn with_l1_trust_rpc(self, l1_trust_rpc: bool) -> Self {
Self { l1_trust_rpc, ..self }
}

/// Appends an L1 beacon API URL to the builder.
pub fn with_l1_beacon_api_url(self, l1_beacon_api_url: Url) -> Self {
Self { l1_beacon_api_url: Some(l1_beacon_api_url), ..self }
Expand All @@ -70,6 +79,11 @@ impl RollupNodeBuilder {
Self { l2_engine_rpc_url: Some(l2_engine_rpc_url), ..self }
}

/// Sets whether to trust the L2 RPC.
pub fn with_l2_trust_rpc(self, l2_trust_rpc: bool) -> Self {
Self { l2_trust_rpc, ..self }
}

/// Appends a JWT secret to the builder.
pub fn with_jwt_secret(self, jwt_secret: JwtSecret) -> Self {
Self { jwt_secret: Some(jwt_secret), ..self }
Expand Down Expand Up @@ -136,8 +150,10 @@ impl RollupNodeBuilder {
config: rollup_config,
interop_mode: self.interop_mode,
l1_provider,
l1_trust_rpc: self.l1_trust_rpc,
l1_beacon,
l2_provider,
l2_trust_rpc: self.l2_trust_rpc,
engine_builder,
rpc_builder: self.rpc_config,
p2p_config,
Expand Down
8 changes: 8 additions & 0 deletions crates/node/service/src/service/standard/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@ pub struct RollupNode {
pub(crate) interop_mode: InteropMode,
/// The L1 EL provider.
pub(crate) l1_provider: RootProvider,
/// Whether to trust the L1 RPC.
pub(crate) l1_trust_rpc: bool,
/// The L1 beacon API.
pub(crate) l1_beacon: OnlineBeaconClient,
/// The L2 EL provider.
pub(crate) l2_provider: RootProvider<Optimism>,
/// Whether to trust the L2 RPC.
pub(crate) l2_trust_rpc: bool,
/// The [`EngineBuilder`] for the node.
pub(crate) engine_builder: EngineBuilder,
/// The [`RpcBuilder`] for the node.
Expand Down Expand Up @@ -79,7 +83,9 @@ impl RollupNodeService for RollupNode {
seq_cfg: self.sequencer_config.clone(),
rollup_cfg: self.config.clone(),
l1_provider: self.l1_provider.clone(),
l1_trust_rpc: self.l1_trust_rpc,
l2_provider: self.l2_provider.clone(),
l2_trust_rpc: self.l2_trust_rpc,
}
}

Expand All @@ -94,8 +100,10 @@ impl RollupNodeService for RollupNode {
fn derivation_builder(&self) -> DerivationBuilder {
DerivationBuilder {
l1_provider: self.l1_provider.clone(),
l1_trust_rpc: self.l1_trust_rpc,
l1_beacon: self.l1_beacon.clone(),
l2_provider: self.l2_provider.clone(),
l2_trust_rpc: self.l2_trust_rpc,
rollup_config: self.config.clone(),
interop_mode: self.interop_mode,
}
Expand Down
42 changes: 42 additions & 0 deletions crates/providers/providers-alloy/src/chain_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ use std::{boxed::Box, num::NonZeroUsize, vec::Vec};
pub struct AlloyChainProvider {
/// The inner Ethereum JSON-RPC provider.
pub inner: RootProvider,
/// Whether to trust the RPC without verification.
pub trust_rpc: bool,
/// `header_by_hash` LRU cache.
header_by_hash_cache: LruCache<B256, Header>,
/// `receipts_by_hash_cache` LRU cache.
Expand All @@ -33,8 +35,17 @@ impl AlloyChainProvider {
/// ## Panics
/// - Panics if `cache_size` is zero.
pub fn new(inner: RootProvider, cache_size: usize) -> Self {
Self::new_with_trust(inner, cache_size, true)
}

/// Creates a new [AlloyChainProvider] with the given alloy provider and trust setting.
///
/// ## Panics
/// - Panics if `cache_size` is zero.
pub fn new_with_trust(inner: RootProvider, cache_size: usize, trust_rpc: bool) -> Self {
Self {
inner,
trust_rpc,
header_by_hash_cache: LruCache::new(NonZeroUsize::new(cache_size).unwrap()),
receipts_by_hash_cache: LruCache::new(NonZeroUsize::new(cache_size).unwrap()),
block_info_and_transactions_by_hash_cache: LruCache::new(
Expand Down Expand Up @@ -67,6 +78,31 @@ impl AlloyChainProvider {
pub async fn chain_id(&mut self) -> Result<u64, RpcError<TransportErrorKind>> {
self.inner.get_chain_id().await
}

/// Verifies that a header's hash matches the expected hash when trust_rpc is false.
fn verify_header_hash(
&self,
header: &Header,
expected_hash: B256,
) -> Result<(), AlloyChainProviderError> {
if self.trust_rpc {
return Ok(());
}

let actual_hash = header.hash_slow();
Comment thread
refcell marked this conversation as resolved.
if actual_hash != expected_hash {
return Err(AlloyChainProviderError::Transport(RpcError::Transport(
TransportErrorKind::Custom(
format!(
"Header hash mismatch: expected {expected_hash:?}, got {actual_hash:?}"
)
.into(),
),
)));
}

Ok(())
}
}

/// An error for the [AlloyChainProvider].
Expand Down Expand Up @@ -126,6 +162,9 @@ impl ChainProvider for AlloyChainProvider {
.ok_or(AlloyChainProviderError::BlockNotFound(hash.into()))?;
let header = block.header.into_consensus();

// Verify the header hash matches what we requested
self.verify_header_hash(&header, hash)?;

self.header_by_hash_cache.put(hash, header.clone());

kona_macros::inc!(gauge, Metrics::CACHE_ENTRIES, "cache" => "header_by_hash");
Expand Down Expand Up @@ -212,6 +251,9 @@ impl ChainProvider for AlloyChainProvider {
.into_consensus()
.map_transactions(|t| t.inner.into_inner());

// Verify the block hash matches what we requested
self.verify_header_hash(&block.header, hash)?;

let block_info = BlockInfo {
hash: block.header.hash_slow(),
number: block.header.number,
Expand Down
Loading