use std::sync::Arc;
use canton_core::auth::{self, Intercepted};
use canton_core::telemetry::{self, TRANSPORT_GRPC};
use canton_core::{Config, Error, Result};
use canton_proto::com::daml::ledger::api::v2 as lapi;
use canton_proto::com::daml::ledger::api::v2::admin as pb;
use lapi::package_service_client::PackageServiceClient;
use pb::party_management_service_client::PartyManagementServiceClient;
use pb::user_management_service_client::UserManagementServiceClient;
use tonic::transport::Channel;
#[derive(Clone, Debug)]
pub struct AdminClient {
channel: Channel,
config: Arc<Config>,
}
macro_rules! service {
($self:ident, $ctor:expr) => {
$ctor($self.intercepted().await?)
.max_decoding_message_size($self.config.max_decoding_message_size())
};
}
impl AdminClient {
pub fn connect_lazy(config: Config) -> Result<Self> {
Ok(Self {
channel: config.connect_channel()?,
config: Arc::new(config),
})
}
async fn intercepted(&self) -> Result<Intercepted> {
auth::intercepted(&self.channel, self.config.auth()).await
}
async fn with_retry<T, F, Fut>(&self, op: F) -> Result<T>
where
F: FnMut() -> Fut,
Fut: std::future::Future<Output = Result<T>>,
{
canton_core::retry::run_with_retry(self.config.retry(), op).await
}
pub async fn participant_id(&self) -> Result<String> {
telemetry::instrument("participant_id", TRANSPORT_GRPC, async {
self.with_retry(|| async {
let mut client = service!(self, PartyManagementServiceClient::new);
Ok(client
.get_participant_id(pb::GetParticipantIdRequest {})
.await?
.into_inner()
.participant_id)
})
.await
})
.await
}
pub async fn allocate_party(&self, party_id_hint: Option<&str>) -> Result<pb::PartyDetails> {
let party_id_hint = party_id_hint.unwrap_or_default().to_string();
telemetry::instrument("allocate_party", TRANSPORT_GRPC, async move {
let mut client = service!(self, PartyManagementServiceClient::new);
let response = client
.allocate_party(pb::AllocatePartyRequest {
party_id_hint,
..Default::default()
})
.await?
.into_inner();
response.party_details.ok_or_else(|| {
Error::UnexpectedResponse("allocate_party returned no party details".to_string())
})
})
.await
}
pub async fn list_known_parties_page(
&self,
page_size: i32,
page_token: Option<String>,
) -> Result<(Vec<pb::PartyDetails>, Option<String>)> {
telemetry::instrument("list_known_parties_page", TRANSPORT_GRPC, async move {
self.with_retry(|| {
let page_token = page_token.clone().unwrap_or_default();
async move {
let mut client = service!(self, PartyManagementServiceClient::new);
let response = client
.list_known_parties(pb::ListKnownPartiesRequest {
page_token,
page_size,
..Default::default()
})
.await?
.into_inner();
let next =
(!response.next_page_token.is_empty()).then_some(response.next_page_token);
Ok((response.party_details, next))
}
})
.await
})
.await
}
pub async fn list_known_parties(&self) -> Result<Vec<pb::PartyDetails>> {
telemetry::instrument("list_known_parties", TRANSPORT_GRPC, async {
let mut all = Vec::new();
let mut page_token = String::new();
loop {
let sent = page_token.clone();
let (parties, next) = self
.with_retry(|| {
let page_token = sent.clone();
async move {
let mut client = service!(self, PartyManagementServiceClient::new);
let response = client
.list_known_parties(pb::ListKnownPartiesRequest {
page_token,
..Default::default()
})
.await?
.into_inner();
Ok((response.party_details, response.next_page_token))
}
})
.await?;
all.extend(parties);
if next.is_empty() {
break;
}
if next == sent {
return Err(Error::UnexpectedResponse(format!(
"the participant repeated the same page token after {} parties; \
the list is incomplete and cannot be continued",
all.len()
)));
}
page_token = next;
}
Ok(all)
})
.await
}
pub async fn get_parties(&self, parties: Vec<String>) -> Result<Vec<pb::PartyDetails>> {
telemetry::instrument("get_parties", TRANSPORT_GRPC, async move {
self.with_retry(|| {
let parties = parties.clone();
async move {
let mut client = service!(self, PartyManagementServiceClient::new);
Ok(client
.get_parties(pb::GetPartiesRequest {
parties,
..Default::default()
})
.await?
.into_inner()
.party_details)
}
})
.await
})
.await
}
pub async fn current_user(&self) -> Result<pb::User> {
self.get_user("").await
}
pub async fn get_user(&self, user_id: &str) -> Result<pb::User> {
let user_id = user_id.to_string();
telemetry::instrument("get_user", TRANSPORT_GRPC, async move {
self.with_retry(|| {
let user_id = user_id.clone();
async move {
let mut client = service!(self, UserManagementServiceClient::new);
let response = client
.get_user(pb::GetUserRequest {
user_id,
..Default::default()
})
.await?
.into_inner();
response.user.ok_or_else(|| {
Error::UnexpectedResponse("get_user returned no user".to_string())
})
}
})
.await
})
.await
}
pub async fn current_user_rights(&self) -> Result<Vec<pb::Right>> {
self.list_user_rights("").await
}
pub async fn list_user_rights(&self, user_id: &str) -> Result<Vec<pb::Right>> {
let user_id = user_id.to_string();
telemetry::instrument("list_user_rights", TRANSPORT_GRPC, async move {
self.with_retry(|| {
let user_id = user_id.clone();
async move {
let mut client = service!(self, UserManagementServiceClient::new);
Ok(client
.list_user_rights(pb::ListUserRightsRequest {
user_id,
..Default::default()
})
.await?
.into_inner()
.rights)
}
})
.await
})
.await
}
pub async fn list_packages(&self) -> Result<Vec<String>> {
telemetry::instrument("list_packages", TRANSPORT_GRPC, async {
self.with_retry(|| async {
let mut client = service!(self, PackageServiceClient::new);
Ok(client
.list_packages(lapi::ListPackagesRequest {})
.await?
.into_inner()
.package_ids)
})
.await
})
.await
}
pub async fn get_package_status(&self, package_id: &str) -> Result<lapi::PackageStatus> {
let package_id = package_id.to_string();
telemetry::instrument("get_package_status", TRANSPORT_GRPC, async move {
self.with_retry(|| {
let package_id = package_id.clone();
async move {
let mut client = service!(self, PackageServiceClient::new);
let response = client
.get_package_status(lapi::GetPackageStatusRequest { package_id })
.await?
.into_inner();
Ok(response.package_status())
}
})
.await
})
.await
}
pub async fn acting_parties(&self) -> Result<Vec<String>> {
let rights = self.current_user_rights().await?;
Ok(rights
.into_iter()
.filter_map(|right| match right.kind {
Some(pb::right::Kind::CanActAs(can)) => Some(can.party),
_ => None,
})
.collect())
}
}