use serde::{Deserialize, Serialize};
use crate::types::{Amount, BlockSummary, HashHex, PubkeyHex, SignatureHex, ValidatorSummary};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetBlockchainStateRequest;
impl GetBlockchainStateRequest {
pub const METHOD: &'static str = "get_blockchain_state";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetBlockchainStateResponse {
pub height: u64,
pub tip_hash: HashHex,
pub synced: bool,
pub sync_progress: f32,
pub finalized_epoch: u64,
pub sealed_height: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetNetworkInfoRequest;
impl GetNetworkInfoRequest {
pub const METHOD: &'static str = "get_network_info";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetNetworkInfoResponse {
pub network_id: String,
pub genesis_challenge: HashHex,
pub chain_age_seconds: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthzRequest;
impl HealthzRequest {
pub const METHOD: &'static str = "healthz";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthzResponse {
pub ok: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetBlockRequest {
pub hash: HashHex,
}
impl GetBlockRequest {
pub const METHOD: &'static str = "get_block";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetBlockResponse {
pub block: Option<BlockFull>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetBlockByHeightRequest {
pub height: u64,
}
impl GetBlockByHeightRequest {
pub const METHOD: &'static str = "get_block_by_height";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetBlockByHeightResponse {
pub block: Option<BlockFull>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetBlockRecordsRequest {
pub start_height: u64,
pub count: u32,
}
impl GetBlockRecordsRequest {
pub const METHOD: &'static str = "get_block_records";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetBlockRecordsResponse {
pub records: Vec<BlockSummary>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlockFull {
pub header: BlockHeaderWire,
pub body_hex: String,
pub canonical: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlockHeaderWire {
pub height: u64,
pub hash: HashHex,
pub parent_hash: HashHex,
pub timestamp: u64,
pub proposer: PubkeyHex,
pub state_root: HashHex,
pub receipts_root: HashHex,
pub weight: u64,
pub total_iters: u128,
pub signature: SignatureHex,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetCoinRecordRequest {
pub coin_id: HashHex,
}
impl GetCoinRecordRequest {
pub const METHOD: &'static str = "get_coin_record";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetCoinRecordResponse {
pub record: Option<CoinRecordWire>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetCoinRecordsByHintRequest {
pub hint: HashHex,
pub include_spent_coins: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub start_height: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub end_height: Option<u64>,
}
impl GetCoinRecordsByHintRequest {
pub const METHOD: &'static str = "get_coin_records_by_hint";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetCoinRecordsByHintResponse {
pub records: Vec<CoinRecordWire>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetCoinRecordsByPuzzleHashRequest {
pub puzzle_hash: HashHex,
pub include_spent_coins: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub start_height: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub end_height: Option<u64>,
}
impl GetCoinRecordsByPuzzleHashRequest {
pub const METHOD: &'static str = "get_coin_records_by_puzzle_hash";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetCoinRecordsByPuzzleHashResponse {
pub records: Vec<CoinRecordWire>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CoinRecordWire {
pub coin_id: HashHex,
pub parent_coin_info: HashHex,
pub puzzle_hash: HashHex,
pub amount: Amount,
pub confirmed_block_height: u64,
pub spent_block_height: u64,
pub coinbase: bool,
pub timestamp: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetMempoolRequest;
impl GetMempoolRequest {
pub const METHOD: &'static str = "get_mempool";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetMempoolResponse {
pub total_cost: u64,
pub total_fees: Amount,
pub items: Vec<MempoolItem>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MempoolItem {
pub spend_bundle_name: HashHex,
pub cost: u64,
pub fee: Amount,
pub is_cpfp: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PushTxRequest {
pub spend_bundle_hex: String,
}
impl PushTxRequest {
pub const METHOD: &'static str = "push_tx";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PushTxResponse {
pub status: PushTxStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<String>,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum PushTxStatus {
Success,
Rejected,
AlreadyExists,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetConnectionsRequest;
impl GetConnectionsRequest {
pub const METHOD: &'static str = "get_connections";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetConnectionsResponse {
pub peers: Vec<PeerInfoWire>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PeerInfoWire {
pub peer_id: HashHex,
pub remote_addr: String,
pub node_type: String,
pub connected_since: u64,
pub penalty: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BanPeerRequest {
pub peer_id: HashHex,
pub reason: String,
pub duration_secs: u64,
}
impl BanPeerRequest {
pub const METHOD: &'static str = "ban_peer";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BanPeerResponse {
pub banned: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubmitPartialCheckpointSignatureRequest {
pub epoch: u64,
pub validator_index: u32,
pub partial_sig: SignatureHex,
}
impl SubmitPartialCheckpointSignatureRequest {
pub const METHOD: &'static str = "submit_partial_checkpoint_signature";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubmitPartialCheckpointSignatureResponse {
pub accepted: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetCheckpointPoolRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub epoch: Option<u64>,
}
impl GetCheckpointPoolRequest {
pub const METHOD: &'static str = "get_checkpoint_pool";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetCheckpointPoolResponse {
pub epochs: Vec<CheckpointEpochStatus>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CheckpointEpochStatus {
pub epoch: u64,
pub partials_count: u32,
pub threshold: u32,
pub aggregated: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetValidatorRequest {
pub pubkey: PubkeyHex,
}
impl GetValidatorRequest {
pub const METHOD: &'static str = "get_validator";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetValidatorResponse {
pub validator: Option<ValidatorSummary>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetActiveValidatorsRequest {
pub limit: u32,
pub offset: u32,
}
impl GetActiveValidatorsRequest {
pub const METHOD: &'static str = "get_active_validators";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetActiveValidatorsResponse {
pub validators: Vec<ValidatorSummary>,
pub total: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetCurrentProposerRequest {
pub height: u64,
}
impl GetCurrentProposerRequest {
pub const METHOD: &'static str = "get_current_proposer";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetCurrentProposerResponse {
pub proposer: Option<ValidatorSummary>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StopNodeRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}
impl StopNodeRequest {
pub const METHOD: &'static str = "stop_node";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StopNodeResponse {
pub accepted: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetRecoveryStatusRequest;
impl GetRecoveryStatusRequest {
pub const METHOD: &'static str = "get_recovery_status";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetRecoveryStatusResponse {
pub mode: RecoveryMode,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_anomaly: Option<String>,
pub attempts: u32,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum RecoveryMode {
Running,
Recovering,
LoopBreakerOpen,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetVersionRequest;
impl GetVersionRequest {
pub const METHOD: &'static str = "get_version";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetVersionResponse {
pub version: String,
pub build_commit: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn method_names_are_snake_case() {
let method_names = [
GetBlockchainStateRequest::METHOD,
GetNetworkInfoRequest::METHOD,
HealthzRequest::METHOD,
GetBlockRequest::METHOD,
GetBlockByHeightRequest::METHOD,
GetBlockRecordsRequest::METHOD,
GetCoinRecordRequest::METHOD,
GetCoinRecordsByHintRequest::METHOD,
GetCoinRecordsByPuzzleHashRequest::METHOD,
GetMempoolRequest::METHOD,
PushTxRequest::METHOD,
GetConnectionsRequest::METHOD,
BanPeerRequest::METHOD,
SubmitPartialCheckpointSignatureRequest::METHOD,
GetCheckpointPoolRequest::METHOD,
GetValidatorRequest::METHOD,
GetActiveValidatorsRequest::METHOD,
GetCurrentProposerRequest::METHOD,
StopNodeRequest::METHOD,
GetRecoveryStatusRequest::METHOD,
GetVersionRequest::METHOD,
];
for m in method_names {
assert!(
m.chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_'),
"method name {m:?} is not snake_case",
);
assert!(!m.is_empty(), "empty method name");
assert!(!m.starts_with('_'), "method {m:?} starts with underscore");
}
}
#[test]
fn method_names_are_unique() {
let method_names = [
GetBlockchainStateRequest::METHOD,
GetNetworkInfoRequest::METHOD,
HealthzRequest::METHOD,
GetBlockRequest::METHOD,
GetBlockByHeightRequest::METHOD,
GetBlockRecordsRequest::METHOD,
GetCoinRecordRequest::METHOD,
GetCoinRecordsByHintRequest::METHOD,
GetCoinRecordsByPuzzleHashRequest::METHOD,
GetMempoolRequest::METHOD,
PushTxRequest::METHOD,
GetConnectionsRequest::METHOD,
BanPeerRequest::METHOD,
SubmitPartialCheckpointSignatureRequest::METHOD,
GetCheckpointPoolRequest::METHOD,
GetValidatorRequest::METHOD,
GetActiveValidatorsRequest::METHOD,
GetCurrentProposerRequest::METHOD,
StopNodeRequest::METHOD,
GetRecoveryStatusRequest::METHOD,
GetVersionRequest::METHOD,
];
let mut seen = std::collections::HashSet::new();
for m in method_names {
assert!(seen.insert(m), "duplicate method name {m:?}");
}
}
#[test]
fn get_blockchain_state_roundtrip() {
let r = GetBlockchainStateResponse {
height: 100,
tip_hash: HashHex::new([1u8; 32]),
synced: true,
sync_progress: 1.0,
finalized_epoch: 3,
sealed_height: 96,
};
let j = serde_json::to_string(&r).unwrap();
let back: GetBlockchainStateResponse = serde_json::from_str(&j).unwrap();
assert_eq!(back.height, r.height);
assert_eq!(back.tip_hash, r.tip_hash);
assert_eq!(back.synced, r.synced);
}
#[test]
fn push_tx_status_snake_case() {
let s = serde_json::to_string(&PushTxStatus::Success).unwrap();
assert_eq!(s, "\"success\"");
let s = serde_json::to_string(&PushTxStatus::AlreadyExists).unwrap();
assert_eq!(s, "\"already_exists\"");
}
#[test]
fn optional_fields_omitted_when_none() {
let req = GetCoinRecordsByHintRequest {
hint: HashHex::new([0u8; 32]),
include_spent_coins: false,
start_height: None,
end_height: None,
};
let s = serde_json::to_string(&req).unwrap();
assert!(
!s.contains("start_height"),
"start_height should be omitted, got: {s}"
);
assert!(
!s.contains("end_height"),
"end_height should be omitted, got: {s}"
);
}
}