mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-07-16 10:49:32 +00:00
feat(sequencer): adminConfigureChannel RPC for channel roster changes
This commit is contained in:
parent
b20a39f353
commit
1dca85b41c
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -9085,6 +9085,7 @@ dependencies = [
|
|||||||
"common",
|
"common",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"futures",
|
"futures",
|
||||||
|
"hex",
|
||||||
"jsonrpsee",
|
"jsonrpsee",
|
||||||
"lee",
|
"lee",
|
||||||
"log",
|
"log",
|
||||||
@ -9105,6 +9106,7 @@ dependencies = [
|
|||||||
"hex",
|
"hex",
|
||||||
"lee",
|
"lee",
|
||||||
"lee_core",
|
"lee_core",
|
||||||
|
"serde",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -108,14 +108,17 @@ pub trait BlockPublisherTrait: Clone {
|
|||||||
/// `ChannelConfig` op. The sequencer's bedrock key must be the channel
|
/// `ChannelConfig` op. The sequencer's bedrock key must be the channel
|
||||||
/// admin (`keys[0]`); the L1 rejects non-admin signers, so this is not
|
/// admin (`keys[0]`); the L1 rejects non-admin signers, so this is not
|
||||||
/// re-validated here.
|
/// re-validated here.
|
||||||
async fn configure_channel(
|
///
|
||||||
|
/// Desugared (not `async fn`) so the returned future is provably `Send` —
|
||||||
|
/// generic callers awaiting it inside jsonrpsee handlers require that.
|
||||||
|
fn configure_channel(
|
||||||
&self,
|
&self,
|
||||||
keys: Vec<Ed25519PublicKey>,
|
keys: Vec<Ed25519PublicKey>,
|
||||||
posting_timeframe: u32,
|
posting_timeframe: u32,
|
||||||
posting_timeout: u32,
|
posting_timeout: u32,
|
||||||
configuration_threshold: u16,
|
configuration_threshold: u16,
|
||||||
withdraw_threshold: u16,
|
withdraw_threshold: u16,
|
||||||
) -> Result<()>;
|
) -> impl Future<Output = Result<()>> + Send;
|
||||||
|
|
||||||
fn channel_id(&self) -> ChannelId;
|
fn channel_id(&self) -> ChannelId;
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,7 @@ programs.workspace = true
|
|||||||
clap = { workspace = true, features = ["derive", "env"] }
|
clap = { workspace = true, features = ["derive", "env"] }
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
|
hex.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
tokio-util.workspace = true
|
tokio-util.workspace = true
|
||||||
|
|||||||
@ -13,4 +13,5 @@ lee.workspace = true
|
|||||||
lee_core.workspace = true
|
lee_core.workspace = true
|
||||||
|
|
||||||
hex.workspace = true
|
hex.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
serde_with.workspace = true
|
serde_with.workspace = true
|
||||||
|
|||||||
@ -26,3 +26,17 @@ impl FromStr for ChannelId {
|
|||||||
Ok(Self(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)]
|
||||||
|
pub struct ConfigureChannelRequest {
|
||||||
|
pub keys: Vec<String>,
|
||||||
|
pub posting_timeframe: u32,
|
||||||
|
pub posting_timeout: u32,
|
||||||
|
pub configuration_threshold: u16,
|
||||||
|
pub withdraw_threshold: u16,
|
||||||
|
}
|
||||||
|
|||||||
@ -6,8 +6,8 @@ use jsonrpsee::types::ErrorObjectOwned;
|
|||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
pub use jsonrpsee::{core::ClientError, http_client::HttpClientBuilder as SequencerClientBuilder};
|
pub use jsonrpsee::{core::ClientError, http_client::HttpClientBuilder as SequencerClientBuilder};
|
||||||
use sequencer_service_protocol::{
|
use sequencer_service_protocol::{
|
||||||
Account, AccountId, Block, BlockId, ChannelId, Commitment, HashType, LeeTransaction,
|
Account, AccountId, Block, BlockId, ChannelId, Commitment, ConfigureChannelRequest, HashType,
|
||||||
MembershipProof, Nonce, ProgramId,
|
LeeTransaction, MembershipProof, Nonce, ProgramId,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(all(not(feature = "server"), not(feature = "client")))]
|
#[cfg(all(not(feature = "server"), not(feature = "client")))]
|
||||||
@ -92,4 +92,12 @@ pub trait Rpc {
|
|||||||
async fn get_channel_id(&self) -> Result<ChannelId, ErrorObjectOwned>;
|
async fn get_channel_id(&self) -> Result<ChannelId, ErrorObjectOwned>;
|
||||||
|
|
||||||
// =============================================================================================
|
// =============================================================================================
|
||||||
|
|
||||||
|
/// Admin-only in effect: the L1 rejects the config op unless this
|
||||||
|
/// sequencer's key is the channel admin (`keys[0]` of the current roster).
|
||||||
|
#[method(name = "adminConfigureChannel")]
|
||||||
|
async fn admin_configure_channel(
|
||||||
|
&self,
|
||||||
|
request: ConfigureChannelRequest,
|
||||||
|
) -> Result<(), ErrorObjectOwned>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,11 +9,12 @@ use lee;
|
|||||||
use log::warn;
|
use log::warn;
|
||||||
use mempool::MemPoolHandle;
|
use mempool::MemPoolHandle;
|
||||||
use sequencer_core::{
|
use sequencer_core::{
|
||||||
DbError, SequencerCore, TransactionOrigin, block_publisher::BlockPublisherTrait,
|
DbError, SequencerCore, TransactionOrigin,
|
||||||
|
block_publisher::{BlockPublisherTrait, Ed25519PublicKey},
|
||||||
};
|
};
|
||||||
use sequencer_service_protocol::{
|
use sequencer_service_protocol::{
|
||||||
Account, AccountId, Block, BlockId, ChannelId, Commitment, HashType, MembershipProof, Nonce,
|
Account, AccountId, Block, BlockId, ChannelId, Commitment, ConfigureChannelRequest, HashType,
|
||||||
ProgramId,
|
MembershipProof, Nonce, ProgramId,
|
||||||
};
|
};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ impl<BC: BlockPublisherTrait> SequencerService<BC> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<BC: BlockPublisherTrait + Send + 'static> sequencer_service_rpc::RpcServer
|
impl<BC: BlockPublisherTrait + Send + Sync + 'static> sequencer_service_rpc::RpcServer
|
||||||
for SequencerService<BC>
|
for SequencerService<BC>
|
||||||
{
|
{
|
||||||
async fn send_transaction(&self, tx: LeeTransaction) -> Result<HashType, ErrorObjectOwned> {
|
async fn send_transaction(&self, tx: LeeTransaction) -> Result<HashType, ErrorObjectOwned> {
|
||||||
@ -198,8 +199,66 @@ impl<BC: BlockPublisherTrait + Send + 'static> sequencer_service_rpc::RpcServer
|
|||||||
let channel_id = self.sequencer.lock().await.block_publisher().channel_id();
|
let channel_id = self.sequencer.lock().await.block_publisher().channel_id();
|
||||||
Ok(ChannelId(*channel_id.as_ref()))
|
Ok(ChannelId(*channel_id.as_ref()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn admin_configure_channel(
|
||||||
|
&self,
|
||||||
|
request: ConfigureChannelRequest,
|
||||||
|
) -> Result<(), ErrorObjectOwned> {
|
||||||
|
let keys = request
|
||||||
|
.keys
|
||||||
|
.iter()
|
||||||
|
.map(|hex_key| parse_channel_key(hex_key))
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
let sequencer = self.sequencer.lock().await;
|
||||||
|
sequencer
|
||||||
|
.configure_channel(
|
||||||
|
keys,
|
||||||
|
request.posting_timeframe,
|
||||||
|
request.posting_timeout,
|
||||||
|
request.configuration_threshold,
|
||||||
|
request.withdraw_threshold,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|err| {
|
||||||
|
ErrorObjectOwned::owned(
|
||||||
|
ErrorCode::InternalError.code(),
|
||||||
|
format!("{err:#}"),
|
||||||
|
None::<()>,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn internal_error(err: &DbError) -> ErrorObjectOwned {
|
fn internal_error(err: &DbError) -> ErrorObjectOwned {
|
||||||
ErrorObjectOwned::owned(ErrorCode::InternalError.code(), err.to_string(), None::<()>)
|
ErrorObjectOwned::owned(ErrorCode::InternalError.code(), err.to_string(), None::<()>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses one hex-encoded 32-byte Ed25519 public key from an RPC request.
|
||||||
|
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];
|
||||||
|
hex::decode_to_slice(hex_key, &mut bytes)
|
||||||
|
.map_err(|err| invalid(format!("Invalid hex-encoded key: {err}")))?;
|
||||||
|
Ed25519PublicKey::from_bytes(&bytes)
|
||||||
|
.map_err(|err| invalid(format!("Invalid Ed25519 public key: {err}")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use sequencer_core::block_publisher::Ed25519Key;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_channel_key_roundtrips_and_rejects_garbage() {
|
||||||
|
let key = Ed25519Key::from_bytes(&[7; 32]).public_key();
|
||||||
|
let parsed = parse_channel_key(&hex::encode(key.to_bytes())).unwrap();
|
||||||
|
assert_eq!(parsed.to_bytes(), key.to_bytes());
|
||||||
|
|
||||||
|
assert!(parse_channel_key("not-hex").is_err());
|
||||||
|
assert!(parse_channel_key("abcd").is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user