#![allow(missing_docs)]
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StatusResponse {
pub network: String,
pub version: String,
pub commit: String,
pub node_info: NodeInfo,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct NodeInfo {
pub id: String,
pub listen_addr: String,
pub network: String,
pub version: String,
pub channels: String,
pub moniker: String,
pub other: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct QueryResponse<T> {
pub result: T,
pub height: i64,
pub proof: Option<serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TransactionResponse {
pub txid: String,
pub hash: String,
pub height: i64,
pub index: i32,
pub tx: serde_json::Value,
pub tx_result: TransactionResult,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TransactionResult {
pub code: i32,
pub data: Option<String>,
pub log: String,
pub info: String,
pub gas_wanted: String,
pub gas_used: String,
pub events: Vec<Event>,
pub codespace: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Event {
#[serde(rename = "type")]
pub event_type: String,
pub attributes: Vec<Attribute>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Attribute {
pub key: String,
pub value: String,
pub index: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SignedTransaction {
pub body: serde_json::Value,
pub signatures: Vec<Signature>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Signature {
pub public_key: String,
pub signature: String,
#[serde(rename = "type")]
pub signature_type: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Account {
pub url: String,
#[serde(rename = "type")]
pub account_type: String,
pub data: serde_json::Value,
pub credits: Option<i64>,
pub nonce: Option<i64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FaucetResponse {
#[serde(default, alias = "transactionHash", alias = "hash")]
pub txid: String,
#[serde(default)]
pub link: String,
#[serde(default)]
pub account: String,
#[serde(default)]
pub amount: String,
#[serde(default, alias = "simpleHash")]
pub simple_hash: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MerkleReceiptEntry {
#[serde(default)]
pub right: bool,
#[serde(with = "hex::serde")]
pub hash: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MerkleReceipt {
#[serde(with = "hex::serde")]
pub start: Vec<u8>,
#[serde(default)]
pub start_index: i64,
#[serde(with = "hex::serde")]
pub end: Vec<u8>,
#[serde(default)]
pub end_index: i64,
#[serde(with = "hex::serde")]
pub anchor: Vec<u8>,
#[serde(default)]
pub entries: Vec<MerkleReceiptEntry>,
}
impl MerkleReceipt {
pub fn new() -> Self {
Self {
start: Vec::new(),
start_index: 0,
end: Vec::new(),
end_index: 0,
anchor: Vec::new(),
entries: Vec::new(),
}
}
pub fn validate(&self) -> Result<(), crate::errors::Error> {
use crate::errors::ValidationError;
if !self.start.is_empty() && self.start.len() != 32 {
return Err(ValidationError::InvalidHash {
expected: 32,
actual: self.start.len(),
}.into());
}
if !self.end.is_empty() && self.end.len() != 32 {
return Err(ValidationError::InvalidHash {
expected: 32,
actual: self.end.len(),
}.into());
}
if !self.anchor.is_empty() && self.anchor.len() != 32 {
return Err(ValidationError::InvalidHash {
expected: 32,
actual: self.anchor.len(),
}.into());
}
for (i, entry) in self.entries.iter().enumerate() {
if entry.hash.len() != 32 {
return Err(ValidationError::InvalidFieldValue {
field: format!("entries[{}].hash", i),
reason: format!("expected 32 bytes, got {}", entry.hash.len()),
}.into());
}
}
if self.start_index < 0 {
return Err(ValidationError::OutOfRange {
field: "startIndex".to_string(),
min: "0".to_string(),
max: "i64::MAX".to_string(),
}.into());
}
if self.end_index < 0 {
return Err(ValidationError::OutOfRange {
field: "endIndex".to_string(),
min: "0".to_string(),
max: "i64::MAX".to_string(),
}.into());
}
if self.end_index < self.start_index {
return Err(ValidationError::InvalidFieldValue {
field: "endIndex".to_string(),
reason: format!("endIndex ({}) must be >= startIndex ({})", self.end_index, self.start_index),
}.into());
}
Ok(())
}
pub fn verify(&self) -> Result<bool, crate::errors::Error> {
use sha2::{Sha256, Digest};
self.validate()?;
if self.start.is_empty() || self.anchor.is_empty() {
return Ok(false);
}
let mut current: [u8; 32] = self.start.clone().try_into()
.map_err(|_| crate::errors::Error::General("Invalid start hash length".to_string()))?;
for entry in &self.entries {
let entry_hash: [u8; 32] = entry.hash.clone().try_into()
.map_err(|_| crate::errors::Error::General("Invalid entry hash length".to_string()))?;
let mut hasher = Sha256::new();
if entry.right {
hasher.update(¤t);
hasher.update(&entry_hash);
} else {
hasher.update(&entry_hash);
hasher.update(¤t);
}
current = hasher.finalize().into();
}
Ok(current.as_slice() == self.anchor.as_slice())
}
pub fn is_empty(&self) -> bool {
self.start.is_empty() && self.end.is_empty() && self.anchor.is_empty() && self.entries.is_empty()
}
}
impl Default for MerkleReceipt {
fn default() -> Self {
Self::new()
}
}
impl std::hash::Hash for MerkleReceipt {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.start.hash(state);
self.start_index.hash(state);
self.end.hash(state);
self.end_index.hash(state);
self.anchor.hash(state);
self.entries.hash(state);
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct V3SubmitRequest {
pub envelope: TransactionEnvelope,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct V3SubmitResponse {
pub hash: String,
pub result: SubmitResult,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SubmitResult {
pub code: i32,
pub message: String,
pub data: Option<serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TransactionEnvelope {
pub transaction: serde_json::Value,
pub signatures: Vec<V3Signature>,
pub metadata: Option<serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct V3Signature {
pub public_key: Vec<u8>,
pub signature: Vec<u8>,
pub timestamp: i64,
pub vote: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct V3NodeInfo {
#[serde(default)]
pub peer_id: String,
#[serde(default)]
pub network: String,
#[serde(default)]
pub services: Vec<ServiceAddress>,
#[serde(default)]
pub version: String,
#[serde(default)]
pub commit: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ServiceAddress {
#[serde(rename = "type")]
pub service_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub argument: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct NodeInfoOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub peer_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct V3ConsensusStatus {
#[serde(default)]
pub ok: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_block: Option<LastBlock>,
#[serde(default)]
pub version: String,
#[serde(default)]
pub commit: String,
#[serde(default)]
pub node_key_hash: String,
#[serde(default)]
pub validator_key_hash: String,
#[serde(default)]
pub partition_id: String,
#[serde(default)]
pub partition_type: String,
#[serde(default)]
pub peers: Vec<ConsensusPeerInfo>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LastBlock {
#[serde(default)]
pub height: i64,
#[serde(default)]
pub time: String,
#[serde(default)]
pub chain_root: String,
#[serde(default)]
pub state_root: String,
#[serde(default)]
pub directory_anchor_height: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ConsensusPeerInfo {
#[serde(default)]
pub node_id: String,
#[serde(default)]
pub host: String,
#[serde(default)]
pub port: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct ConsensusStatusOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub node_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub partition: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_peers: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_accumulate: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct V3NetworkStatus {
#[serde(skip_serializing_if = "Option::is_none")]
pub oracle: Option<AcmeOracle>,
#[serde(skip_serializing_if = "Option::is_none")]
pub globals: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub network: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub routing: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub executor_version: Option<String>,
#[serde(default)]
pub directory_height: u64,
#[serde(default)]
pub major_block_height: u64,
#[serde(default)]
pub bvn_executor_versions: Vec<PartitionExecutorVersion>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AcmeOracle {
#[serde(default)]
pub price: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PartitionExecutorVersion {
#[serde(default)]
pub partition: String,
#[serde(default)]
pub version: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct NetworkStatusOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub partition: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct V3Metrics {
#[serde(default)]
pub tps: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct MetricsOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub partition: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub span: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct V3Submission {
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<serde_json::Value>,
#[serde(default)]
pub success: bool,
#[serde(default)]
pub message: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct SubmitOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub verify: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub wait: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct ValidateOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub full: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct V3FaucetOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub token: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct V3SnapshotInfo {
#[serde(skip_serializing_if = "Option::is_none")]
pub header: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub consensus_info: Option<serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct ListSnapshotsOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub node_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub partition: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct FindServiceOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub network: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub service: Option<ServiceAddress>,
#[serde(skip_serializing_if = "Option::is_none")]
pub known: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FindServiceResult {
#[serde(default)]
pub peer_id: String,
#[serde(default)]
pub status: String,
#[serde(default)]
pub addresses: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct SubscribeOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub partition: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub account: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct RangeOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub start: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub count: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expand: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub from_end: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct ReceiptOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub for_any: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub for_height: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct DefaultQuery {
#[serde(skip_serializing_if = "Option::is_none")]
pub include_receipt: Option<ReceiptOptions>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct ChainQuery {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub index: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub entry: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub range: Option<RangeOptions>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_receipt: Option<ReceiptOptions>,
}
impl ChainQuery {
pub fn validate(&self) -> Result<(), crate::errors::Error> {
let has_name = self.name.is_some();
let has_index = self.index.is_some();
let has_entry = self.entry.is_some();
let has_range = self.range.is_some();
if has_range && (has_index || has_entry) {
return Err(crate::errors::ValidationError::InvalidFieldValue {
field: "range".to_string(),
reason: "range is mutually exclusive with index and entry".to_string(),
}.into());
}
if !has_name && (has_index || has_entry || has_range) {
return Err(crate::errors::ValidationError::RequiredFieldMissing(
"name is required when querying by index, entry, or range".to_string(),
).into());
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct DataQuery {
#[serde(skip_serializing_if = "Option::is_none")]
pub index: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub entry: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub range: Option<RangeOptions>,
}
impl DataQuery {
pub fn validate(&self) -> Result<(), crate::errors::Error> {
let has_index = self.index.is_some();
let has_entry = self.entry.is_some();
let has_range = self.range.is_some();
if has_range && (has_index || has_entry) {
return Err(crate::errors::ValidationError::InvalidFieldValue {
field: "range".to_string(),
reason: "range is mutually exclusive with index and entry".to_string(),
}.into());
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct DirectoryQuery {
#[serde(skip_serializing_if = "Option::is_none")]
pub range: Option<RangeOptions>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct PendingQuery {
#[serde(skip_serializing_if = "Option::is_none")]
pub range: Option<RangeOptions>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct BlockQuery {
#[serde(skip_serializing_if = "Option::is_none")]
pub minor: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub major: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub minor_range: Option<RangeOptions>,
#[serde(skip_serializing_if = "Option::is_none")]
pub major_range: Option<RangeOptions>,
#[serde(skip_serializing_if = "Option::is_none")]
pub entry_range: Option<RangeOptions>,
#[serde(skip_serializing_if = "Option::is_none")]
pub omit_empty: Option<bool>,
}
impl BlockQuery {
pub fn validate(&self) -> Result<(), crate::errors::Error> {
let has_minor = self.minor.is_some();
let has_major = self.major.is_some();
let has_minor_range = self.minor_range.is_some();
let has_major_range = self.major_range.is_some();
let has_entry_range = self.entry_range.is_some();
if !has_minor && !has_major && !has_minor_range && !has_major_range {
return Err(crate::errors::ValidationError::RequiredFieldMissing(
"minor, major, minor_range, or major_range must be specified".to_string(),
).into());
}
if has_minor && has_major {
return Err(crate::errors::ValidationError::InvalidFieldValue {
field: "minor/major".to_string(),
reason: "minor and major are mutually exclusive".to_string(),
}.into());
}
if has_minor_range && has_major_range {
return Err(crate::errors::ValidationError::InvalidFieldValue {
field: "minor_range/major_range".to_string(),
reason: "minor_range and major_range are mutually exclusive".to_string(),
}.into());
}
if has_minor && (has_minor_range || has_major_range) {
return Err(crate::errors::ValidationError::InvalidFieldValue {
field: "minor".to_string(),
reason: "minor is mutually exclusive with minor_range and major_range".to_string(),
}.into());
}
if has_major && has_major_range {
return Err(crate::errors::ValidationError::InvalidFieldValue {
field: "major".to_string(),
reason: "major and major_range are mutually exclusive".to_string(),
}.into());
}
if has_entry_range && (has_major || has_minor_range || has_major_range) {
return Err(crate::errors::ValidationError::InvalidFieldValue {
field: "entry_range".to_string(),
reason: "entry_range is mutually exclusive with major, minor_range, and major_range".to_string(),
}.into());
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AnchorSearchQuery {
pub anchor: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_receipt: Option<ReceiptOptions>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PublicKeySearchQuery {
pub public_key: String,
#[serde(rename = "type")]
pub signature_type: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PublicKeyHashSearchQuery {
pub public_key_hash: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DelegateSearchQuery {
pub delegate: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MessageHashSearchQuery {
pub hash: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "queryType", rename_all = "camelCase")]
pub enum V3Query {
#[serde(rename = "default")]
Default(DefaultQuery),
#[serde(rename = "chain")]
Chain(ChainQuery),
#[serde(rename = "data")]
Data(DataQuery),
#[serde(rename = "directory")]
Directory(DirectoryQuery),
#[serde(rename = "pending")]
Pending(PendingQuery),
#[serde(rename = "block")]
Block(BlockQuery),
#[serde(rename = "anchorSearch")]
AnchorSearch(AnchorSearchQuery),
#[serde(rename = "publicKeySearch")]
PublicKeySearch(PublicKeySearchQuery),
#[serde(rename = "publicKeyHashSearch")]
PublicKeyHashSearch(PublicKeyHashSearchQuery),
#[serde(rename = "delegateSearch")]
DelegateSearch(DelegateSearchQuery),
#[serde(rename = "messageHashSearch")]
MessageHashSearch(MessageHashSearchQuery),
}
impl V3Query {
pub fn validate(&self) -> Result<(), crate::errors::Error> {
match self {
V3Query::Chain(q) => q.validate(),
V3Query::Data(q) => q.validate(),
V3Query::Block(q) => q.validate(),
_ => Ok(()),
}
}
}