use std::convert::TryFrom;
use std::fmt::{self, Display, Formatter};
use std::ops::Deref;
use std::sync::Arc;
use bitcoin::blockdata::block;
use bitcoin::consensus::encode::deserialize;
use bitcoin::hashes::hex::FromHex;
use bitcoin::hashes::{sha256, Hash};
use bitcoin::{Script, Txid};
use serde::{de, Deserialize, Serialize};
static JSONRPC_2_0: &str = "2.0";
#[derive(Serialize, Clone)]
#[serde(untagged)]
pub enum Param {
U32(u32),
Usize(usize),
String(String),
Bool(bool),
Bytes(Vec<u8>),
}
#[derive(Serialize, Clone)]
pub struct Request<'a> {
jsonrpc: &'static str,
pub id: usize,
pub method: &'a str,
pub params: Vec<Param>,
}
impl<'a> Request<'a> {
fn new(method: &'a str, params: Vec<Param>) -> Self {
Self {
id: 0,
jsonrpc: JSONRPC_2_0,
method,
params,
}
}
pub fn new_id(id: usize, method: &'a str, params: Vec<Param>) -> Self {
let mut instance = Self::new(method, params);
instance.id = id;
instance
}
}
#[doc(hidden)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize)]
pub struct Hex32Bytes(#[serde(deserialize_with = "from_hex")] [u8; 32]);
impl Deref for Hex32Bytes {
type Target = [u8; 32];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<[u8; 32]> for Hex32Bytes {
fn from(other: [u8; 32]) -> Hex32Bytes {
Hex32Bytes(other)
}
}
pub type ScriptHash = Hex32Bytes;
pub type ScriptStatus = Hex32Bytes;
pub trait ToElectrumScriptHash {
fn to_electrum_scripthash(&self) -> ScriptHash;
}
impl ToElectrumScriptHash for Script {
fn to_electrum_scripthash(&self) -> ScriptHash {
let mut result = sha256::Hash::hash(self.as_bytes()).into_inner();
result.reverse();
result.into()
}
}
fn from_hex<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: FromHex,
D: de::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
T::from_hex(&s).map_err(de::Error::custom)
}
fn from_hex_array<'de, T, D>(deserializer: D) -> Result<Vec<T>, D::Error>
where
T: FromHex + std::fmt::Debug,
D: de::Deserializer<'de>,
{
let arr = Vec::<String>::deserialize(deserializer)?;
let results: Vec<Result<T, _>> = arr
.into_iter()
.map(|s| T::from_hex(&s).map_err(de::Error::custom))
.collect();
let mut answer = Vec::new();
for x in results.into_iter() {
answer.push(x?);
}
Ok(answer)
}
fn from_hex_header<'de, D>(deserializer: D) -> Result<block::BlockHeader, D::Error>
where
D: de::Deserializer<'de>,
{
let vec: Vec<u8> = from_hex(deserializer)?;
deserialize(&vec).map_err(de::Error::custom)
}
#[derive(Debug, Deserialize)]
pub struct GetHistoryRes {
pub height: i32,
pub tx_hash: Txid,
pub fee: Option<u64>,
}
#[derive(Debug, Deserialize)]
pub struct ListUnspentRes {
pub height: usize,
pub tx_hash: Txid,
pub tx_pos: usize,
pub value: u64,
}
#[derive(Debug, Deserialize)]
pub struct ServerFeaturesRes {
pub server_version: String,
#[serde(deserialize_with = "from_hex")]
pub genesis_hash: [u8; 32],
pub protocol_min: String,
pub protocol_max: String,
pub hash_function: Option<String>,
pub pruning: Option<i64>,
}
#[derive(Debug, Deserialize)]
pub struct GetHeadersRes {
pub max: usize,
pub count: usize,
#[serde(rename(deserialize = "hex"), deserialize_with = "from_hex")]
pub raw_headers: Vec<u8>,
#[serde(skip)]
pub headers: Vec<block::BlockHeader>,
}
#[derive(Debug, Deserialize)]
pub struct GetBalanceRes {
pub confirmed: u64,
pub unconfirmed: i64,
}
#[derive(Debug, Deserialize)]
pub struct GetMerkleRes {
pub block_height: usize,
pub pos: usize,
#[serde(deserialize_with = "from_hex_array")]
pub merkle: Vec<[u8; 32]>,
}
#[derive(Debug, Deserialize)]
pub struct HeaderNotification {
pub height: usize,
#[serde(rename = "hex", deserialize_with = "from_hex_header")]
pub header: block::BlockHeader,
}
#[derive(Debug, Deserialize)]
pub struct RawHeaderNotification {
pub height: usize,
#[serde(rename = "hex", deserialize_with = "from_hex")]
pub header: Vec<u8>,
}
impl TryFrom<RawHeaderNotification> for HeaderNotification {
type Error = Error;
fn try_from(raw: RawHeaderNotification) -> Result<Self, Self::Error> {
Ok(HeaderNotification {
height: raw.height,
header: deserialize(&raw.header)?,
})
}
}
#[derive(Debug, Deserialize)]
pub struct ScriptNotification {
pub scripthash: ScriptHash,
pub status: ScriptStatus,
}
#[derive(Debug)]
pub enum Error {
IOError(std::io::Error),
JSON(serde_json::error::Error),
Hex(bitcoin::hashes::hex::Error),
Protocol(serde_json::Value),
Bitcoin(bitcoin::consensus::encode::Error),
AlreadySubscribed(ScriptHash),
NotSubscribed(ScriptHash),
InvalidResponse(serde_json::Value),
Message(String),
InvalidDNSNameError(String),
MissingDomain,
AllAttemptsErrored(Vec<Error>),
SharedIOError(Arc<std::io::Error>),
BothSocksAndTimeout,
CouldntLockReader,
#[cfg(feature = "use-openssl")]
InvalidSslMethod(openssl::error::ErrorStack),
#[cfg(feature = "use-openssl")]
SslHandshakeError(openssl::ssl::HandshakeError<std::net::TcpStream>),
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Error::IOError(e) => Display::fmt(e, f),
Error::JSON(e) => Display::fmt(e, f),
Error::Hex(e) => Display::fmt(e, f),
Error::Bitcoin(e) => Display::fmt(e, f),
Error::SharedIOError(e) => Display::fmt(e, f),
#[cfg(feature = "use-openssl")]
Error::SslHandshakeError(e) => Display::fmt(e, f),
#[cfg(feature = "use-openssl")]
Error::InvalidSslMethod(e) => Display::fmt(e, f),
Error::Message(e) => f.write_str(e),
Error::InvalidDNSNameError(domain) => write!(f, "Invalid domain name {} not matching SSL certificate", domain),
Error::AllAttemptsErrored(errors) => {
f.write_str("Made one or multiple attempts, all errored:\n")?;
for err in errors {
writeln!(f, "\t- {}", err)?;
}
Ok(())
}
Error::Protocol(e) => write!(f, "Electrum server error: {}", e.clone().take()),
Error::InvalidResponse(e) => write!(f, "Error during the deserialization of a response from the server: {}", e.clone().take()),
Error::AlreadySubscribed(_) => write!(f, "Already subscribed to the notifications of an address"),
Error::NotSubscribed(_) => write!(f, "Not subscribed to the notifications of an address"),
Error::MissingDomain => f.write_str("Missing domain while it was explicitly asked to validate it"),
Error::BothSocksAndTimeout => f.write_str("Setting both a proxy and a timeout in `Config` is an error"),
Error::CouldntLockReader => f.write_str("Couldn't take a lock on the reader mutex. This means that there's already another reader thread is running"),
}
}
}
impl std::error::Error for Error {}
macro_rules! impl_error {
( $from:ty, $to:ident ) => {
impl std::convert::From<$from> for Error {
fn from(err: $from) -> Self {
Error::$to(err.into())
}
}
};
}
impl_error!(std::io::Error, IOError);
impl_error!(serde_json::Error, JSON);
impl_error!(bitcoin::hashes::hex::Error, Hex);
impl_error!(bitcoin::consensus::encode::Error, Bitcoin);