neptune-rpc-api 0.14.0

Neptune JSON-RPC API contract: the RpcApi trait and its data-transfer model types
use neptune_consensus::transaction::Transaction;
use neptune_consensus::transaction::transaction_proof::TransactionProof;
use neptune_consensus::transaction::utxo::Coin;
use neptune_consensus::transaction::utxo::Utxo;
use neptune_consensus::transaction::validity::neptune_proof::NeptuneProof;
use neptune_consensus::transaction::validity::proof_collection::ProofCollection;
use neptune_primitives::network::Network;
use neptune_wallet::utxo_notification::PrivateNotificationData;
use neptune_wallet::utxo_notification::UtxoNotificationPayload;
use serde::Deserialize;
use serde::Serialize;
use tasm_lib::prelude::Digest;

use crate::model::block::transaction_kernel::RpcTransactionKernel;
use crate::model::common::RpcBFieldElements;

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct RpcNeptuneProof(RpcBFieldElements);

impl From<NeptuneProof> for RpcNeptuneProof {
    fn from(proof: NeptuneProof) -> Self {
        Self(proof.0.clone().into())
    }
}

impl From<RpcNeptuneProof> for NeptuneProof {
    fn from(proof: RpcNeptuneProof) -> NeptuneProof {
        proof.0.0.into()
    }
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct RpcProofCollection {
    pub removal_records_integrity: RpcNeptuneProof,
    pub collect_lock_scripts: RpcNeptuneProof,
    pub lock_scripts_halt: Vec<RpcNeptuneProof>,
    pub kernel_to_outputs: RpcNeptuneProof,
    pub collect_type_scripts: RpcNeptuneProof,
    pub type_scripts_halt: Vec<RpcNeptuneProof>,
    pub lock_script_hashes: Vec<Digest>,
    pub type_script_hashes: Vec<Digest>,
    pub kernel_mast_hash: Digest,
    pub salted_inputs_hash: Digest,
    pub salted_outputs_hash: Digest,
    pub merge_bit_mast_path: Vec<Digest>,
}

impl From<RpcProofCollection> for ProofCollection {
    fn from(pc: RpcProofCollection) -> Self {
        Self {
            removal_records_integrity: pc.removal_records_integrity.into(),
            collect_lock_scripts: pc.collect_lock_scripts.into(),
            lock_scripts_halt: pc.lock_scripts_halt.into_iter().map(Into::into).collect(),
            kernel_to_outputs: pc.kernel_to_outputs.into(),
            collect_type_scripts: pc.collect_type_scripts.into(),
            type_scripts_halt: pc.type_scripts_halt.into_iter().map(Into::into).collect(),
            lock_script_hashes: pc.lock_script_hashes,
            type_script_hashes: pc.type_script_hashes,
            kernel_mast_hash: pc.kernel_mast_hash,
            salted_inputs_hash: pc.salted_inputs_hash,
            salted_outputs_hash: pc.salted_outputs_hash,
            merge_bit_mast_path: pc.merge_bit_mast_path,
        }
    }
}

impl From<ProofCollection> for RpcProofCollection {
    fn from(pc: ProofCollection) -> Self {
        Self {
            removal_records_integrity: pc.removal_records_integrity.into(),
            collect_lock_scripts: pc.collect_lock_scripts.into(),
            lock_scripts_halt: pc.lock_scripts_halt.into_iter().map(Into::into).collect(),
            kernel_to_outputs: pc.kernel_to_outputs.into(),
            collect_type_scripts: pc.collect_type_scripts.into(),
            type_scripts_halt: pc.type_scripts_halt.into_iter().map(Into::into).collect(),
            lock_script_hashes: pc.lock_script_hashes,
            type_script_hashes: pc.type_script_hashes,
            kernel_mast_hash: pc.kernel_mast_hash,
            salted_inputs_hash: pc.salted_inputs_hash,
            salted_outputs_hash: pc.salted_outputs_hash,
            merge_bit_mast_path: pc.merge_bit_mast_path,
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(untagged)]
pub enum RpcTransactionProof {
    ProofCollection(Box<RpcProofCollection>),
    SingleProof(RpcNeptuneProof),
}

impl From<RpcTransactionProof> for TransactionProof {
    fn from(proof: RpcTransactionProof) -> TransactionProof {
        match proof {
            RpcTransactionProof::ProofCollection(pc) => {
                TransactionProof::ProofCollection((*pc).into())
            }
            RpcTransactionProof::SingleProof(sp) => TransactionProof::SingleProof(sp.into()),
        }
    }
}

impl TryFrom<TransactionProof> for RpcTransactionProof {
    type Error = String;

    fn try_from(value: TransactionProof) -> Result<Self, Self::Error> {
        match value {
            TransactionProof::Witness(_) => {
                Err("Cannot convert primitive witness to a transferable transaction proof as this would leak secrets".to_owned())
            }
            TransactionProof::SingleProof(sp) => Ok(RpcTransactionProof::SingleProof(sp.into())),
            TransactionProof::ProofCollection(pc) => Ok(RpcTransactionProof::ProofCollection(Box::new(pc.into()))),
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcTransaction {
    pub kernel: RpcTransactionKernel,
    pub proof: RpcTransactionProof,
}

impl From<RpcTransaction> for Transaction {
    fn from(tx: RpcTransaction) -> Self {
        Transaction {
            kernel: tx.kernel.into(),
            proof: tx.proof.into(),
        }
    }
}

impl TryFrom<Transaction> for RpcTransaction {
    type Error = String;

    fn try_from(value: Transaction) -> Result<Self, Self::Error> {
        Ok(Self {
            kernel: RpcTransactionKernel::from(&value.kernel),
            proof: value.proof.try_into()?,
        })
    }
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct RpcCoin {
    pub type_script_hash: Digest,
    pub state: RpcBFieldElements,
}

impl From<Coin> for RpcCoin {
    fn from(value: Coin) -> Self {
        Self {
            type_script_hash: value.type_script_hash,
            state: value.state.into(),
        }
    }
}

impl From<&Coin> for RpcCoin {
    fn from(value: &Coin) -> Self {
        Self {
            type_script_hash: value.type_script_hash,
            state: value.state.to_owned().into(),
        }
    }
}

impl From<RpcCoin> for Coin {
    fn from(value: RpcCoin) -> Self {
        Self {
            type_script_hash: value.type_script_hash,
            state: value.state.into(),
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct RpcUtxo {
    lock_script_hash: Digest,
    coins: Vec<RpcCoin>,
}

impl From<Utxo> for RpcUtxo {
    fn from(value: Utxo) -> Self {
        Self {
            lock_script_hash: value.lock_script_hash(),
            coins: value.coins().iter().map(|x| x.into()).collect(),
        }
    }
}

impl From<RpcUtxo> for Utxo {
    fn from(value: RpcUtxo) -> Self {
        Self::new(
            value.lock_script_hash,
            value.coins.into_iter().map(|x| x.into()).collect(),
        )
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RpcUtxoNotificationPayload {
    pub(crate) utxo: RpcUtxo,
    pub(crate) sender_randomness: Digest,
}

impl From<UtxoNotificationPayload> for RpcUtxoNotificationPayload {
    fn from(value: UtxoNotificationPayload) -> Self {
        Self {
            utxo: value.utxo.into(),
            sender_randomness: value.sender_randomness,
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcPrivateNotificationData {
    pub cleartext: RpcUtxoNotificationPayload,
    pub ciphertext: String,
    pub recipient_address: String,

    /// Indicates if this client can unlock the UTXO
    pub owned: bool,
}

impl RpcPrivateNotificationData {
    pub fn from_private_notification_data(
        value: PrivateNotificationData,
        network: Network,
    ) -> Self {
        Self {
            cleartext: value.cleartext.into(),
            ciphertext: value.ciphertext,
            recipient_address: value
                .recipient_address
                .to_bech32m(network)
                .expect("Locally produced address must be bech32 serializable"),
            owned: value.owned,
        }
    }
}