amadeus-node 1.3.6

The core library for the Amadeus blockchain node, containing consensus, networking and runtime
Documentation
use crate::config::Config;
use crate::consensus::DST_TX;
use crate::utils::bls12_381;
use crate::utils::{Hash, PublicKey, Signature};
use amadeus_utils::vecpak;
use sha2::{Digest, Sha256};

mod args_serde {
    use serde::{Deserialize, Deserializer, Serialize, Serializer};
    pub fn serialize<S: Serializer>(args: &[Vec<u8>], ser: S) -> Result<S::Ok, S::Error> {
        let v: Vec<serde_bytes::ByteBuf> = args.iter().map(|a| serde_bytes::ByteBuf::from(a.clone())).collect();
        v.serialize(ser)
    }
    pub fn deserialize<'de, D: Deserializer<'de>>(de: D) -> Result<Vec<Vec<u8>>, D::Error> {
        let v: Vec<serde_bytes::ByteBuf> = Deserialize::deserialize(de)?;
        Ok(v.into_iter().map(|b| b.into_vec()).collect())
    }
}

mod serde_bytes_option {
    use serde::{Deserialize, Deserializer, Serialize, Serializer};
    pub fn serialize<S: Serializer>(opt: &Option<Vec<u8>>, ser: S) -> Result<S::Ok, S::Error> {
        match opt {
            Some(v) => serde_bytes::ByteBuf::from(v.clone()).serialize(ser),
            None => ser.serialize_none(),
        }
    }
    pub fn deserialize<'de, D: Deserializer<'de>>(de: D) -> Result<Option<Vec<u8>>, D::Error> {
        let opt: Option<serde_bytes::ByteBuf> = Deserialize::deserialize(de)?;
        Ok(opt.map(|b| b.into_vec()))
    }
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct TxAction {
    #[serde(with = "args_serde")]
    pub args: Vec<Vec<u8>>,
    #[serde(with = "serde_bytes")]
    pub contract: Vec<u8>,
    #[serde(with = "serde_bytes")]
    pub function: Vec<u8>,
    pub op: String,
    #[serde(skip_serializing_if = "Option::is_none", with = "serde_bytes_option", default)]
    pub attached_symbol: Option<Vec<u8>>,
    #[serde(skip_serializing_if = "Option::is_none", with = "serde_bytes_option", default)]
    pub attached_amount: Option<Vec<u8>>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Tx {
    pub action: TxAction,
    pub nonce: i128,
    pub signer: PublicKey,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct TxU {
    pub hash: Hash,
    pub signature: Signature,
    pub tx: Tx,
}

pub type EntryTxAction = TxAction;
pub type EntryTxInner = Tx;
pub type EntryTx = TxU;

impl TxU {
    pub fn tx_encoded(&self) -> Vec<u8> {
        vecpak::to_vec(&self.tx).unwrap_or_default()
    }
}

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("wrong term type: {0}")]
    WrongType(&'static str),
    #[error("invalid hash")]
    InvalidHash,
    #[error("invalid signature")]
    InvalidSignature,
    #[error("nonce too high")]
    NonceTooHigh,
    #[error("op must be call")]
    OpMustBeCall,
    #[error("contract must be binary")]
    ContractMustBeBinary,
    #[error("function must be binary")]
    FunctionMustBeBinary,
    #[error("invalid module for special meeting")]
    InvalidModuleForSpecial,
    #[error("invalid function for special meeting")]
    InvalidFunctionForSpecial,
    #[error("attached_symbol wrong size")]
    AttachedSymbolWrongSize,
    #[error("attached_amount must be included")]
    AttachedAmountMustBeIncluded,
    #[error("attached_symbol must be included")]
    AttachedSymbolMustBeIncluded,
    #[error("too large")]
    TooLarge,
}

impl TxU {
    pub fn exec_cost_from_len(&self) -> i128 {
        let bytes = self.tx_encoded().len() + 32 + 96;
        amadeus_runtime::consensus::bic::coin::to_cents((1 + bytes / 1024) as i128)
    }

    pub fn exec_cost(&self, _epoch: u32) -> i128 {
        self.exec_cost_from_len()
    }

    pub fn contract_bytes(&self) -> Vec<u8> {
        self.tx.action.contract.clone()
    }
}

pub fn valid_pk(pk: &[u8]) -> bool {
    if pk.len() == 48 && pk == amadeus_runtime::consensus::bic::coin::BURN_ADDRESS {
        return true;
    }
    bls12_381::validate_public_key(pk).is_ok()
}

pub fn known_receivers(txu: &TxU) -> Vec<Vec<u8>> {
    let a = &txu.tx.action;
    let c = a.contract.as_slice();
    let f = a.function.as_slice();
    match (c, f, a.args.as_slice()) {
        (b"Coin", b"transfer", [receiver, _amount, _symbol]) if valid_pk(receiver) => vec![receiver.clone()],
        (b"Epoch", b"slash_trainer", [_epoch, malicious_pk, _sig, _mask_size, _mask]) if valid_pk(malicious_pk) => {
            vec![malicious_pk.clone()]
        }
        _ => vec![],
    }
}

pub fn validate_basic(tx_packed: &[u8], is_special_meeting_block: bool) -> Result<TxU, Error> {
    const DEFAULT_TX_SIZE: usize = 100_000;
    if tx_packed.len() >= DEFAULT_TX_SIZE {
        return Err(Error::TooLarge);
    }

    let txu: TxU = vecpak::from_slice(tx_packed).map_err(|_| Error::WrongType("vecpak_decode"))?;
    let tx_encoded = txu.tx_encoded();

    let h: [u8; 32] = Sha256::digest(&tx_encoded).into();
    if txu.hash.as_slice() != h.as_ref() {
        return Err(Error::InvalidHash);
    }

    bls12_381::verify(&txu.tx.signer, &txu.signature, &h, DST_TX).map_err(|_| Error::InvalidSignature)?;

    if txu.tx.nonce > 99_999_999_999_999_999_999_i128 {
        return Err(Error::NonceTooHigh);
    }

    let a = &txu.tx.action;

    if a.op != "call" {
        return Err(Error::OpMustBeCall);
    }
    if a.contract.is_empty() {
        return Err(Error::ContractMustBeBinary);
    }
    if a.function.is_empty() {
        return Err(Error::FunctionMustBeBinary);
    }

    if is_special_meeting_block {
        if a.contract.as_slice() != b"Epoch" {
            return Err(Error::InvalidModuleForSpecial);
        }
        if a.function.as_slice() != b"slash_trainer" {
            return Err(Error::InvalidFunctionForSpecial);
        }
    }

    if let Some(sym) = &a.attached_symbol {
        if sym.is_empty() || sym.len() > 32 {
            return Err(Error::AttachedSymbolWrongSize);
        }
        if a.attached_amount.is_none() {
            return Err(Error::AttachedAmountMustBeIncluded);
        }
    }
    if a.attached_amount.is_some() && a.attached_symbol.is_none() {
        return Err(Error::AttachedSymbolMustBeIncluded);
    }

    Ok(txu)
}

pub fn validate(tx_packed: &[u8], is_special_meeting_block: bool) -> Result<TxU, Error> {
    validate_basic(tx_packed, is_special_meeting_block)
}

pub fn pack(txu: &TxU) -> Vec<u8> {
    vecpak::to_vec(txu).unwrap_or_default()
}

pub fn build(
    config: &Config,
    contract: &[u8],
    function: &str,
    args: &[Vec<u8>],
    nonce: Option<i64>,
    attached_symbol: Option<&[u8]>,
    attached_amount: Option<&[u8]>,
) -> Vec<u8> {
    let nonce_val: i128 = match nonce {
        Some(n) => n as i128,
        None => crate::utils::misc::get_unix_nanos_now() as i128,
    };

    let action = TxAction {
        op: "call".to_string(),
        contract: contract.to_vec(),
        function: function.as_bytes().to_vec(),
        args: args.to_vec(),
        attached_symbol: attached_symbol.map(|s| s.to_vec()),
        attached_amount: attached_amount.map(|a| a.to_vec()),
    };

    let tx = Tx { signer: config.get_pk(), nonce: nonce_val, action };
    let tx_encoded = vecpak::to_vec(&tx).expect("failed to encode tx");
    let hash: [u8; 32] = Sha256::digest(&tx_encoded).into();
    let signature = bls12_381::sign(&config.get_sk(), &hash, DST_TX).expect("failed to sign tx");

    let txu = TxU { hash: Hash::from(hash), signature, tx };
    pack(&txu)
}

pub fn chain_valid_txu(fabric: &crate::consensus::fabric::Fabric, txu: &TxU) -> bool {
    let chain_nonce = fabric.chain_nonce(txu.tx.signer.as_ref());
    let nonce_valid = match chain_nonce {
        None => true,
        Some(n) => txu.tx.nonce > n as i128,
    };

    let has_balance = txu.exec_cost(crate::consensus::chain_epoch(fabric.db())) as i128
        <= fabric.chain_balance(txu.tx.signer.as_ref());

    let action = &txu.tx.action;
    let mut epoch_sol_valid = true;
    if action.function.as_slice() == b"submit_sol" {
        if let Some(first_arg) = action.args.first() {
            if first_arg.len() >= 4 {
                let sol_epoch = u32::from_le_bytes([first_arg[0], first_arg[1], first_arg[2], first_arg[3]]);
                epoch_sol_valid = crate::consensus::chain_epoch(fabric.db()) as u32 == sol_epoch;
            }
        }
    }

    epoch_sol_valid && nonce_valid && has_balance
}

pub fn chain_valid(fabric: &crate::consensus::fabric::Fabric, tx_input: &[u8]) -> bool {
    match unpack(tx_input) {
        Ok(txu) => chain_valid_txu(fabric, &txu),
        Err(_) => false,
    }
}

pub fn unpack(tx_packed: &[u8]) -> Result<TxU, Error> {
    vecpak::from_slice(tx_packed).map_err(|_| Error::WrongType("vecpak_decode"))
}

pub mod db {
    use amadeus_utils::database::pad_integer_20;
    use amadeus_utils::rocksdb::RocksDb;
    use amadeus_utils::vecpak::{self, Term as VTerm};

    #[derive(Debug, Clone)]
    pub struct TxPointer {
        pub entry_hash: Vec<u8>,
        pub index_start: usize,
        pub index_size: usize,
    }

    impl TxPointer {
        pub fn pack(&self) -> Vec<u8> {
            let term = VTerm::PropList(vec![
                (VTerm::Binary(b"entry_hash".to_vec()), VTerm::Binary(self.entry_hash.clone())),
                (VTerm::Binary(b"index_start".to_vec()), VTerm::VarInt(self.index_start as i128)),
                (VTerm::Binary(b"index_size".to_vec()), VTerm::VarInt(self.index_size as i128)),
            ]);
            vecpak::encode(term)
        }
    }

    pub fn store_tx_pointer(
        tx_hash: &[u8],
        tx_packed: &[u8],
        entry_hash: &[u8],
        entry_packed: &[u8],
        db: &RocksDb,
    ) -> Result<(), amadeus_utils::rocksdb::Error> {
        if let Some(index_start) = entry_packed.windows(tx_packed.len()).position(|window| window == tx_packed) {
            let tx_ptr = TxPointer { entry_hash: entry_hash.to_vec(), index_start, index_size: tx_packed.len() };

            db.put("tx", tx_hash, &tx_ptr.pack())?;
        }

        Ok(())
    }

    pub fn store_tx_nonce_index(
        tx_hash: &[u8],
        signer: &[u8],
        nonce: u64,
        db: &RocksDb,
    ) -> Result<(), amadeus_utils::rocksdb::Error> {
        let key = format!("{}:{}", hex::encode(signer), pad_integer_20(nonce));
        db.put("tx_account_nonce", key.as_bytes(), tx_hash)?;
        Ok(())
    }

    pub fn store_tx_receiver_nonce_index(
        tx_hash: &[u8],
        receiver: &[u8],
        nonce: u64,
        db: &RocksDb,
    ) -> Result<(), amadeus_utils::rocksdb::Error> {
        let key = format!("{}:{}", hex::encode(receiver), pad_integer_20(nonce));
        db.put("tx_receiver_nonce", key.as_bytes(), tx_hash)?;
        Ok(())
    }
}