use std::{
convert::TryFrom,
fmt::{Display, Error, Formatter},
};
use serde::{Deserialize, Serialize};
use tari_common_types::types::{BlockHash, CompressedSignature};
use tari_utilities::ByteArray;
use crate::proto::base_node as proto;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct TxSubmissionResponse {
pub accepted: bool,
pub rejection_reason: TxSubmissionRejectionReason,
pub is_synced: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum TxSubmissionRejectionReason {
None,
AlreadyMined,
DoubleSpend,
Orphan,
TimeLocked,
ValidationFailed,
FeeTooLow,
}
impl Display for TxSubmissionRejectionReason {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), Error> {
#[allow(clippy::enum_glob_use)]
use TxSubmissionRejectionReason::*;
let response = match self {
AlreadyMined => "Already Mined ",
DoubleSpend => "Double Spend",
Orphan => "Orphan",
TimeLocked => "Time Locked",
ValidationFailed => "Validation Failed",
FeeTooLow => "Fee too low",
None => "None",
};
fmt.write_str(response)
}
}
impl TryFrom<proto::TxSubmissionRejectionReason> for TxSubmissionRejectionReason {
type Error = String;
fn try_from(tx_rejection_reason: proto::TxSubmissionRejectionReason) -> Result<Self, Self::Error> {
#[allow(clippy::enum_glob_use)]
use proto::TxSubmissionRejectionReason::*;
Ok(match tx_rejection_reason {
None => TxSubmissionRejectionReason::None,
AlreadyMined => TxSubmissionRejectionReason::AlreadyMined,
DoubleSpend => TxSubmissionRejectionReason::DoubleSpend,
Orphan => TxSubmissionRejectionReason::Orphan,
TimeLocked => TxSubmissionRejectionReason::TimeLocked,
ValidationFailed => TxSubmissionRejectionReason::ValidationFailed,
FeeTooLow => TxSubmissionRejectionReason::FeeTooLow,
})
}
}
impl From<TxSubmissionRejectionReason> for proto::TxSubmissionRejectionReason {
fn from(response: TxSubmissionRejectionReason) -> Self {
#[allow(clippy::enum_glob_use)]
use TxSubmissionRejectionReason::*;
match response {
None => proto::TxSubmissionRejectionReason::None,
AlreadyMined => proto::TxSubmissionRejectionReason::AlreadyMined,
DoubleSpend => proto::TxSubmissionRejectionReason::DoubleSpend,
Orphan => proto::TxSubmissionRejectionReason::Orphan,
TimeLocked => proto::TxSubmissionRejectionReason::TimeLocked,
ValidationFailed => proto::TxSubmissionRejectionReason::ValidationFailed,
FeeTooLow => proto::TxSubmissionRejectionReason::FeeTooLow,
}
}
}
impl TryFrom<proto::TxSubmissionResponse> for TxSubmissionResponse {
type Error = String;
fn try_from(value: proto::TxSubmissionResponse) -> Result<Self, Self::Error> {
Ok(Self {
accepted: value.accepted,
rejection_reason: TxSubmissionRejectionReason::try_from(
proto::TxSubmissionRejectionReason::try_from(value.rejection_reason)
.map_err(|_| "Invalid or unrecognised `TxSubmissionRejectionReason` enum".to_string())?,
)?,
is_synced: value.is_synced,
})
}
}
impl From<TxSubmissionResponse> for proto::TxSubmissionResponse {
fn from(value: TxSubmissionResponse) -> Self {
Self {
accepted: value.accepted,
rejection_reason: proto::TxSubmissionRejectionReason::from(value.rejection_reason) as i32,
is_synced: value.is_synced,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct TxQueryResponse {
pub location: TxLocation,
pub best_block_hash: Option<BlockHash>,
pub confirmations: u64,
pub is_synced: bool,
pub best_block_height: u64,
pub mined_timestamp: Option<u64>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct TxQueryBatchResponse {
pub signature: CompressedSignature,
pub location: TxLocation,
pub best_block_hash: Option<BlockHash>,
pub confirmations: u64,
pub best_block_height: u64,
pub mined_timestamp: Option<u64>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum TxLocation {
NotStored,
InMempool,
Mined,
}
impl Display for TxLocation {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), Error> {
let response = match self {
TxLocation::NotStored => "Not Stored",
TxLocation::InMempool => "In Mempool",
TxLocation::Mined => "Mined",
};
fmt.write_str(response)
}
}
impl TryFrom<proto::TxLocation> for TxLocation {
type Error = String;
fn try_from(tx_location: proto::TxLocation) -> Result<Self, Self::Error> {
use proto::TxLocation::{InMempool, Mined, None, NotStored};
Ok(match tx_location {
None => return Err("TxLocation not provided".to_string()),
NotStored => TxLocation::NotStored,
InMempool => TxLocation::InMempool,
Mined => TxLocation::Mined,
})
}
}
impl From<TxLocation> for proto::TxLocation {
fn from(resp: TxLocation) -> Self {
use TxLocation::{InMempool, Mined, NotStored};
match resp {
NotStored => proto::TxLocation::NotStored,
InMempool => proto::TxLocation::InMempool,
Mined => proto::TxLocation::Mined,
}
}
}
impl TryFrom<proto::TxQueryResponse> for TxQueryResponse {
type Error = String;
fn try_from(proto_response: proto::TxQueryResponse) -> Result<Self, Self::Error> {
let hash = if proto_response.best_block_hash.is_empty() {
None
} else {
Some(match BlockHash::try_from(proto_response.best_block_hash.clone()) {
Ok(h) => h,
Err(e) => {
return Err(format!("Malformed block hash: {e}"));
},
})
};
let mined_timestamp = match proto_response.mined_timestamp {
0 => None,
t => Some(t),
};
Ok(Self {
location: TxLocation::try_from(
proto::TxLocation::try_from(proto_response.location)
.map_err(|_| "Invalid or unrecognised `TxLocation` enum".to_string())?,
)?,
best_block_hash: hash,
confirmations: proto_response.confirmations,
is_synced: proto_response.is_synced,
best_block_height: proto_response.best_block_height,
mined_timestamp,
})
}
}
impl From<TxQueryResponse> for proto::TxQueryResponse {
fn from(response: TxQueryResponse) -> Self {
Self {
location: proto::TxLocation::from(response.location) as i32,
best_block_hash: response.best_block_hash.map(|v| v.to_vec()).unwrap_or_default(),
confirmations: response.confirmations,
is_synced: response.is_synced,
best_block_height: response.best_block_height,
mined_timestamp: response.mined_timestamp.unwrap_or_default(),
}
}
}
impl TryFrom<proto::TxQueryBatchResponse> for TxQueryBatchResponse {
type Error = String;
fn try_from(proto_response: proto::TxQueryBatchResponse) -> Result<Self, Self::Error> {
let hash = if proto_response.best_block_hash.is_empty() {
None
} else {
Some(match BlockHash::try_from(proto_response.best_block_hash.clone()) {
Ok(h) => h,
Err(e) => {
return Err(format!("Malformed block hash: {e}"));
},
})
};
let mined_timestamp = match proto_response.mined_timestamp {
0 => None,
t => Some(t),
};
Ok(Self {
signature: CompressedSignature::try_from(
proto_response
.signature
.ok_or_else(|| "Signature not present".to_string())?,
)?,
location: TxLocation::try_from(
proto::TxLocation::try_from(proto_response.location)
.map_err(|_| "Invalid or unrecognised `TxLocation` enum".to_string())?,
)?,
best_block_hash: hash,
best_block_height: proto_response.best_block_height,
confirmations: proto_response.confirmations,
mined_timestamp,
})
}
}