Skip to main content

amadeus_node/consensus/doms/
tx.rs

1use crate::config::Config;
2use crate::consensus::DST_TX;
3use crate::utils::bls12_381;
4use crate::utils::{Hash, PublicKey, Signature};
5use amadeus_utils::vecpak;
6use sha2::{Digest, Sha256};
7
8mod args_serde {
9    use serde::{Deserialize, Deserializer, Serialize, Serializer};
10    pub fn serialize<S: Serializer>(args: &[Vec<u8>], ser: S) -> Result<S::Ok, S::Error> {
11        let v: Vec<serde_bytes::ByteBuf> = args.iter().map(|a| serde_bytes::ByteBuf::from(a.clone())).collect();
12        v.serialize(ser)
13    }
14    pub fn deserialize<'de, D: Deserializer<'de>>(de: D) -> Result<Vec<Vec<u8>>, D::Error> {
15        let v: Vec<serde_bytes::ByteBuf> = Deserialize::deserialize(de)?;
16        Ok(v.into_iter().map(|b| b.into_vec()).collect())
17    }
18}
19
20mod serde_bytes_option {
21    use serde::{Deserialize, Deserializer, Serialize, Serializer};
22    pub fn serialize<S: Serializer>(opt: &Option<Vec<u8>>, ser: S) -> Result<S::Ok, S::Error> {
23        match opt {
24            Some(v) => serde_bytes::ByteBuf::from(v.clone()).serialize(ser),
25            None => ser.serialize_none(),
26        }
27    }
28    pub fn deserialize<'de, D: Deserializer<'de>>(de: D) -> Result<Option<Vec<u8>>, D::Error> {
29        let opt: Option<serde_bytes::ByteBuf> = Deserialize::deserialize(de)?;
30        Ok(opt.map(|b| b.into_vec()))
31    }
32}
33
34#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
35pub struct TxAction {
36    #[serde(with = "args_serde")]
37    pub args: Vec<Vec<u8>>,
38    #[serde(with = "serde_bytes")]
39    pub contract: Vec<u8>,
40    #[serde(with = "serde_bytes")]
41    pub function: Vec<u8>,
42    pub op: String,
43    #[serde(skip_serializing_if = "Option::is_none", with = "serde_bytes_option", default)]
44    pub attached_symbol: Option<Vec<u8>>,
45    #[serde(skip_serializing_if = "Option::is_none", with = "serde_bytes_option", default)]
46    pub attached_amount: Option<Vec<u8>>,
47}
48
49#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
50pub struct Tx {
51    pub action: TxAction,
52    pub nonce: i128,
53    pub signer: PublicKey,
54}
55
56#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
57pub struct TxU {
58    pub hash: Hash,
59    pub signature: Signature,
60    pub tx: Tx,
61}
62
63pub type EntryTxAction = TxAction;
64pub type EntryTxInner = Tx;
65pub type EntryTx = TxU;
66
67impl TxU {
68    pub fn tx_encoded(&self) -> Vec<u8> {
69        vecpak::to_vec(&self.tx).unwrap_or_default()
70    }
71}
72
73#[derive(Debug, thiserror::Error)]
74pub enum Error {
75    #[error("wrong term type: {0}")]
76    WrongType(&'static str),
77    #[error("invalid hash")]
78    InvalidHash,
79    #[error("invalid signature")]
80    InvalidSignature,
81    #[error("nonce too high")]
82    NonceTooHigh,
83    #[error("op must be call")]
84    OpMustBeCall,
85    #[error("contract must be binary")]
86    ContractMustBeBinary,
87    #[error("function must be binary")]
88    FunctionMustBeBinary,
89    #[error("invalid module for special meeting")]
90    InvalidModuleForSpecial,
91    #[error("invalid function for special meeting")]
92    InvalidFunctionForSpecial,
93    #[error("attached_symbol wrong size")]
94    AttachedSymbolWrongSize,
95    #[error("attached_amount must be included")]
96    AttachedAmountMustBeIncluded,
97    #[error("attached_symbol must be included")]
98    AttachedSymbolMustBeIncluded,
99    #[error("too large")]
100    TooLarge,
101}
102
103impl TxU {
104    pub fn exec_cost_from_len(&self) -> i128 {
105        let bytes = self.tx_encoded().len() + 32 + 96;
106        amadeus_runtime::consensus::bic::coin::to_cents((1 + bytes / 1024) as i128)
107    }
108
109    pub fn exec_cost(&self, _epoch: u32) -> i128 {
110        self.exec_cost_from_len()
111    }
112
113    pub fn contract_bytes(&self) -> Vec<u8> {
114        self.tx.action.contract.clone()
115    }
116}
117
118pub fn valid_pk(pk: &[u8]) -> bool {
119    if pk.len() == 48 && pk == amadeus_runtime::consensus::bic::coin::BURN_ADDRESS {
120        return true;
121    }
122    bls12_381::validate_public_key(pk).is_ok()
123}
124
125pub fn known_receivers(txu: &TxU) -> Vec<Vec<u8>> {
126    let a = &txu.tx.action;
127    let c = a.contract.as_slice();
128    let f = a.function.as_slice();
129    match (c, f, a.args.as_slice()) {
130        (b"Coin", b"transfer", [receiver, _amount, _symbol]) if valid_pk(receiver) => vec![receiver.clone()],
131        (b"Epoch", b"slash_trainer", [_epoch, malicious_pk, _sig, _mask_size, _mask]) if valid_pk(malicious_pk) => {
132            vec![malicious_pk.clone()]
133        }
134        _ => vec![],
135    }
136}
137
138pub fn validate_basic(tx_packed: &[u8], is_special_meeting_block: bool) -> Result<TxU, Error> {
139    const DEFAULT_TX_SIZE: usize = 100_000;
140    if tx_packed.len() >= DEFAULT_TX_SIZE {
141        return Err(Error::TooLarge);
142    }
143
144    let txu: TxU = vecpak::from_slice(tx_packed).map_err(|_| Error::WrongType("vecpak_decode"))?;
145    let tx_encoded = txu.tx_encoded();
146
147    let h: [u8; 32] = Sha256::digest(&tx_encoded).into();
148    if txu.hash.as_slice() != h.as_ref() {
149        return Err(Error::InvalidHash);
150    }
151
152    bls12_381::verify(&txu.tx.signer, &txu.signature, &h, DST_TX).map_err(|_| Error::InvalidSignature)?;
153
154    if txu.tx.nonce > 99_999_999_999_999_999_999_i128 {
155        return Err(Error::NonceTooHigh);
156    }
157
158    let a = &txu.tx.action;
159
160    if a.op != "call" {
161        return Err(Error::OpMustBeCall);
162    }
163    if a.contract.is_empty() {
164        return Err(Error::ContractMustBeBinary);
165    }
166    if a.function.is_empty() {
167        return Err(Error::FunctionMustBeBinary);
168    }
169
170    if is_special_meeting_block {
171        if a.contract.as_slice() != b"Epoch" {
172            return Err(Error::InvalidModuleForSpecial);
173        }
174        if a.function.as_slice() != b"slash_trainer" {
175            return Err(Error::InvalidFunctionForSpecial);
176        }
177    }
178
179    if let Some(sym) = &a.attached_symbol {
180        if sym.is_empty() || sym.len() > 32 {
181            return Err(Error::AttachedSymbolWrongSize);
182        }
183        if a.attached_amount.is_none() {
184            return Err(Error::AttachedAmountMustBeIncluded);
185        }
186    }
187    if a.attached_amount.is_some() && a.attached_symbol.is_none() {
188        return Err(Error::AttachedSymbolMustBeIncluded);
189    }
190
191    Ok(txu)
192}
193
194pub fn validate(tx_packed: &[u8], is_special_meeting_block: bool) -> Result<TxU, Error> {
195    validate_basic(tx_packed, is_special_meeting_block)
196}
197
198pub fn pack(txu: &TxU) -> Vec<u8> {
199    vecpak::to_vec(txu).unwrap_or_default()
200}
201
202pub fn build(
203    config: &Config,
204    contract: &[u8],
205    function: &str,
206    args: &[Vec<u8>],
207    nonce: Option<i64>,
208    attached_symbol: Option<&[u8]>,
209    attached_amount: Option<&[u8]>,
210) -> Vec<u8> {
211    let nonce_val: i128 = match nonce {
212        Some(n) => n as i128,
213        None => crate::utils::misc::get_unix_nanos_now() as i128,
214    };
215
216    let action = TxAction {
217        op: "call".to_string(),
218        contract: contract.to_vec(),
219        function: function.as_bytes().to_vec(),
220        args: args.to_vec(),
221        attached_symbol: attached_symbol.map(|s| s.to_vec()),
222        attached_amount: attached_amount.map(|a| a.to_vec()),
223    };
224
225    let tx = Tx { signer: config.get_pk(), nonce: nonce_val, action };
226    let tx_encoded = vecpak::to_vec(&tx).expect("failed to encode tx");
227    let hash: [u8; 32] = Sha256::digest(&tx_encoded).into();
228    let signature = bls12_381::sign(&config.get_sk(), &hash, DST_TX).expect("failed to sign tx");
229
230    let txu = TxU { hash: Hash::from(hash), signature, tx };
231    pack(&txu)
232}
233
234pub fn chain_valid_txu(fabric: &crate::consensus::fabric::Fabric, txu: &TxU) -> bool {
235    let chain_nonce = fabric.chain_nonce(txu.tx.signer.as_ref());
236    let nonce_valid = match chain_nonce {
237        None => true,
238        Some(n) => txu.tx.nonce > n as i128,
239    };
240
241    let has_balance = txu.exec_cost(crate::consensus::chain_epoch(fabric.db())) as i128
242        <= fabric.chain_balance(txu.tx.signer.as_ref());
243
244    let action = &txu.tx.action;
245    let mut epoch_sol_valid = true;
246    if action.function.as_slice() == b"submit_sol" {
247        if let Some(first_arg) = action.args.first() {
248            if first_arg.len() >= 4 {
249                let sol_epoch = u32::from_le_bytes([first_arg[0], first_arg[1], first_arg[2], first_arg[3]]);
250                epoch_sol_valid = crate::consensus::chain_epoch(fabric.db()) as u32 == sol_epoch;
251            }
252        }
253    }
254
255    epoch_sol_valid && nonce_valid && has_balance
256}
257
258pub fn chain_valid(fabric: &crate::consensus::fabric::Fabric, tx_input: &[u8]) -> bool {
259    match unpack(tx_input) {
260        Ok(txu) => chain_valid_txu(fabric, &txu),
261        Err(_) => false,
262    }
263}
264
265pub fn unpack(tx_packed: &[u8]) -> Result<TxU, Error> {
266    vecpak::from_slice(tx_packed).map_err(|_| Error::WrongType("vecpak_decode"))
267}
268
269pub mod db {
270    use amadeus_utils::database::pad_integer_20;
271    use amadeus_utils::rocksdb::RocksDb;
272    use amadeus_utils::vecpak::{self, Term as VTerm};
273
274    #[derive(Debug, Clone)]
275    pub struct TxPointer {
276        pub entry_hash: Vec<u8>,
277        pub index_start: usize,
278        pub index_size: usize,
279    }
280
281    impl TxPointer {
282        pub fn pack(&self) -> Vec<u8> {
283            let term = VTerm::PropList(vec![
284                (VTerm::Binary(b"entry_hash".to_vec()), VTerm::Binary(self.entry_hash.clone())),
285                (VTerm::Binary(b"index_start".to_vec()), VTerm::VarInt(self.index_start as i128)),
286                (VTerm::Binary(b"index_size".to_vec()), VTerm::VarInt(self.index_size as i128)),
287            ]);
288            vecpak::encode(term)
289        }
290    }
291
292    pub fn store_tx_pointer(
293        tx_hash: &[u8],
294        tx_packed: &[u8],
295        entry_hash: &[u8],
296        entry_packed: &[u8],
297        db: &RocksDb,
298    ) -> Result<(), amadeus_utils::rocksdb::Error> {
299        if let Some(index_start) = entry_packed.windows(tx_packed.len()).position(|window| window == tx_packed) {
300            let tx_ptr = TxPointer { entry_hash: entry_hash.to_vec(), index_start, index_size: tx_packed.len() };
301
302            db.put("tx", tx_hash, &tx_ptr.pack())?;
303        }
304
305        Ok(())
306    }
307
308    pub fn store_tx_nonce_index(
309        tx_hash: &[u8],
310        signer: &[u8],
311        nonce: u64,
312        db: &RocksDb,
313    ) -> Result<(), amadeus_utils::rocksdb::Error> {
314        let key = format!("{}:{}", hex::encode(signer), pad_integer_20(nonce));
315        db.put("tx_account_nonce", key.as_bytes(), tx_hash)?;
316        Ok(())
317    }
318
319    pub fn store_tx_receiver_nonce_index(
320        tx_hash: &[u8],
321        receiver: &[u8],
322        nonce: u64,
323        db: &RocksDb,
324    ) -> Result<(), amadeus_utils::rocksdb::Error> {
325        let key = format!("{}:{}", hex::encode(receiver), pad_integer_20(nonce));
326        db.put("tx_receiver_nonce", key.as_bytes(), tx_hash)?;
327        Ok(())
328    }
329}