use crate::{
blockchain::UtxoSet,
crypto::{SaitoHash, SaitoPublicKey, SaitoUTXOSetKey},
};
use ahash::AHashMap;
use bigint::uint::U256;
use log::{error, info};
use saito_macros::TryFromByte;
use serde::{Deserialize, Serialize};
use std::convert::{TryFrom, TryInto};
pub const SLIP_SIZE: usize = 75;
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Hash, Eq, PartialEq, TryFromByte)]
pub enum SlipType {
Normal,
ATR,
VipInput,
VipOutput,
MinerInput,
MinerOutput,
RouterInput,
RouterOutput,
StakerOutput,
StakerDeposit,
StakerWithdrawalPending,
StakerWithdrawalStaking,
}
#[serde_with::serde_as]
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct Slip {
#[serde_as(as = "[_; 33]")]
publickey: SaitoPublicKey,
uuid: SaitoHash,
amount: u64,
payout: u64,
slip_ordinal: u8,
slip_type: SlipType,
#[serde_as(as = "[_; 74]")]
utxoset_key: SaitoUTXOSetKey,
is_utxoset_key_set: bool,
}
impl Slip {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self {
publickey: [0; 33],
uuid: [0; 32],
amount: 0,
payout: 0,
slip_ordinal: 0,
slip_type: SlipType::Normal,
utxoset_key: [0; 74],
is_utxoset_key_set: false,
}
}
pub fn validate(&self, utxoset: &UtxoSet) -> bool {
if self.get_amount() > 0 {
match utxoset.get(&self.utxoset_key) {
Some(value) => {
if *value == 1 {
true
} else {
info!(
"in utxoset but invalid: value is {} at {:?}",
*value, &self.utxoset_key
);
false
}
}
None => {
info!("not in utxoset so invalid");
error!(
"value is returned false: {:?} w/ type {:?} ordinal {} and amount {}",
self.utxoset_key,
self.get_slip_type(),
self.get_slip_ordinal(),
self.get_amount()
);
false
}
}
} else {
true
}
}
pub fn on_chain_reorganization(
&self,
utxoset: &mut AHashMap<SaitoUTXOSetKey, u64>,
_lc: bool,
slip_value: u64,
) {
if self.get_slip_type() == SlipType::StakerDeposit {
if _lc == true {
info!(
" ====> update deposit to {}: {:?} -- {:?}",
slip_value,
self.get_utxoset_key(),
self.get_slip_type()
);
}
}
if self.get_slip_type() == SlipType::StakerOutput {
if _lc == true {
info!(
" ====> update output to {}: {:?} -- {:?}",
slip_value,
self.get_utxoset_key(),
self.get_slip_type(),
);
}
}
if self.get_amount() > 0 {
if utxoset.contains_key(&self.utxoset_key) {
utxoset.insert(self.utxoset_key, slip_value);
} else {
utxoset.entry(self.utxoset_key).or_insert(slip_value);
}
}
}
pub fn get_publickey(&self) -> SaitoPublicKey {
self.publickey
}
pub fn get_amount(&self) -> u64 {
self.amount
}
pub fn get_payout(&self) -> u64 {
self.payout
}
pub fn get_uuid(&self) -> SaitoHash {
self.uuid
}
pub fn get_slip_ordinal(&self) -> u8 {
self.slip_ordinal
}
pub fn get_slip_type(&self) -> SlipType {
self.slip_type
}
pub fn set_publickey(&mut self, publickey: SaitoPublicKey) {
self.publickey = publickey;
}
pub fn set_amount(&mut self, amount: u64) {
self.amount = amount;
}
pub fn set_payout(&mut self, payout: u64) {
self.payout = payout;
}
pub fn set_uuid(&mut self, uuid: SaitoHash) {
self.uuid = uuid;
}
pub fn set_slip_ordinal(&mut self, slip_ordinal: u8) {
self.slip_ordinal = slip_ordinal;
}
pub fn set_slip_type(&mut self, slip_type: SlipType) {
self.slip_type = slip_type;
}
pub fn delete(&self, utxoset: &mut AHashMap<SaitoUTXOSetKey, u64>) -> bool {
if self.get_utxoset_key() == [0; 74] {
error!("ERROR 572034: asked to remove a slip without its utxoset_key properly set!");
false;
}
utxoset.remove_entry(&self.get_utxoset_key());
true
}
pub fn compare(&self, other: Slip) -> u64 {
let x = U256::from_big_endian(&self.get_publickey()[0..32]);
let y = U256::from_big_endian(&other.get_publickey()[0..32]);
if x > y {
return 1;
}
if y > x {
return 2;
}
let a = U256::from_big_endian(&self.get_utxoset_key()[42..74]);
let b = U256::from_big_endian(&other.get_utxoset_key()[42..74]);
info!("{:?} --- {:?}", a, b);
if a > b {
return 1;
}
if b > a {
return 2;
}
return 3;
}
pub fn serialize_input_for_signature(&self) -> Vec<u8> {
let mut vbytes: Vec<u8> = vec![];
vbytes.extend(&self.publickey);
vbytes.extend(&self.uuid);
vbytes.extend(&self.amount.to_be_bytes());
vbytes.extend(&(self.slip_ordinal.to_be_bytes()));
vbytes.extend(&(self.slip_type as u32).to_be_bytes());
vbytes
}
pub fn serialize_output_for_signature(&self) -> Vec<u8> {
let mut vbytes: Vec<u8> = vec![];
vbytes.extend(&self.publickey);
vbytes.extend(&[0; 32]);
vbytes.extend(&self.amount.to_be_bytes());
vbytes.extend(&(self.slip_ordinal.to_be_bytes()));
vbytes.extend(&(self.slip_type as u32).to_be_bytes());
vbytes
}
pub fn generate_utxoset_key(&mut self) {
self.utxoset_key = self.get_utxoset_key();
self.is_utxoset_key_set = true;
}
pub fn get_utxoset_key(&self) -> SaitoUTXOSetKey {
let mut res: Vec<u8> = vec![];
res.extend(&self.get_publickey());
res.extend(&self.get_uuid());
res.extend(&self.get_amount().to_be_bytes());
res.extend(&self.get_slip_ordinal().to_be_bytes());
res[0..74].try_into().unwrap()
}
pub fn deserialize_from_net(bytes: Vec<u8>) -> Slip {
let publickey: SaitoPublicKey = bytes[..33].try_into().unwrap();
let uuid: SaitoHash = bytes[33..65].try_into().unwrap();
let amount: u64 = u64::from_be_bytes(bytes[65..73].try_into().unwrap());
let slip_ordinal: u8 = bytes[73];
let slip_type: SlipType = SlipType::try_from(bytes[SLIP_SIZE - 1]).unwrap();
let mut slip = Slip::new();
slip.set_publickey(publickey);
slip.set_uuid(uuid);
slip.set_amount(amount);
slip.set_slip_ordinal(slip_ordinal);
slip.set_slip_type(slip_type);
slip
}
pub fn serialize_for_net(&self) -> Vec<u8> {
let mut vbytes: Vec<u8> = vec![];
vbytes.extend(&self.publickey);
vbytes.extend(&self.uuid);
vbytes.extend(&self.amount.to_be_bytes());
vbytes.extend(&self.slip_ordinal.to_be_bytes());
vbytes.extend(&(self.slip_type as u8).to_be_bytes());
vbytes
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::blockchain::Blockchain;
use crate::wallet::Wallet;
use std::sync::Arc;
use tokio::sync::RwLock;
#[test]
fn slip_new_test() {
let mut slip = Slip::new();
assert_eq!(slip.get_publickey(), [0; 33]);
assert_eq!(slip.get_uuid(), [0; 32]);
assert_eq!(slip.get_amount(), 0);
assert_eq!(slip.get_slip_type(), SlipType::Normal);
assert_eq!(slip.get_slip_ordinal(), 0);
slip.set_publickey([1; 33]);
assert_eq!(slip.get_publickey(), [1; 33]);
slip.set_amount(100);
assert_eq!(slip.get_amount(), 100);
slip.set_uuid([30; 32]);
assert_eq!(slip.get_uuid(), [30; 32]);
slip.set_slip_ordinal(1);
assert_eq!(slip.get_slip_ordinal(), 1);
slip.set_slip_type(SlipType::MinerInput);
assert_eq!(slip.get_slip_type(), SlipType::MinerInput);
}
#[test]
fn slip_serialize_for_signature_test() {
let slip = Slip::new();
assert_eq!(slip.serialize_input_for_signature(), vec![0; 78]);
}
#[test]
fn slip_get_utxoset_key_test() {
let slip = Slip::new();
assert_eq!(slip.get_utxoset_key(), [0; 74]);
}
#[test]
fn slip_serialization_for_net_test() {
let slip = Slip::new();
let serialized_slip = slip.serialize_for_net();
assert_eq!(serialized_slip.len(), 75);
let deserilialized_slip = Slip::deserialize_from_net(serialized_slip);
assert_eq!(slip, deserilialized_slip);
}
#[tokio::test]
#[serial_test::serial]
async fn slip_addition_and_removal_from_utxoset() {
let wallet_lock = Arc::new(RwLock::new(Wallet::new()));
let blockchain_lock = Arc::new(RwLock::new(Blockchain::new(wallet_lock.clone())));
let mut blockchain = blockchain_lock.write().await;
let wallet = wallet_lock.write().await;
let mut slip = Slip::new();
slip.set_amount(100_000);
slip.set_uuid([1; 32]);
slip.set_publickey(wallet.get_publickey());
slip.generate_utxoset_key();
slip.on_chain_reorganization(&mut blockchain.utxoset, true, 2);
assert_eq!(
blockchain.utxoset.contains_key(&slip.get_utxoset_key()),
true
);
}
}