Skip to content
Closed
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
12 changes: 12 additions & 0 deletions node/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ pub struct RunFlags {
#[arg(long, default_value_t = 3030)]
pub rpc_port: u16,

/// Address authorized to sign pause/unpause RPC requests
#[cfg(feature = "permissioned")]
#[arg(long, default_value_t = String::from(summit_rpc::DEFAULT_PAUSE_ADMIN_ADDRESS_HEX))]
pub pause_admin_address: String,

/// Number of tokio worker threads (defaults to number of logical CPUs)
#[arg(long)]
pub worker_threads: Option<usize>,
Expand Down Expand Up @@ -674,6 +679,11 @@ where
let engine: Engine<_, _, _, _> = Engine::new(context.with_label("engine"), config).await;
#[cfg(feature = "permissioned")]
let paused = engine.paused.clone();
#[cfg(feature = "permissioned")]
let pause_admin = match summit_rpc::parse_admin_address(&flags.pause_admin_address) {
Ok(address) => address,
Err(_) => panic!("invalid --pause-admin-address"),
};

let finalizer_mailbox = engine.finalizer_mailbox.clone();
let engine = engine.start(pending, recovered, resolver, broadcaster, backfiller);
Expand All @@ -692,6 +702,8 @@ where
stop_signal,
#[cfg(feature = "permissioned")]
paused,
#[cfg(feature = "permissioned")]
pause_admin,
)
.await
{
Expand Down
2 changes: 2 additions & 0 deletions node/src/bin/observer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,8 @@ fn get_node_flags(node: usize, genesis_path: &str) -> RunFlags {
prom_port: (28600 + (node * 10)) as u16,
prom_ip: "0.0.0.0".into(),
rpc_port: (3030 + (node * 10)) as u16,
#[cfg(feature = "permissioned")]
pause_admin_address: summit_rpc::DEFAULT_PAUSE_ADMIN_ADDRESS_HEX.into(),
worker_threads: Some(2),
log_level: "debug".into(),
db_prefix: format!("{node}"),
Expand Down
2 changes: 2 additions & 0 deletions node/src/bin/protocol_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,8 @@ fn get_node_flags(node: usize, genesis_path: &str) -> RunFlags {
prom_port: (28600 + (node * 10)) as u16,
prom_ip: "0.0.0.0".into(),
rpc_port: (3030 + (node * 10)) as u16,
#[cfg(feature = "permissioned")]
pause_admin_address: summit_rpc::DEFAULT_PAUSE_ADMIN_ADDRESS_HEX.into(),
worker_threads: Some(2),
log_level: "debug".into(),
db_prefix: format!("{node}"),
Expand Down
2 changes: 2 additions & 0 deletions node/src/bin/stake_and_checkpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,8 @@ fn get_node_flags(node: usize, genesis_path: &str) -> RunFlags {
prom_port: (28600 + (node * 10)) as u16,
prom_ip: "0.0.0.0".into(),
rpc_port: (3030 + (node * 10)) as u16,
#[cfg(feature = "permissioned")]
pause_admin_address: summit_rpc::DEFAULT_PAUSE_ADMIN_ADDRESS_HEX.into(),
worker_threads: Some(2),
log_level: "debug".into(),
db_prefix: format!("{node}"),
Expand Down
2 changes: 2 additions & 0 deletions node/src/bin/stake_and_join_with_outdated_ckpt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,8 @@ fn get_node_flags(node: usize, genesis_path: &str) -> RunFlags {
prom_port: (28600 + (node * 10)) as u16,
prom_ip: "0.0.0.0".into(),
rpc_port: (3030 + (node * 10)) as u16,
#[cfg(feature = "permissioned")]
pause_admin_address: summit_rpc::DEFAULT_PAUSE_ADMIN_ADDRESS_HEX.into(),
worker_threads: Some(2),
log_level: "debug".into(),
db_prefix: format!("{node}"),
Expand Down
2 changes: 2 additions & 0 deletions node/src/bin/sync_from_genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,8 @@ fn get_node_flags(node: usize, genesis_path: &str) -> RunFlags {
prom_port: (28600 + (node * 10)) as u16,
prom_ip: "0.0.0.0".into(),
rpc_port: (3030 + (node * 10)) as u16,
#[cfg(feature = "permissioned")]
pause_admin_address: summit_rpc::DEFAULT_PAUSE_ADMIN_ADDRESS_HEX.into(),
worker_threads: Some(2),
log_level: "debug".into(),
db_prefix: format!("{node}"),
Expand Down
2 changes: 2 additions & 0 deletions node/src/bin/testnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ fn get_node_flags(node: usize) -> RunFlags {
prom_port: (28600 + (node * 10)) as u16,
prom_ip: "0.0.0.0".into(),
rpc_port: (3030 + (node * 10)) as u16,
#[cfg(feature = "permissioned")]
pause_admin_address: summit_rpc::DEFAULT_PAUSE_ADMIN_ADDRESS_HEX.into(),
worker_threads: Some(2),
log_level: "debug".into(),
db_prefix: format!("{node}"),
Expand Down
2 changes: 2 additions & 0 deletions node/src/bin/verify_consensus_state_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,8 @@ fn get_node_flags(node: usize, genesis_path: &str) -> RunFlags {
prom_port: (28600 + (node * 10)) as u16,
prom_ip: "0.0.0.0".into(),
rpc_port: (3030 + (node * 10)) as u16,
#[cfg(feature = "permissioned")]
pause_admin_address: summit_rpc::DEFAULT_PAUSE_ADMIN_ADDRESS_HEX.into(),
worker_threads: Some(2),
log_level: "debug".into(),
db_prefix: format!("{node}"),
Expand Down
2 changes: 2 additions & 0 deletions node/src/bin/withdraw_and_exit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,8 @@ fn get_node_flags(node: usize, genesis_path: &str) -> RunFlags {
prom_port: (28600 + (node * 10)) as u16,
prom_ip: "0.0.0.0".into(),
rpc_port: (3030 + (node * 10)) as u16,
#[cfg(feature = "permissioned")]
pause_admin_address: summit_rpc::DEFAULT_PAUSE_ADMIN_ADDRESS_HEX.into(),
worker_threads: Some(2),
log_level: "debug".into(),
db_prefix: format!("{node}"),
Expand Down
34 changes: 22 additions & 12 deletions rpc/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,24 @@ use commonware_utils::from_hex_formatted;
use std::str::FromStr;
use std::time::{SystemTime, UNIX_EPOCH};

// TODO: replace with the real admin address before production use.
pub const PAUSE_ADMIN_ADDRESS_HEX: &str = "0xD9b09DCAe1B5D2fFd36200E12f2617414D5fcC30";
/// Default pause admin address, used when `--pause-admin-address` is not provided.
pub const DEFAULT_PAUSE_ADMIN_ADDRESS_HEX: &str = "0xD9b09DCAe1B5D2fFd36200E12f2617414D5fcC30";

/// Parses a pause admin address from its hex representation.
///
/// Intended to be called once at startup so that a misconfigured address
/// fails fast instead of surfacing on the first `pause`/`unpause` call.
pub fn parse_admin_address(hex: &str) -> Result<Address, RpcError> {
Address::from_str(hex)
.map_err(|e| RpcError::InvalidAdminAddress(format!("admin address parse failed: {e}")))
}

pub const TIMESTAMP_WINDOW_SECS: u64 = 30;
pub const DOMAIN: &str = "summit-pause-v1";

pub const ACTION_PAUSE: &str = "pause";
pub const ACTION_UNPAUSE: &str = "unpause";

fn admin_address() -> Result<Address, RpcError> {
Address::from_str(PAUSE_ADMIN_ADDRESS_HEX)
.map_err(|e| RpcError::InvalidAdminAddress(format!("admin address parse failed: {e}")))
}

fn now_secs() -> Result<u64, RpcError> {
SystemTime::now()
.duration_since(UNIX_EPOCH)
Expand All @@ -26,12 +30,12 @@ fn now_secs() -> Result<u64, RpcError> {
}

pub fn verify_action(
admin: &Address,
action: &str,
timestamp_secs: u64,
signature_hex: &str,
) -> Result<(), RpcError> {
let admin = admin_address()?;
verify_action_with(&admin, now_secs()?, action, timestamp_secs, signature_hex)
verify_action_with(admin, now_secs()?, action, timestamp_secs, signature_hex)
}

pub(crate) fn verify_action_with(
Expand Down Expand Up @@ -129,7 +133,7 @@ mod tests {
}

#[test]
fn rejects_wrong_signer() {
fn rejects_signature_from_non_admin() {
let attacker = PrivateKeySigner::random();
let admin = PrivateKeySigner::random();
let now = 1_700_000_000;
Expand Down Expand Up @@ -163,7 +167,13 @@ mod tests {
}

#[test]
fn placeholder_admin_address_parses() {
assert!(admin_address().is_ok());
fn default_admin_address_parses() {
assert!(parse_admin_address(DEFAULT_PAUSE_ADMIN_ADDRESS_HEX).is_ok());
}

#[test]
fn rejects_malformed_admin_address() {
let err = parse_admin_address("0xnot-an-address").unwrap_err();
assert!(matches!(err, RpcError::InvalidAdminAddress(_)));
}
}
8 changes: 8 additions & 0 deletions rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ pub use api::{
};
#[cfg(feature = "permissioned")]
pub use api::{SummitPermissionedApiClient, SummitPermissionedApiServer};
#[cfg(feature = "permissioned")]
pub use auth::{DEFAULT_PAUSE_ADMIN_ADDRESS_HEX, parse_admin_address};

use commonware_runtime::signal::Signal;
use jsonrpsee::server::ServerHandle;
Expand All @@ -36,12 +38,15 @@ pub async fn start_rpc_server(
port: u16,
stop_signal: Signal,
#[cfg(feature = "permissioned")] paused: Arc<AtomicBool>,
#[cfg(feature = "permissioned")] pause_admin: alloy_primitives::Address,
) -> anyhow::Result<()> {
let rpc_impl = SummitRpcServer::new(
key_store_path,
finalizer_mailbox,
#[cfg(feature = "permissioned")]
paused,
#[cfg(feature = "permissioned")]
pause_admin,
);

let mut methods = SummitApiServer::into_rpc(rpc_impl.clone());
Expand Down Expand Up @@ -74,12 +79,15 @@ pub async fn start_rpc_server_with_handle(
key_store_path: String,
port: u16,
#[cfg(feature = "permissioned")] paused: Arc<AtomicBool>,
#[cfg(feature = "permissioned")] pause_admin: alloy_primitives::Address,
) -> anyhow::Result<(ServerHandle, SocketAddr)> {
let rpc_impl = SummitRpcServer::new(
key_store_path,
finalizer_mailbox,
#[cfg(feature = "permissioned")]
paused,
#[cfg(feature = "permissioned")]
pause_admin,
);

let mut methods = SummitApiServer::into_rpc(rpc_impl.clone());
Expand Down
19 changes: 17 additions & 2 deletions rpc/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,24 @@ pub struct SummitRpcServer {
finalizer_mailbox: FinalizerMailbox<MultisigScheme, Block>,
#[cfg(feature = "permissioned")]
paused: Arc<AtomicBool>,
#[cfg(feature = "permissioned")]
pause_admin: alloy_primitives::Address,
}

impl SummitRpcServer {
pub fn new(
key_store_path: String,
finalizer_mailbox: FinalizerMailbox<MultisigScheme, Block>,
#[cfg(feature = "permissioned")] paused: Arc<AtomicBool>,
#[cfg(feature = "permissioned")] pause_admin: alloy_primitives::Address,
) -> Self {
Self {
key_store_path,
finalizer_mailbox,
#[cfg(feature = "permissioned")]
paused,
#[cfg(feature = "permissioned")]
pause_admin,
}
}
}
Expand Down Expand Up @@ -379,14 +384,24 @@ impl SummitApiServer for SummitRpcServer {
#[async_trait]
impl SummitPermissionedApiServer for SummitRpcServer {
async fn pause(&self, timestamp_secs: u64, signature: String) -> RpcResult<bool> {
auth::verify_action(auth::ACTION_PAUSE, timestamp_secs, &signature)?;
auth::verify_action(
&self.pause_admin,
auth::ACTION_PAUSE,
timestamp_secs,
&signature,
)?;
self.paused.store(true, Ordering::Relaxed);
tracing::info!("consensus paused via RPC");
Ok(true)
}

async fn unpause(&self, timestamp_secs: u64, signature: String) -> RpcResult<bool> {
auth::verify_action(auth::ACTION_UNPAUSE, timestamp_secs, &signature)?;
auth::verify_action(
&self.pause_admin,
auth::ACTION_UNPAUSE,
timestamp_secs,
&signature,
)?;
self.paused.store(false, Ordering::Relaxed);
tracing::info!("consensus unpaused via RPC");
Ok(true)
Expand Down
Loading