mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-08-03 03:23:33 +00:00
fix(sequencer): validate adminConfigureChannel requests, per Copilot review
This commit is contained in:
parent
bbb1f999df
commit
7c3043119b
@ -30,8 +30,8 @@ impl FromStr for ChannelId {
|
|||||||
/// Request for `adminConfigureChannel`: replaces the channel's accredited key
|
/// Request for `adminConfigureChannel`: replaces the channel's accredited key
|
||||||
/// set and rotation parameters.
|
/// set and rotation parameters.
|
||||||
///
|
///
|
||||||
/// `keys` are hex-encoded 32-byte Ed25519 public keys; `keys[0]` must be this
|
/// - `keys` are hex-encoded 32-byte Ed25519 public keys
|
||||||
/// sequencer's (admin) key.
|
/// - `keys[0]` must be this sequencer's (admin) key.
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct ConfigureChannelRequest {
|
pub struct ConfigureChannelRequest {
|
||||||
pub keys: Vec<String>,
|
pub keys: Vec<String>,
|
||||||
@ -40,3 +40,81 @@ pub struct ConfigureChannelRequest {
|
|||||||
pub configuration_threshold: u16,
|
pub configuration_threshold: u16,
|
||||||
pub withdraw_threshold: u16,
|
pub withdraw_threshold: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ConfigureChannelRequest {
|
||||||
|
/// Structural sanity checks for the request.
|
||||||
|
///
|
||||||
|
/// The L1 validates this too, but its async so we don't immediately
|
||||||
|
/// know about them when we submit. Checking this here instead gives
|
||||||
|
/// immediate feedback to the caller.
|
||||||
|
///
|
||||||
|
/// We don't need a particular error type here, it's going to be logged only.
|
||||||
|
pub fn validate(&self) -> Result<(), String> {
|
||||||
|
let key_count = self.keys.len();
|
||||||
|
if key_count == 0 {
|
||||||
|
return Err("Channel key list must not be empty".to_owned());
|
||||||
|
}
|
||||||
|
for (name, threshold) in [
|
||||||
|
("configuration_threshold", self.configuration_threshold),
|
||||||
|
("withdraw_threshold", self.withdraw_threshold),
|
||||||
|
] {
|
||||||
|
if threshold == 0 || usize::from(threshold) > key_count {
|
||||||
|
return Err(format!(
|
||||||
|
"{name} must be between 1 and the key count ({key_count}), got {threshold}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn configure_channel_request_validate_rejects_static_garbage() {
|
||||||
|
// `validate` is structural: key contents are not parsed here.
|
||||||
|
let base = ConfigureChannelRequest {
|
||||||
|
keys: vec!["unparsed".to_owned(), "unparsed".to_owned()],
|
||||||
|
posting_timeframe: 20,
|
||||||
|
posting_timeout: 30,
|
||||||
|
configuration_threshold: 1,
|
||||||
|
withdraw_threshold: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(base.validate().is_ok());
|
||||||
|
assert!(
|
||||||
|
ConfigureChannelRequest {
|
||||||
|
keys: vec![],
|
||||||
|
..base
|
||||||
|
}
|
||||||
|
.validate()
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
ConfigureChannelRequest {
|
||||||
|
configuration_threshold: 0,
|
||||||
|
..base.clone()
|
||||||
|
}
|
||||||
|
.validate()
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
ConfigureChannelRequest {
|
||||||
|
configuration_threshold: 3,
|
||||||
|
..base.clone()
|
||||||
|
}
|
||||||
|
.validate()
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
ConfigureChannelRequest {
|
||||||
|
withdraw_threshold: 3,
|
||||||
|
..base
|
||||||
|
}
|
||||||
|
.validate()
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -204,6 +204,7 @@ impl<BC: BlockPublisherTrait + Send + Sync + 'static> sequencer_service_rpc::Rpc
|
|||||||
&self,
|
&self,
|
||||||
request: ConfigureChannelRequest,
|
request: ConfigureChannelRequest,
|
||||||
) -> Result<(), ErrorObjectOwned> {
|
) -> Result<(), ErrorObjectOwned> {
|
||||||
|
request.validate().map_err(invalid_params)?;
|
||||||
let keys = request
|
let keys = request
|
||||||
.keys
|
.keys
|
||||||
.iter()
|
.iter()
|
||||||
@ -234,16 +235,17 @@ fn internal_error(err: &DbError) -> ErrorObjectOwned {
|
|||||||
ErrorObjectOwned::owned(ErrorCode::InternalError.code(), err.to_string(), None::<()>)
|
ErrorObjectOwned::owned(ErrorCode::InternalError.code(), err.to_string(), None::<()>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn invalid_params(detail: String) -> ErrorObjectOwned {
|
||||||
|
ErrorObjectOwned::owned(ErrorCode::InvalidParams.code(), detail, None::<()>)
|
||||||
|
}
|
||||||
|
|
||||||
/// Parses one hex-encoded 32-byte Ed25519 public key from an RPC request.
|
/// Parses one hex-encoded 32-byte Ed25519 public key from an RPC request.
|
||||||
fn parse_channel_key(hex_key: &str) -> Result<Ed25519PublicKey, ErrorObjectOwned> {
|
fn parse_channel_key(hex_key: &str) -> Result<Ed25519PublicKey, ErrorObjectOwned> {
|
||||||
let invalid = |detail: String| {
|
|
||||||
ErrorObjectOwned::owned(ErrorCode::InvalidParams.code(), detail, None::<()>)
|
|
||||||
};
|
|
||||||
let mut bytes = [0_u8; 32];
|
let mut bytes = [0_u8; 32];
|
||||||
hex::decode_to_slice(hex_key, &mut bytes)
|
hex::decode_to_slice(hex_key, &mut bytes)
|
||||||
.map_err(|err| invalid(format!("Invalid hex-encoded key: {err}")))?;
|
.map_err(|err| invalid_params(format!("Invalid hex-encoded key: {err}")))?;
|
||||||
Ed25519PublicKey::from_bytes(&bytes)
|
Ed25519PublicKey::from_bytes(&bytes)
|
||||||
.map_err(|err| invalid(format!("Invalid Ed25519 public key: {err}")))
|
.map_err(|err| invalid_params(format!("Invalid Ed25519 public key: {err}")))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user