1use aelf_proto::aelf::{ResourceTokenCharged, TransactionFeeCharged};
2use base64::Engine;
3use prost::Message;
4use serde::{Deserialize, Deserializer, Serialize};
5use serde_json::{Map, Value};
6use std::collections::{BTreeMap, HashMap};
7
8#[derive(Clone, Debug, Default, Serialize, Deserialize)]
10#[serde(rename_all = "PascalCase")]
11pub struct ChainStatusDto {
12 pub chain_id: String,
13 #[serde(default, deserialize_with = "block_height_map_as_default")]
14 pub branches: HashMap<String, i64>,
15 #[serde(default, deserialize_with = "block_height_map_as_default")]
16 pub not_linked_blocks: HashMap<String, i64>,
17 pub longest_chain_height: i64,
18 pub longest_chain_hash: String,
19 pub genesis_block_hash: String,
20 pub genesis_contract_address: String,
21 pub last_irreversible_block_hash: String,
22 pub last_irreversible_block_height: i64,
23 pub best_chain_hash: String,
24 pub best_chain_height: i64,
25}
26
27#[derive(Clone, Debug, Default, Serialize, Deserialize)]
29#[serde(rename_all = "PascalCase")]
30pub struct BlockDto {
31 pub block_hash: String,
32 pub header: BlockHeaderDto,
33 pub body: BlockBodyDto,
34}
35
36#[derive(Clone, Debug, Default, Serialize, Deserialize)]
38#[serde(rename_all = "PascalCase")]
39pub struct BlockHeaderDto {
40 pub height: i64,
41 #[serde(default)]
42 pub previous_block_hash: String,
43}
44
45#[derive(Clone, Debug, Default, Serialize, Deserialize)]
47#[serde(rename_all = "PascalCase")]
48pub struct BlockBodyDto {
49 #[serde(default)]
50 pub transactions: Vec<String>,
51}
52
53#[derive(Clone, Debug, Default, Serialize, Deserialize)]
55#[serde(rename_all = "PascalCase")]
56pub struct TransactionPoolStatusOutput {
57 #[serde(default)]
58 pub queued: i64,
59 #[serde(default)]
60 pub validated: i64,
61}
62
63#[derive(Clone, Debug, Default, Serialize, Deserialize)]
65#[serde(rename_all = "PascalCase")]
66pub struct CreateRawTransactionInput {
67 pub from: String,
68 pub to: String,
69 pub ref_block_number: i64,
70 pub ref_block_hash: String,
71 pub method_name: String,
72 pub params: String,
73}
74
75#[derive(Clone, Debug, Default, Serialize, Deserialize)]
77#[serde(rename_all = "PascalCase")]
78pub struct CreateRawTransactionOutput {
79 pub raw_transaction: String,
80}
81
82#[derive(Clone, Debug, Default, Serialize, Deserialize)]
84#[serde(rename_all = "PascalCase")]
85pub struct ExecuteRawTransactionDto {
86 pub raw_transaction: String,
87 pub signature: String,
88}
89
90#[derive(Clone, Debug, Default, Serialize, Deserialize)]
92#[serde(rename_all = "PascalCase")]
93pub struct SendRawTransactionInput {
94 pub transaction: String,
95 pub signature: String,
96 pub return_transaction: bool,
97}
98
99#[derive(Clone, Debug, Default, Serialize, Deserialize)]
101#[serde(rename_all = "PascalCase")]
102pub struct SendRawTransactionOutput {
103 #[serde(alias = "TransactionID")]
104 pub transaction_id: String,
105 #[serde(default)]
106 pub transaction: TransactionDto,
107}
108
109#[derive(Clone, Debug, Default, Serialize, Deserialize)]
111#[serde(rename_all = "PascalCase")]
112pub struct SendTransactionInput {
113 pub raw_transaction: String,
114}
115
116#[derive(Clone, Debug, Default, Serialize, Deserialize)]
118#[serde(rename_all = "PascalCase")]
119pub struct SendTransactionOutput {
120 #[serde(alias = "TransactionID")]
121 pub transaction_id: String,
122}
123
124#[derive(Clone, Debug, Default, Serialize, Deserialize)]
126#[serde(rename_all = "PascalCase")]
127pub struct SendTransactionsInput {
128 pub raw_transactions: String,
129}
130
131#[derive(Clone, Debug, Default, Serialize, Deserialize)]
133#[serde(rename_all = "PascalCase")]
134pub struct TransactionDto {
135 #[serde(default)]
136 pub from: String,
137 #[serde(default)]
138 pub to: String,
139 #[serde(default)]
140 pub ref_block_number: i64,
141 #[serde(default)]
142 pub ref_block_prefix: String,
143 #[serde(default)]
144 pub method_name: String,
145 #[serde(default)]
146 pub params: String,
147 #[serde(default)]
148 pub signature: String,
149}
150
151#[derive(Clone, Debug, Default, Serialize, Deserialize)]
153#[serde(rename_all = "PascalCase")]
154pub struct LogEventDto {
155 pub address: String,
156 pub name: String,
157 #[serde(default, deserialize_with = "null_vec_as_default")]
158 pub indexed: Vec<String>,
159 #[serde(default, deserialize_with = "null_string_as_default")]
160 pub non_indexed: String,
161}
162
163#[derive(Clone, Debug, Default, Serialize, Deserialize)]
165#[serde(rename_all = "PascalCase")]
166pub struct TransactionResultDto {
167 #[serde(alias = "TransactionID")]
168 pub transaction_id: String,
169 pub status: String,
170 #[serde(default, deserialize_with = "null_vec_as_default")]
171 pub logs: Vec<LogEventDto>,
172 #[serde(default, deserialize_with = "null_string_as_default")]
173 pub bloom: String,
174 #[serde(default, deserialize_with = "null_json_as_default")]
175 pub transaction: serde_json::Value,
176 #[serde(default, deserialize_with = "null_string_as_default")]
177 pub return_value: String,
178 #[serde(default, deserialize_with = "null_i64_as_default")]
179 pub block_number: i64,
180 #[serde(default, deserialize_with = "null_string_as_default")]
181 pub block_hash: String,
182 #[serde(default, deserialize_with = "null_string_as_default")]
183 pub error: String,
184}
185
186impl TransactionResultDto {
187 pub fn get_transaction_fees(&self) -> HashMap<String, i64> {
189 let engine = base64::engine::general_purpose::STANDARD;
190 let mut result = HashMap::new();
191
192 for log in &self.logs {
193 match log.name.as_str() {
194 "TransactionFeeCharged" => {
195 let Ok(bytes) = engine.decode(&log.non_indexed) else {
196 continue;
197 };
198 let Ok(event) = TransactionFeeCharged::decode(bytes.as_slice()) else {
199 continue;
200 };
201 result.insert(event.symbol, event.amount);
202 }
203 "ResourceTokenCharged" => {
204 let Ok(bytes) = engine.decode(&log.non_indexed) else {
205 continue;
206 };
207 let Ok(event) = ResourceTokenCharged::decode(bytes.as_slice()) else {
208 continue;
209 };
210 result.insert(event.symbol, event.amount);
211 }
212 _ => {}
213 }
214 }
215
216 result
217 }
218}
219
220fn null_string_as_default<'de, D>(deserializer: D) -> Result<String, D::Error>
221where
222 D: Deserializer<'de>,
223{
224 let value = Option::<String>::deserialize(deserializer)?;
225 Ok(value.unwrap_or_default())
226}
227
228fn null_vec_as_default<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
229where
230 D: Deserializer<'de>,
231 T: Deserialize<'de>,
232{
233 let value = Option::<Vec<T>>::deserialize(deserializer)?;
234 Ok(value.unwrap_or_default())
235}
236
237fn null_json_as_default<'de, D>(deserializer: D) -> Result<serde_json::Value, D::Error>
238where
239 D: Deserializer<'de>,
240{
241 let value = Option::<serde_json::Value>::deserialize(deserializer)?;
242 Ok(value.unwrap_or(serde_json::Value::Null))
243}
244
245fn null_i64_as_default<'de, D>(deserializer: D) -> Result<i64, D::Error>
246where
247 D: Deserializer<'de>,
248{
249 let value = Option::<i64>::deserialize(deserializer)?;
250 Ok(value.unwrap_or_default())
251}
252
253fn block_height_map_as_default<'de, D>(deserializer: D) -> Result<HashMap<String, i64>, D::Error>
254where
255 D: Deserializer<'de>,
256{
257 let value = Option::<Map<String, Value>>::deserialize(deserializer)?;
258 let mut result = HashMap::new();
259
260 for (key, value) in value.unwrap_or_default() {
261 match value {
262 Value::Number(number) => {
263 let height = number.as_i64().ok_or_else(|| {
264 serde::de::Error::custom(format!(
265 "block height value for '{key}' is not a signed integer"
266 ))
267 })?;
268 result.insert(key, height);
269 }
270 Value::String(text) => {
271 if let Ok(height) = text.parse::<i64>() {
272 result.insert(key, height);
273 continue;
274 }
275
276 let inverted_height = key.parse::<i64>().map_err(|_| {
277 serde::de::Error::custom(format!(
278 "invalid block height map entry '{key}': '{text}'"
279 ))
280 })?;
281 result.insert(text, inverted_height);
282 }
283 other => {
284 return Err(serde::de::Error::custom(format!(
285 "invalid block height map value for '{key}': {other}"
286 )));
287 }
288 }
289 }
290
291 Ok(result)
292}
293
294#[derive(Clone, Debug, Default, Serialize, Deserialize)]
296#[serde(rename_all = "PascalCase")]
297pub struct MerklePathNodeDto {
298 pub hash: String,
299 pub is_left_child_node: bool,
300}
301
302#[derive(Clone, Debug, Default, Serialize, Deserialize)]
304#[serde(rename_all = "PascalCase")]
305pub struct MerklePathDto {
306 #[serde(default)]
307 pub merkle_path_nodes: Vec<MerklePathNodeDto>,
308}
309
310#[derive(Clone, Debug, Default, Serialize, Deserialize)]
312#[serde(rename_all = "PascalCase")]
313pub struct PeerDto {
314 #[serde(default)]
315 pub ip_address: String,
316 #[serde(flatten)]
317 pub extra: BTreeMap<String, serde_json::Value>,
318}
319
320#[derive(Clone, Debug, Default, Serialize, Deserialize)]
322#[serde(rename_all = "PascalCase")]
323pub struct NetworkInfoOutput {
324 #[serde(default)]
325 pub version: String,
326 #[serde(flatten)]
327 pub extra: BTreeMap<String, serde_json::Value>,
328}
329
330#[derive(Clone, Debug, Default, Serialize, Deserialize)]
332#[serde(rename_all = "PascalCase")]
333pub struct TaskQueueInfoDto {
334 #[serde(flatten)]
335 pub extra: BTreeMap<String, serde_json::Value>,
336}
337
338#[derive(Clone, Debug, Default, Serialize, Deserialize)]
340#[serde(rename_all = "PascalCase")]
341pub struct CalculateTransactionFeeInput {
342 pub raw_transaction: String,
343}
344
345#[derive(Clone, Debug, Default, Serialize, Deserialize)]
347#[serde(rename_all = "PascalCase")]
348pub struct CalculateTransactionFeeOutput {
349 pub success: bool,
350 #[serde(default)]
351 pub transaction_fee: HashMap<String, f64>,
352}
353
354#[derive(Clone, Debug, Default, Serialize, Deserialize)]
356pub struct WebAppErrorResponse {
357 pub error: WebAppError,
358}
359
360#[derive(Clone, Debug, Default, Serialize, Deserialize)]
362pub struct WebAppError {
363 pub code: String,
364 pub message: String,
365 #[serde(default)]
366 pub details: Option<String>,
367}
368
369#[cfg(test)]
370mod tests {
371 use super::{ChainStatusDto, LogEventDto, TransactionResultDto};
372 use aelf_proto::aelf::{Address, ResourceTokenCharged, TransactionFeeCharged};
373 use base64::Engine;
374 use prost::Message;
375 use serde_json::json;
376
377 #[test]
378 fn parses_transaction_fee_logs() {
379 let engine = base64::engine::general_purpose::STANDARD;
380 let tx_fee = TransactionFeeCharged {
381 symbol: "ELF".to_owned(),
382 amount: 12_345,
383 };
384 let resource_fee = ResourceTokenCharged {
385 symbol: "CPU".to_owned(),
386 amount: 999,
387 contract_address: Some(Address {
388 value: vec![1_u8; 32],
389 }),
390 };
391 let result = TransactionResultDto {
392 transaction_id: "0x01".to_owned(),
393 status: "MINED".to_owned(),
394 logs: vec![
395 LogEventDto {
396 address: "addr".to_owned(),
397 name: "TransactionFeeCharged".to_owned(),
398 indexed: Vec::new(),
399 non_indexed: engine.encode(tx_fee.encode_to_vec()),
400 },
401 LogEventDto {
402 address: "addr".to_owned(),
403 name: "ResourceTokenCharged".to_owned(),
404 indexed: Vec::new(),
405 non_indexed: engine.encode(resource_fee.encode_to_vec()),
406 },
407 ],
408 bloom: String::new(),
409 transaction: serde_json::Value::Null,
410 return_value: String::new(),
411 block_number: 1,
412 block_hash: "0x02".to_owned(),
413 error: String::new(),
414 };
415
416 let fees = result.get_transaction_fees();
417 assert_eq!(fees.get("ELF"), Some(&12_345));
418 assert_eq!(fees.get("CPU"), Some(&999));
419 }
420
421 #[test]
422 fn parses_chain_status_hash_to_height_maps() {
423 let status: ChainStatusDto = serde_json::from_value(json!({
424 "ChainId": "AELF",
425 "Branches": {
426 "abc": 42
427 },
428 "NotLinkedBlocks": null,
429 "LongestChainHeight": 42,
430 "LongestChainHash": "abc",
431 "GenesisBlockHash": "genesis",
432 "GenesisContractAddress": "contract",
433 "LastIrreversibleBlockHash": "lib",
434 "LastIrreversibleBlockHeight": 40,
435 "BestChainHash": "abc",
436 "BestChainHeight": 42
437 }))
438 .expect("chain status");
439
440 assert_eq!(status.branches.get("abc"), Some(&42));
441 assert!(status.not_linked_blocks.is_empty());
442 }
443
444 #[test]
445 fn parses_chain_status_height_to_hash_maps() {
446 let status: ChainStatusDto = serde_json::from_value(json!({
447 "ChainId": "AELF",
448 "Branches": {
449 "42": "abc"
450 },
451 "NotLinkedBlocks": {
452 "7": "def"
453 },
454 "LongestChainHeight": 42,
455 "LongestChainHash": "abc",
456 "GenesisBlockHash": "genesis",
457 "GenesisContractAddress": "contract",
458 "LastIrreversibleBlockHash": "lib",
459 "LastIrreversibleBlockHeight": 40,
460 "BestChainHash": "abc",
461 "BestChainHeight": 42
462 }))
463 .expect("chain status");
464
465 assert_eq!(status.branches.get("abc"), Some(&42));
466 assert_eq!(status.not_linked_blocks.get("def"), Some(&7));
467 }
468}