mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-07-23 08:10:20 +00:00
210 lines
6.5 KiB
Rust
210 lines
6.5 KiB
Rust
//! Reexports of types used by sequencer rpc specification.
|
|
|
|
use std::{fmt::Display, str::FromStr};
|
|
|
|
pub use common::{HashType, block::Block, transaction::LeeTransaction};
|
|
pub use lee::{Account, AccountId, ProgramId};
|
|
pub use lee_core::{BlockId, Commitment, CommitmentSetDigest, MembershipProof, account::Nonce};
|
|
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, SerializeDisplay, DeserializeFromStr)]
|
|
pub struct ChannelId(pub [u8; 32]);
|
|
|
|
impl Display for ChannelId {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
let hex_string = hex::encode(self.0);
|
|
write!(f, "{hex_string}")
|
|
}
|
|
}
|
|
|
|
impl FromStr for ChannelId {
|
|
type Err = hex::FromHexError;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
let mut bytes = [0_u8; 32];
|
|
hex::decode_to_slice(s, &mut bytes)?;
|
|
Ok(Self(bytes))
|
|
}
|
|
}
|
|
|
|
/// Request for `adminConfigureChannel`: replaces the channel's accredited key
|
|
/// set and rotation parameters.
|
|
///
|
|
/// - `keys` are hex-encoded 32-byte Ed25519 public keys
|
|
/// - `keys[0]` must be this sequencer's (admin) key.
|
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
#[serde(try_from = "UncheckedConfigureChannelRequest")]
|
|
pub struct ConfigureChannelRequest {
|
|
pub keys: Vec<String>,
|
|
pub posting_timeframe: u32,
|
|
pub posting_timeout: u32,
|
|
pub configuration_threshold: u16,
|
|
pub withdraw_threshold: u16,
|
|
}
|
|
|
|
/// Wire-shape mirror of [`ConfigureChannelRequest`]; deserialization goes
|
|
/// through it so [`ConfigureChannelRequest::validate`] runs on every decode.
|
|
#[derive(serde::Deserialize)]
|
|
struct UncheckedConfigureChannelRequest {
|
|
keys: Vec<String>,
|
|
posting_timeframe: u32,
|
|
posting_timeout: u32,
|
|
configuration_threshold: u16,
|
|
withdraw_threshold: u16,
|
|
}
|
|
|
|
impl TryFrom<UncheckedConfigureChannelRequest> for ConfigureChannelRequest {
|
|
type Error = String;
|
|
|
|
fn try_from(unchecked: UncheckedConfigureChannelRequest) -> Result<Self, Self::Error> {
|
|
let UncheckedConfigureChannelRequest {
|
|
keys,
|
|
posting_timeframe,
|
|
posting_timeout,
|
|
configuration_threshold,
|
|
withdraw_threshold,
|
|
} = unchecked;
|
|
let request = Self {
|
|
keys,
|
|
posting_timeframe,
|
|
posting_timeout,
|
|
configuration_threshold,
|
|
withdraw_threshold,
|
|
};
|
|
request.validate()?;
|
|
Ok(request)
|
|
}
|
|
}
|
|
|
|
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}"
|
|
));
|
|
}
|
|
}
|
|
if self.posting_timeframe == 0 || self.posting_timeout == 0 {
|
|
return Err(format!(
|
|
"posting_timeframe and posting_timeout must be nonzero, got {} and {}",
|
|
self.posting_timeframe, self.posting_timeout
|
|
));
|
|
}
|
|
if self.posting_timeout < self.posting_timeframe {
|
|
return Err(format!(
|
|
"posting_timeout ({}) must not be shorter than posting_timeframe ({})",
|
|
self.posting_timeout, self.posting_timeframe
|
|
));
|
|
}
|
|
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 {
|
|
posting_timeframe: 0,
|
|
..base.clone()
|
|
}
|
|
.validate()
|
|
.is_err()
|
|
);
|
|
assert!(
|
|
ConfigureChannelRequest {
|
|
posting_timeout: 0,
|
|
..base.clone()
|
|
}
|
|
.validate()
|
|
.is_err()
|
|
);
|
|
assert!(
|
|
ConfigureChannelRequest {
|
|
posting_timeout: 10,
|
|
..base.clone()
|
|
}
|
|
.validate()
|
|
.is_err()
|
|
);
|
|
assert!(
|
|
ConfigureChannelRequest {
|
|
withdraw_threshold: 3,
|
|
..base
|
|
}
|
|
.validate()
|
|
.is_err()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn configure_channel_request_validates_on_deserialization() {
|
|
let valid = serde_json::json!({
|
|
"keys": ["unparsed"],
|
|
"posting_timeframe": 20,
|
|
"posting_timeout": 30,
|
|
"configuration_threshold": 1,
|
|
"withdraw_threshold": 1,
|
|
});
|
|
assert!(serde_json::from_value::<ConfigureChannelRequest>(valid.clone()).is_ok());
|
|
|
|
let mut invalid = valid;
|
|
invalid["posting_timeout"] = serde_json::json!(10);
|
|
let err = serde_json::from_value::<ConfigureChannelRequest>(invalid)
|
|
.expect_err("timeout below timeframe must fail to deserialize");
|
|
assert!(err.to_string().contains("posting_timeout"));
|
|
}
|
|
}
|