use crate::ecash::models::EcashSignerStatusResponse;
use crate::models::tendermint_types::{BlockHeader, BlockId};
use crate::models::{ChainStatus, SignerInformationResponse};
use crate::signable::SignedMessage;
use nym_coconut_dkg_common::types::EpochId;
use nym_crypto::asymmetric::ed25519::PublicKey;
use nym_ecash_signer_check_types::helper_traits::{
ChainResponse, LegacyChainResponse, LegacySignerResponse, SignerResponse, TimestampedResponse,
Verifiable,
};
use nym_ecash_signer_check_types::status::SignerResult;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use time::OffsetDateTime;
use utoipa::ToSchema;
pub type ChainBlocksStatusResponse = SignedMessage<ChainBlocksStatusResponseBody>;
pub type SignersStatusResponse = SignedMessage<SignersStatusResponseBody>;
pub type DetailedSignersStatusResponse = SignedMessage<DetailedSignersStatusResponseBody>;
#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct SignersStatusResponseBody {
#[serde(with = "time::serde::rfc3339")]
#[schema(value_type = String)]
pub as_at: OffsetDateTime,
pub overview: SignersStatusOverview,
pub results: Vec<MinimalSignerResult>,
}
pub type TypedSignerResult = SignerResult<
SignerInformationResponse,
EcashSignerStatusResponse,
ChainStatusResponse,
ChainBlocksStatusResponse,
>;
#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct MinimalSignerResult {
pub announce_address: String,
pub owner_address: String,
pub node_index: u64,
pub public_key: String,
pub local_chain_working: bool,
pub credential_issuance_available: bool,
}
impl From<&TypedSignerResult> for MinimalSignerResult {
fn from(result: &TypedSignerResult) -> MinimalSignerResult {
MinimalSignerResult {
announce_address: result.information.announce_address.clone(),
owner_address: result.information.owner_address.clone(),
node_index: result.information.node_index,
public_key: result.information.public_key.clone(),
local_chain_working: result.chain_available(),
credential_issuance_available: result.signing_available(),
}
}
}
#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct DetailedSignersStatusResponseBody {
#[serde(with = "time::serde::rfc3339")]
#[schema(value_type = String)]
pub as_at: OffsetDateTime,
pub overview: SignersStatusOverview,
pub details: Vec<TypedSignerResult>,
}
#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct SignersStatusOverview {
#[schema(value_type = Option<u64>)]
pub epoch_id: Option<EpochId>,
pub signing_threshold: Option<u64>,
pub threshold_available: Option<bool>,
pub total_signers: usize,
pub unreachable_signers: usize,
pub malformed_signers: usize,
pub unknown_local_chain_status: usize,
pub working_local_chain: usize,
pub provably_stalled_local_chain: usize,
pub unprovably_stalled_local_chain: usize,
pub unknown_credential_issuance_status: usize,
pub working_credential_issuance: usize,
pub provably_unavailable_credential_issuance: usize,
pub unprovably_unavailable_credential_issuance: usize,
}
impl SignersStatusOverview {
pub fn new(results: &[TypedSignerResult], signing_threshold: Option<u64>) -> Self {
let epoch_id = results.first().map(|r| r.dkg_epoch_id);
let mut unreachable_signers = 0;
let mut malformed_signers = 0;
let mut unknown_local_chain_status = 0;
let mut working_local_chain = 0;
let mut provably_stalled_local_chain = 0;
let mut unprovably_stalled_local_chain = 0;
let mut unknown_credential_issuance_status = 0;
let mut working_credential_issuance = 0;
let mut provably_unavailable_credential_issuance = 0;
let mut unprovably_unavailable_credential_issuance = 0;
for result in results {
if result.signer_unreachable() {
unreachable_signers += 1;
}
if result.malformed_details() {
malformed_signers += 1;
}
if result.unknown_chain_status() {
unknown_local_chain_status += 1;
}
if result.chain_available() {
working_local_chain += 1;
}
if result.chain_provably_stalled() {
provably_stalled_local_chain += 1;
}
if result.chain_unprovably_stalled() {
unprovably_stalled_local_chain += 1;
}
if result.unknown_signing_status() {
unknown_credential_issuance_status += 1;
}
if result.signing_available() {
working_credential_issuance += 1;
}
if result.signing_provably_unavailable() {
provably_unavailable_credential_issuance += 1;
}
if result.signing_unprovably_unavailable() {
unprovably_unavailable_credential_issuance += 1;
}
}
SignersStatusOverview {
epoch_id,
signing_threshold,
threshold_available: signing_threshold.map(|threshold| {
(working_local_chain as u64) >= threshold
&& (working_credential_issuance as u64) >= threshold
}),
total_signers: results.len(),
unreachable_signers,
malformed_signers,
unknown_local_chain_status,
working_local_chain,
provably_stalled_local_chain,
unprovably_stalled_local_chain,
unknown_credential_issuance_status,
working_credential_issuance,
provably_unavailable_credential_issuance,
unprovably_unavailable_credential_issuance,
}
}
}
#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ChainBlocksStatusResponseBody {
#[serde(with = "time::serde::rfc3339")]
#[schema(value_type = String)]
pub current_time: OffsetDateTime,
pub latest_cached_block: Option<DetailedChainStatus>,
pub chain_status: ChainStatus,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct ChainStatusResponse {
pub connected_nyxd: String,
pub status: DetailedChainStatus,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct DetailedChainStatus {
pub abci: crate::models::tendermint_types::AbciInfo,
pub latest_block: BlockInfo,
}
impl DetailedChainStatus {
pub fn stall_status(&self, now: OffsetDateTime, threshold: Duration) -> ChainStatus {
let block_time: OffsetDateTime = self.latest_block.block.header.time.into();
let diff = now - block_time;
if diff > threshold {
ChainStatus::Stalled {
approximate_amount: diff.unsigned_abs(),
}
} else {
ChainStatus::Synced
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct BlockInfo {
pub block_id: BlockId,
pub block: FullBlockInfo,
}
impl From<tendermint_rpc::endpoint::block::Response> for BlockInfo {
fn from(value: tendermint_rpc::endpoint::block::Response) -> Self {
BlockInfo {
block_id: value.block_id.into(),
block: FullBlockInfo {
header: value.block.header.into(),
},
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct FullBlockInfo {
pub header: BlockHeader,
}
pub mod tendermint_types {
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tendermint::abci::response::Info;
use tendermint::block::header::Version;
use tendermint::{block, Hash};
use utoipa::ToSchema;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct AbciInfo {
pub data: String,
pub version: String,
pub app_version: u64,
pub last_block_height: u64,
pub last_block_app_hash: String,
}
impl From<Info> for AbciInfo {
fn from(value: Info) -> Self {
AbciInfo {
data: value.data,
version: value.version,
app_version: value.app_version,
last_block_height: value.last_block_height.value(),
last_block_app_hash: value.last_block_app_hash.to_string(),
}
}
}
#[derive(
Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, ToSchema,
)]
pub struct HeaderVersion {
pub block: u64,
pub app: u64,
}
impl From<tendermint::block::header::Version> for HeaderVersion {
fn from(value: Version) -> Self {
HeaderVersion {
block: value.block,
app: value.app,
}
}
}
#[derive(
Serialize,
Deserialize,
Copy,
Clone,
Debug,
Default,
Hash,
Eq,
PartialEq,
PartialOrd,
Ord,
JsonSchema,
ToSchema,
)]
pub struct BlockId {
#[schemars(with = "String")]
#[schema(value_type = String)]
pub hash: Hash,
pub part_set_header: PartSetHeader,
}
impl From<block::Id> for BlockId {
fn from(value: block::Id) -> Self {
BlockId {
hash: value.hash,
part_set_header: value.part_set_header.into(),
}
}
}
#[derive(
Clone,
Copy,
Debug,
Default,
Hash,
Eq,
PartialEq,
PartialOrd,
Ord,
Deserialize,
Serialize,
JsonSchema,
ToSchema,
)]
#[non_exhaustive]
pub struct PartSetHeader {
pub total: u32,
#[schemars(with = "String")]
#[schema(value_type = String)]
pub hash: Hash,
}
impl From<tendermint::block::parts::Header> for PartSetHeader {
fn from(value: block::parts::Header) -> Self {
PartSetHeader {
total: value.total,
hash: value.hash,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct BlockHeader {
pub version: HeaderVersion,
pub chain_id: String,
pub height: u64,
#[schemars(with = "String")]
#[schema(value_type = String)]
pub time: tendermint::Time,
pub last_block_id: Option<BlockId>,
#[schemars(with = "Option<String>")]
#[schema(value_type = Option<String>)]
pub last_commit_hash: Option<Hash>,
#[schemars(with = "Option<String>")]
#[schema(value_type = Option<String>)]
pub data_hash: Option<Hash>,
#[schemars(with = "String")]
#[schema(value_type = String)]
pub validators_hash: Hash,
#[schemars(with = "String")]
#[schema(value_type = String)]
pub next_validators_hash: Hash,
#[schemars(with = "String")]
#[schema(value_type = String)]
pub consensus_hash: Hash,
#[schemars(with = "String")]
#[schema(value_type = String)]
pub app_hash: Hash,
#[schemars(with = "Option<String>")]
#[schema(value_type = Option<String>)]
pub last_results_hash: Option<Hash>,
#[schemars(with = "Option<String>")]
#[schema(value_type = Option<String>)]
pub evidence_hash: Option<Hash>,
#[serde(with = "nym_serde_helpers::hex")]
#[schemars(with = "String")]
#[schema(value_type = String)]
pub proposer_address: Vec<u8>,
}
impl From<block::Header> for BlockHeader {
fn from(value: block::Header) -> Self {
BlockHeader {
version: value.version.into(),
chain_id: value.chain_id.to_string(),
height: value.height.value(),
time: value.time,
last_block_id: value.last_block_id.map(Into::into),
last_commit_hash: value.last_commit_hash,
data_hash: value.data_hash,
validators_hash: value.validators_hash,
next_validators_hash: value.next_validators_hash,
consensus_hash: value.consensus_hash,
app_hash: Hash::try_from(value.app_hash.as_bytes().to_vec()).unwrap_or_default(),
last_results_hash: value.last_results_hash,
evidence_hash: value.evidence_hash,
proposer_address: value.proposer_address.as_bytes().to_vec(),
}
}
}
}
impl LegacyChainResponse for ChainStatusResponse {
fn chain_synced(&self, now: OffsetDateTime, stall_threshold: Duration) -> bool {
self.status.stall_status(now, stall_threshold).is_synced()
}
}
impl Verifiable for ChainBlocksStatusResponse {
fn verify_signature(&self, pub_key: &PublicKey) -> bool {
self.verify_signature(pub_key)
}
}
impl TimestampedResponse for ChainBlocksStatusResponse {
fn timestamp(&self) -> OffsetDateTime {
self.body.current_time
}
}
impl ChainResponse for ChainBlocksStatusResponse {
fn chain_synced(&self) -> bool {
self.body.chain_status.is_synced()
}
}
impl LegacySignerResponse for SignerInformationResponse {
fn signer_identity(&self) -> &str {
&self.identity
}
fn signer_verification_key(&self) -> &Option<String> {
&self.verification_key
}
}
impl Verifiable for EcashSignerStatusResponse {
fn verify_signature(&self, pub_key: &PublicKey) -> bool {
self.verify_signature(pub_key)
}
}
impl TimestampedResponse for EcashSignerStatusResponse {
fn timestamp(&self) -> OffsetDateTime {
self.body.current_time
}
}
impl SignerResponse for EcashSignerStatusResponse {
fn has_signing_keys(&self) -> bool {
self.body.has_signing_keys
}
fn signer_disabled(&self) -> bool {
self.body.signer_disabled
}
fn is_ecash_signer(&self) -> bool {
self.body.is_ecash_signer
}
fn dkg_ecash_epoch_id(&self) -> EpochId {
self.body.dkg_ecash_epoch_id
}
}