amadeus_node/consensus/doms/
tx.rs1use 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}