1use crate::api_types::*;
2use crate::errors::{Error, ErrorKind, Result};
3use crate::wallet_types::Wallet;
4use crate::wasm::WASM;
5use eosio_client_keys::EOSPublicKey;
6use libabieos_sys::{AbiFiles, ABIEOS, vec_u8_to_hex};
7use reqwest::header::{HeaderValue, CONTENT_TYPE};
8use reqwest::Client;
9use reqwest::StatusCode;
10use serde_json::Value;
11use chrono::{DateTime, Utc};
14
15pub const ERROR_TXN_SET_EXACT_CODE: usize = 3_160_008;
16
17pub struct EOSRPC {
18 pub client: Client,
19 pub host: String,
20 pub abi_abi_js: String,
21 pub transaction_abi_js: String,
22}
23
24impl EOSRPC {
25 pub async fn non_blocking(host: String) -> Result<EOSRPC> {
26 let client = reqwest::Client::new();
27 let abi_f = AbiFiles::get("abi_rv.abi.json").unwrap();
28 let abi_abi_js: String = String::from_utf8(abi_f.as_ref().to_vec())?;
29 let transaction_abi_js: String = String::from_utf8(
30 AbiFiles::get("transaction.abi.json")
31 .unwrap()
32 .as_ref()
33 .to_vec(),
34 )?;
35 Ok(EOSRPC {
36 client,
37 host,
38 abi_abi_js,
39 transaction_abi_js,
40 })
41 }
42
43 pub async fn non_blocking_ex(
44 host: String,
45 abi_abi_js: &str,
46 transaction_abi_js: &str,
47 ) -> EOSRPC {
48 let client = reqwest::Client::new();
49 EOSRPC {
50 client,
51 host,
52 abi_abi_js: abi_abi_js.to_string(),
53 transaction_abi_js: transaction_abi_js.to_string(),
54 }
55 }
56
57 pub async fn non_blocking_req(&self, url: &str, in_json: Value) -> Result<String> {
58 let full_url = [&self.host, url].concat();
59 let req = self.client.post(&full_url).json(&in_json);
60 let response = req.send().await?;
61 let content_type = response.headers().get(CONTENT_TYPE).unwrap();
62 let hv_json = HeaderValue::from_static("application/json");
63 if content_type != hv_json {
64 return Err(ErrorKind::InvalidResponseContentType.into());
65 }
66 let status = response.status();
67
68 if status == StatusCode::OK
69 || status == StatusCode::CREATED
70 || status == StatusCode::ACCEPTED
71 {
72 Ok(response.text().await?)
73 } else {
74 let tx: &str = &response.text().await?;
75 let error_reply: serde_json::Result<ErrorReply> = serde_json::from_str(tx);
76 match error_reply {
77 Ok (e) => {
78 Err(ErrorKind::InvalidResponseStatus(e.error).into())
79 },
80 _ => {
81 Err(ErrorKind::InvalidResponseErr(tx.to_string()).into())
82 }
83 }
84
85
86 }
87 }
88
89 pub async fn get_account(&self, account_name: &str) -> Result<GetAccount> {
90 let value = serde_json::json!({ "account_name": account_name });
91 let res = self
92 .non_blocking_req("/v1/chain/get_account", value)
93 .await?;
94 let ga: GetAccount = serde_json::from_str(&res).unwrap();
95 Ok(ga)
96 }
97 pub async fn get_abi(&self, account_name: &str) -> Result<GetAbi> {
98 let value = serde_json::json!({ "account_name": account_name });
99 let res = self.non_blocking_req("/v1/chain/get_abi", value).await?;
100 let ga: GetAbi = serde_json::from_str(&res).unwrap();
101 Ok(ga)
102 }
103
104 pub async fn get_info(&self) -> Result<GetInfo> {
105 let value = serde_json::json!({});
106 let res = self.non_blocking_req("/v1/chain/get_info", value).await?;
107 let ga: GetInfo = serde_json::from_str(&res).unwrap();
108 Ok(ga)
109 }
110
111 pub async fn get_code_hash(&self, account_name: &str) -> Result<GetCodeHash> {
112 let value = serde_json::json!({ "account_name": account_name });
113 let res = self
114 .non_blocking_req("/v1/chain/get_code_hash", value)
115 .await?;
116 let gc: GetCodeHash = serde_json::from_str(&res)?;
117 Ok(gc)
118 }
119
120 pub async fn get_raw_abi(&self, account_name: &str) -> Result<GetRawABI> {
121 let value = serde_json::json!({ "account_name": account_name });
122
123 let res = self
124 .non_blocking_req("/v1/chain/get_raw_abi", value)
125 .await?;
126 let gr: GetRawABI = serde_json::from_str(&res)?;
127 Ok(gr)
128 }
129 pub async fn get_block_num(&self, block_num: usize) -> Result<GetBlock> {
130 let value = serde_json::json!({ "block_num_or_id": block_num });
131
132 let res = self.non_blocking_req("/v1/chain/get_block", value).await?;
133 let gb: GetBlock = serde_json::from_str(&res)?;
134 Ok(gb)
135 }
136 pub async fn get_block_id(&self, block_id: &str) -> Result<GetBlock> {
137 let value = serde_json::json!({ "block_num_or_id": block_id });
138
139 let res = self.non_blocking_req("/v1/chain/get_block", value).await?;
140 let gb: GetBlock = serde_json::from_str(&res)?;
141 Ok(gb)
142 }
143
144 pub async fn get_required_keys(
145 &self,
146 transaction: &TransactionIn,
147 keys: Vec<EOSPublicKey>,
148 ) -> Result<RequiredKeys> {
149 let mut key_str: Vec<String> = vec![];
150 for key in keys {
151 let x = key.to_eos_string()?;
152 key_str.push(x);
153 }
154
155 let value = serde_json::json!({ "transaction": transaction, "available_keys":key_str});
156 let res = self
157 .non_blocking_req("/v1/chain/get_required_keys", value)
158 .await?;
159 let rk: RequiredKeys = serde_json::from_str(&res).unwrap();
160 Ok(rk)
161 }
162
163 pub async fn get_abi_from_account(
164 &self,
165 abieos_eosio: &ABIEOS,
166 account_name: &str,
167 ) -> Result<ABIEOS> {
168 let rawabi_r = self.get_raw_abi(account_name).await;
169 let account_abi = rawabi_r?.decode_abi()?;
170
171 let account_abi_json = abieos_eosio.bin_to_json("eosio", "abi_def", &account_abi)?;
172 Ok(ABIEOS::new_with_abi(account_name, &account_abi_json)?)
173 }
174
175 pub async fn push_transaction(
176 &self,
177 abieos: &ABIEOS,
178 wallet: &Wallet,
179 actions: Vec<ActionIn>,
180 ref_block: &str,
181 exp_time: DateTime<Utc>,
182 ) -> Result<TransactionResponse> {
183 let ti = TransactionIn::simple(actions, ref_block, exp_time)?;
184
185 let trx_json = serde_json::to_string(&ti)?;
186 let trx = abieos.json_to_hex("eosio", "transaction", &trx_json)?;
187
188 let pubkeys = wallet.keys().await?;
189 let required_keys = self.get_required_keys(&ti, pubkeys).await?;
190 let eospubs: Vec<EOSPublicKey> =
191 EOSPublicKey::from_eos_strings(&required_keys.required_keys)?;
192
193 let signed_transaction = wallet.sign_transaction(ti, eospubs).await?;
194 let pti = PackedTransactionIn {
195 signatures: signed_transaction.signatures,
196 compression: "none".to_string(),
197 packed_context_free_data: "".to_string(),
198 packed_trx: trx.to_string(),
199 };
200
201 let in_val = serde_json::json!(pti);
202 let res = self
203 .non_blocking_req("/v1/chain/push_transaction", in_val)
204 .await?;
205 let tr: TransactionResponse = serde_json::from_str(&res).unwrap();
206 Ok(tr)
207 }
208
209 pub async fn get_table_rows(
210 &self,
211 code: &str,
212 scope: &str,
213 table: &str,
214 table_key: &str,
215 lower_bound: &str,
216 upper_bound: &str,
217 limit: usize,
218 key_type: &str,
219 index_position: &str,
220 encode_type: &str,
221 reverse: bool,
222 show_payer: bool,
223 ) -> Result<GetTableRows> {
224 let in_j = GetTableRowsIn {
225 json: false,
226 code: code.parse().unwrap(),
227 scope: scope.parse().unwrap(),
228 table: table.parse().unwrap(),
229 table_key: table_key.parse().unwrap(),
230 lower_bound: lower_bound.parse().unwrap(),
231 upper_bound: upper_bound.parse().unwrap(),
232 limit,
233 key_type: key_type.parse().unwrap(),
234 index_position: index_position.parse().unwrap(),
235 encode_type: encode_type.parse().unwrap(),
236 reverse,
237 show_payer,
238 };
239 let in_val = serde_json::json!(in_j);
240 let res = self
241 .non_blocking_req("/v1/chain/get_table_rows", in_val)
242 .await?;
243 let tr: GetTableRows = serde_json::from_str(&res).unwrap();
244 Ok(tr)
245 }
246 pub async fn get_table_by_scope(
247 &self,
248 code: &str,
249 table: &str,
250 lower_bound: &str,
251 upper_bound: &str,
252 limit: usize,
253 reverse: bool,
254 ) -> Result<GetTableByScope> {
255 let pti = GetTableByScopeIn {
256 code: code.parse().unwrap(),
257 table: table.parse().unwrap(),
258 lower_bound: lower_bound.parse().unwrap(),
259 upper_bound: upper_bound.parse().unwrap(),
260 limit,
261 reverse,
262 };
263 let in_val = serde_json::json!(pti);
264 let res = self
265 .non_blocking_req("/v1/chain/get_table_by_scope", in_val)
266 .await?;
267 let tr: GetTableByScope = serde_json::from_str(&res).unwrap();
268 Ok(tr)
269 }
270}
271
272pub fn create_setcode_action(acct_abieos: &ABIEOS, name: &str, code: &WASM) -> Result<ActionIn> {
273 let auth = AuthorizationIn {
274 permission: "active".to_string(),
275 actor: String::from(name),
276 };
277 let v_auth: Vec<AuthorizationIn> = vec![auth];
278 let data = ActionSetcodeData {
279 account: String::from(name),
280 vmtype: 0,
281 vmversion: 0,
282 code: vec_u8_to_hex(&code.code)?,
283 }
284 .to_hex(acct_abieos)?;
285
286 Ok(ActionIn {
287 name: "setcode".to_string(),
288 account: "eosio".to_string(),
289 authorization: v_auth,
290 data,
291 })
292}
293
294pub fn create_setcode_clear_action(acct_abieos: &ABIEOS, name: &str) -> Result<ActionIn> {
295 let auth = AuthorizationIn {
296 permission: "active".to_string(),
297 actor: String::from(name),
298 };
299 let v_auth: Vec<AuthorizationIn> = vec![auth];
300 let data = ActionSetcodeData {
301 account: String::from(name),
302 vmtype: 0,
303 vmversion: 0,
304 code: vec_u8_to_hex(&WASM::dummy())?,
305 }
306 .to_hex(acct_abieos)?;
307
308 Ok(ActionIn {
309 name: "setcode".to_string(),
310 account: "eosio".to_string(),
311 authorization: v_auth,
312 data,
313 })
314}
315
316pub struct AbiTrio {
317 pub sys_abi: ABIEOS,
318 pub txn_abi: ABIEOS,
319 pub acct_abi: ABIEOS,
320}
321
322impl AbiTrio {
323 pub async fn create(sys_name: &str, sys_acct_name: &str, eos: &EOSRPC) -> Result<AbiTrio> {
324 let sys_abi = ABIEOS::new_with_abi(sys_name, &eos.abi_abi_js)?;
325 let txn_abi: ABIEOS =
326 ABIEOS::new_with_abi(sys_name, &eos.transaction_abi_js).map_err(|e| {
327 sys_abi.destroy();
328 Error::with_chain(e, "AbiTrio_txn")
329 })?;
330 let acct_abi: ABIEOS = eos
331 .get_abi_from_account(&sys_abi, sys_acct_name)
332 .await
333 .map_err(|e| {
334 sys_abi.destroy();
335 txn_abi.destroy();
336 Error::with_chain(e, "AbiTrio_act")
337 })?;
338
339 Ok(AbiTrio {
340 sys_abi,
341 txn_abi,
342 acct_abi,
343 })
344 }
345 pub fn destroy(&self) {
346 self.acct_abi.destroy();
347 self.txn_abi.destroy();
348 self.sys_abi.destroy()
349 }
350}
351
352pub fn create_setabi_action(
353 sys_abieos: &ABIEOS,
354 acct_abieos: &ABIEOS,
355 name: &str,
356 abi: &str,
357) -> Result<ActionIn> {
358 let auth = AuthorizationIn {
359 permission: "active".to_string(),
360 actor: String::from(name),
361 };
362 let v_auth: Vec<AuthorizationIn> = vec![auth];
363 let abi_hex = sys_abieos.json_to_hex("eosio", "abi_def", abi)?;
364 let data = ActionSetData {
366 account: String::from(name),
367 abi: String::from(abi_hex),
368 }
369 .to_hex(acct_abieos)?;
370
371 Ok(ActionIn {
372 name: "setabi".to_string(),
373 account: "eosio".to_string(),
374 data,
375 authorization: v_auth,
376 })
377}
378
379#[cfg(test)]
380mod test {
381 use super::*;
382
383 use crate::wallet_types::{get_wallet_pass, EOSIO_CHAIN_ID};
385 use chrono::{Duration, NaiveDateTime};
386 use std::fs;
387
388 const TEST_HOST: &str = "https://api.testnet.eos.io";
391 const TEST_KEOSD: &str = "http://127.0.0.1:3888";
392
393 const TEST_WALLET_NAME: &str = "default";
394 const TEST_ACCOUNT_NAME: &str = "fwonhjnefmps";
395 const TEST_ACCOUNT_NO_ABI: &str = "tafoacvsqlmw";
396
397 #[tokio::test]
401 async fn non_blocking_req_test() -> Result<()> {
402 let eos = EOSRPC::non_blocking(String::from(TEST_HOST)).await?;
403 let _ga = eos.get_account("eosio").await?;
404
405 let _abi = eos.get_abi("eosio").await?;
406 Ok(())
407 }
408
409 #[tokio::test]
410 async fn non_blocking_get_info() -> Result<()> {
411 let eos = EOSRPC::non_blocking(String::from(TEST_HOST)).await?;
412 let _gi = eos.get_info().await?;
413 Ok(())
414 }
415
416 #[test]
417 fn datetime_format() {
418 let s = "2020-05-16T05:12:03";
419 const FORMAT: &'static str = "%Y-%m-%dT%H:%M:%S";
420 match NaiveDateTime::parse_from_str(s, FORMAT) {
422 Err(_e) => {
423 eprintln!("{:#?}", _e);
424 assert!(false)
425 }
426 Ok(_dt) => assert!(true),
427 }
428 }
429
430 #[tokio::test]
431 async fn non_blocking_get_required_keys() -> Result<()> {
432 let eos = EOSRPC::non_blocking(String::from(TEST_HOST)).await?;
433 let keys = vec![
434 EOSPublicKey::from_eos_string("EOS6zUgp7uAV1pCTXZMGJyH3dLUSWJUkZWGA9WpWxyP2pCT3mAkNX")
435 .unwrap(),
436 EOSPublicKey::from_eos_string("EOS7ctUUZhtCGHnxUnh4Rg5eethj3qNS5S9fijyLMKgRsBLh8eMBB")
437 .unwrap(),
438 EOSPublicKey::from_eos_string("EOS8fdsPr1aKsmszNHeY4RrgupbabNQ5nmLgQWMEkTn2dENrPbRgP")
439 .unwrap(),
440 ];
441 let gi: GetInfo = eos.get_info().await?;
442 let exp_time = gi.set_exp_time(Duration::seconds(1800));
443
444 let wasm = WASM::read_file("test/good-2.wasm")?;
445
446 let name = TEST_ACCOUNT_NAME;
447
448 let abi_trio = AbiTrio::create("eosio", "eosio", &eos).await?;
449 let action_r = create_setcode_action(&abi_trio.acct_abi, &name, &wasm);
450 abi_trio.destroy();
451
452 let ti = TransactionIn::simple(vec![action_r?], &gi.last_irreversible_block_id, exp_time)?;
453 let rk = eos.get_required_keys(&ti, keys).await?;
454 assert!(rk.required_keys.len() > 0);
455 let k = &rk.required_keys[0];
456
457 if k == "EOS7ctUUZhtCGHnxUnh4Rg5eethj3qNS5S9fijyLMKgRsBLh8eMBB" {
459 ()
460 } else {
461 assert_eq!(k, "EOS8fdsPr1aKsmszNHeY4RrgupbabNQ5nmLgQWMEkTn2dENrPbRgP");
462 }
463 Ok(())
464 }
465
466 #[tokio::test]
471 async fn non_blocking_table_rows() -> Result<()> {
472 let eos = EOSRPC::non_blocking(String::from(TEST_HOST)).await?;
473 let _r = eos
474 .get_table_rows(
475 TEST_ACCOUNT_NAME,
476 "tictactoe",
477 "games",
478 "",
479 "",
480 "",
481 10,
482 "",
483 "",
484 "dec",
485 false,
486 true,
487 )
488 .await;
489 Ok(())
490 }
491
492 #[tokio::test]
497 async fn non_blocking_table_by_scope() -> Result<()> {
498 let eos = EOSRPC::non_blocking(String::from(TEST_HOST)).await?;
499 let _r = eos
500 .get_table_by_scope("eosio.token", "", TEST_ACCOUNT_NAME, "", 10, false)
501 .await;
502 Ok(())
503 }
504
505 #[tokio::test]
506 async fn blocking_push_txn() -> Result<()> {
507 let eos = EOSRPC::non_blocking(String::from(TEST_HOST)).await?;
508 let wallet = Wallet::create_with_chain_id(
509 EOSRPC::non_blocking(String::from(TEST_KEOSD)).await?,
510 EOSIO_CHAIN_ID,
511 );
512 let wallet_pass = get_wallet_pass()?;
513 wallet.unlock(&TEST_WALLET_NAME, &wallet_pass).await?;
514 let wasm = WASM::read_file("test/good-2.wasm")?;
515 let wasm_abi = fs::read_to_string("test/good-2.abi")?;
516
517 let name = TEST_ACCOUNT_NAME;
518
519 let gi: GetInfo = eos.get_info().await?;
520 let exp_time = gi.set_exp_time(Duration::seconds(1800));
521 let abi_trio = AbiTrio::create("eosio", "eosio", &eos).await?;
522
523 let action_clear = create_setcode_clear_action(&abi_trio.acct_abi, &name).map_err(|e| {
524 abi_trio.destroy();
525 Error::with_chain(e, "blocking_push_txn/create_setcode_clear_action")
526 })?;
527
528 let _res_clear_int = eos
529 .push_transaction(
530 &abi_trio.txn_abi,
531 &wallet,
532 vec![action_clear],
533 &gi.head_block_id,
534 exp_time,
535 )
536 .await
537 .map_err(|e| {
538 Error::with_chain(e, "blocking_push_txn/push_transaction(clear)")
540 });
541 if _res_clear_int.is_err() {
542 eprintln!(
543 "Ignoring error for clearing contract - {:#?}",
544 _res_clear_int.err().unwrap()
545 )
546 }
547
548 let action = create_setcode_action(&abi_trio.acct_abi, &name, &wasm).map_err(|e| {
549 abi_trio.destroy();
550 Error::with_chain(e, "blocking_push_txn/create_setcode_action")
551 })?;
552 let action_abi =
553 create_setabi_action(&abi_trio.sys_abi, &abi_trio.acct_abi, &name, &wasm_abi).map_err(
554 |e| {
555 abi_trio.destroy();
556 Error::with_chain(e, "blocking_push_txn/create_setabi_action")
557 },
558 )?;
559
560 let _res_int = eos
561 .push_transaction(
562 &abi_trio.txn_abi,
563 &wallet,
564 vec![action, action_abi],
565 &gi.head_block_id,
566 exp_time,
567 )
568 .await
569 .map_err(|e| {
570 abi_trio.destroy();
571 Error::with_chain(e, "blocking_push_txn/push_transaction(set-code/abi)")
572 })?;
573
574 abi_trio.destroy();
575 Ok(())
576 }
577
578 #[tokio::test]
579 async fn non_blocking_get_raw_abi() -> Result<()> {
580 let eos = EOSRPC::non_blocking(String::from(TEST_HOST)).await?;
581 let _res = eos.get_raw_abi("eosio").await?;
582
583 Ok(())
584 }
585
586 #[tokio::test]
587 async fn non_blocking_getsetabi() -> Result<()> {
588 let eos = EOSRPC::non_blocking(String::from(TEST_HOST)).await?;
589 let wasm_abi = fs::read_to_string("test/good-2.abi")?;
590 let wallet = Wallet::create_with_chain_id(
591 EOSRPC::non_blocking(String::from(TEST_KEOSD)).await?,
592 EOSIO_CHAIN_ID,
593 );
594 let wallet_pass = get_wallet_pass()?;
595 wallet.unlock(&TEST_WALLET_NAME, &wallet_pass).await?;
596 let gi = eos.get_info().await?;
597 let exp_time = gi.set_exp_time(Duration::seconds(1800));
598
599 let name = TEST_ACCOUNT_NAME;
600 let trio = AbiTrio::create("eosio", "eosio", &eos).await?;
601
602 let action_abi = create_setabi_action(&trio.sys_abi, &trio.acct_abi, &name, &wasm_abi)
603 .map_err(|e| {
604 &trio.destroy();
605 Error::with_chain(e, "create_setabi_action")
606 })?;
607
608 let _tr = eos
609 .push_transaction(
610 &trio.txn_abi,
611 &wallet,
612 vec![action_abi],
613 &gi.head_block_id,
614 exp_time,
615 )
616 .await
617 .map_err(|e| {
618 trio.destroy();
619 Error::with_chain(e, "push_transaction")
620 })?;
621 trio.destroy();
622
623 let _get_abi = eos.get_abi(name).await?;
625
626 Ok(())
627 }
628
629 #[tokio::test]
630 async fn non_block_getabi() -> Result<()> {
631 let eos = EOSRPC::non_blocking(String::from(TEST_HOST)).await?;
632 let get_abi = eos.get_abi(TEST_ACCOUNT_NO_ABI).await?;
633 assert!(get_abi.abi.is_none());
634 let get_abi = eos.get_abi(TEST_ACCOUNT_NAME).await?;
635 assert!(get_abi.abi.is_some());
636 Ok(())
637 }
638 #[tokio::test]
639 async fn non_block_getblock() -> Result<()> {
640 let eos = EOSRPC::non_blocking(String::from(TEST_HOST)).await?;
641 let block = eos.get_block_num(1).await?;
642 let block2 = eos.get_block_id(&block.id).await?;
643 assert_eq!(block.block_num, block2.block_num);
644 Ok(())
645 }
646}