eosio_chaintester/
client.rs

1use std::fmt;
2use std::panic;
3
4use std::{fs};
5use std::{thread, time::Duration};
6use std::ops::{Deref, DerefMut};
7
8use serde_json::{Value};
9
10use thrift::protocol::{TBinaryInputProtocol, TBinaryOutputProtocol};
11use thrift::transport::{
12    ReadHalf, TBufferedReadTransport, TBufferedWriteTransport, TIoChannel, TTcpChannel, WriteHalf,
13};
14
15use crate::interfaces::{IPCChainTesterSyncClient, TIPCChainTesterSyncClient, ApplySyncClient, Action};
16
17type ClientInputProtocol = TBinaryInputProtocol<TBufferedReadTransport<ReadHalf<TTcpChannel>>>;
18type ClientOutputProtocol = TBinaryOutputProtocol<TBufferedWriteTransport<WriteHalf<TTcpChannel>>>;
19
20
21use std::convert::{From, Into};
22
23use lazy_static::lazy_static; // 1.4.0
24use std::sync::{
25    Mutex,
26    MutexGuard
27};
28
29
30pub struct ChainTesterError {
31    pub json: Option<Value>,
32    pub error_string: Option<String>,
33}
34
35pub enum JsonKeyType {
36    ArrayIndex(usize),
37    MapKey(String)
38}
39
40impl From<usize> for JsonKeyType {
41    fn from(value: usize) -> Self {
42        JsonKeyType::ArrayIndex(value)
43    }
44}
45
46impl From<String> for JsonKeyType {
47    fn from(value: String) -> Self {
48        JsonKeyType::MapKey(value)
49    }
50}
51
52impl ChainTesterError {
53    pub fn get_err(&self) -> Option<String> {
54        let value = &self.json.as_ref().unwrap()["except"]["stack"][0]["data"]["s"];
55        if let Value::String(s) = value {
56            return Some(s.clone())
57        }
58        return None;
59    }
60
61    pub fn check_err(&self, err: &str) {
62        let err2 = &self.get_err().unwrap() ;
63        if err2 != err {
64            panic!("invalid error, expect {}, got {}", err, err2);
65        }
66    }
67}
68
69impl fmt::Display for ChainTesterError {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        if let Some(ref value) = self.json {
72            write!(f, "{}", serde_json::to_string_pretty(value).unwrap())
73        } else {
74            if let Some(ref err) = self.error_string {
75                write!(f, "{}", err)
76            } else {
77                write!(f, "{}", "Unknown error")
78            }
79        }
80    }
81}
82
83impl fmt::Debug for ChainTesterError {
84    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
85        if let Some(ref value) = self.json {
86            write!(f, "{}", serde_json::to_string_pretty(&value).unwrap())
87        } else {
88            if let Some(ref err) = self.error_string {
89                write!(f, "{}", err)
90            } else {
91                write!(f, "{}", "Unknown error")
92            }
93        }
94    }
95}
96
97pub struct TransactionReturn {
98    pub value: Value
99}
100
101impl fmt::Display for TransactionReturn {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        write!(f, "{}", serde_json::to_string_pretty(&self.value).unwrap())
104    }
105}
106
107impl fmt::Debug for TransactionReturn {
108    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
109        write!(f, "{}", serde_json::to_string_pretty(&self.value).unwrap())
110    }
111}
112
113pub type Result<T> = core::result::Result<T, ChainTesterError>;
114
115pub struct GetTableRowsPrams<'a> {
116    pub json: bool,
117    pub code: &'a str,
118    pub scope: &'a str,
119    pub table: &'a str,
120    pub lower_bound: &'a str,
121    pub upper_bound: &'a str,
122    pub limit: i64,
123    pub key_type: &'a str,
124    pub index_position: &'a str,
125    pub reverse: bool,
126    pub show_payer: bool,
127}
128
129impl<'a> Default for GetTableRowsPrams<'a> {
130    fn default() -> Self {
131        Self {
132            json: true,
133            code: "",
134            scope: "",
135            table: "",
136            lower_bound: "",
137            upper_bound: "",
138            limit: 10,
139            key_type: "",
140            index_position: "",
141            reverse: false,
142            show_payer: false
143        }
144    }
145}
146
147pub struct VMAPIClient {
148    vm_api_client: Option<ApplySyncClient<ClientInputProtocol, ClientOutputProtocol>>,
149}
150
151pub struct ChainTesterClient {
152    client: Option<IPCChainTesterSyncClient<ClientInputProtocol, ClientOutputProtocol>>,
153}
154
155lazy_static! {
156    static ref VM_API_CLIENT: Mutex<VMAPIClient> = Mutex::new(VMAPIClient::new());
157}
158
159lazy_static! {
160    static ref CHAIN_TESTER_CLIENT: Mutex<ChainTesterClient> = Mutex::new(ChainTesterClient::new());
161}
162
163pub fn get_vm_api_client() -> MutexGuard<'static, VMAPIClient> {
164    let mut ret = VM_API_CLIENT.lock().unwrap();
165    if ret.vm_api_client.is_none() {
166        ret.init();
167    }
168    return ret;
169}
170
171pub fn close_vm_api_client() {
172    let mut ret = VM_API_CLIENT.lock().unwrap();
173    ret.close();
174}
175
176impl VMAPIClient {
177    fn new() -> Self {
178        VMAPIClient{vm_api_client: None}
179    }
180
181    pub fn init(&mut self) {
182        if self.vm_api_client.is_none() {
183            let host = crate::get_debugger_config().vm_api_server_address.clone();
184            let port = crate::get_debugger_config().vm_api_server_port;
185            let client = new_vm_api_client(&host, port).unwrap();
186            self.vm_api_client = Some(client);
187        }
188    }
189
190    pub fn close(&mut self) {
191        if self.vm_api_client.is_some() {
192            self.vm_api_client = None;
193        }
194    }
195
196    // pub fn client(&mut self) -> &mut ApplySyncClient<ClientInputProtocol, ClientOutputProtocol> {
197    //     self.vm_api_client.as_mut().unwrap()
198    // }
199}
200
201impl Deref for VMAPIClient {
202    type Target = ApplySyncClient<ClientInputProtocol, ClientOutputProtocol>;
203
204    fn deref(&self) -> &ApplySyncClient<ClientInputProtocol, ClientOutputProtocol>
205    {
206        self.vm_api_client.as_ref().unwrap()
207    }
208}
209
210impl DerefMut for VMAPIClient {
211    fn deref_mut(&mut self) -> &mut Self::Target {
212        self.vm_api_client.as_mut().unwrap()
213    }
214}
215// 
216
217impl ChainTesterClient {
218    fn new() -> Self {
219        better_panic::install();
220        ChainTesterClient{client: None}
221    }
222
223    fn init(&mut self) {
224        if self.client.is_some() {
225            return;
226        }
227
228        let host = crate::get_debugger_config().debugger_server_address.clone();
229        let port = crate::get_debugger_config().debugger_server_port;
230
231        let mut c = TTcpChannel::new();
232    
233        // open the underlying TCP stream
234        println!("connecting to debugger server on {}:{}", host, port);
235        c.open(&format!("{}:{}", host, port)).unwrap();    
236        println!("debugger server connected");
237        
238        // clone the TCP channel into two halves, one which
239        // we'll use for reading, the other for writing
240        let (i_chan, o_chan) = c.split().unwrap();
241    
242        // wrap the raw sockets (slow) with a buffered transport of some kind
243        let i_tran = TBufferedReadTransport::new(i_chan);
244        let o_tran = TBufferedWriteTransport::new(o_chan);
245    
246        // now create the protocol implementations
247        let i_prot = TBinaryInputProtocol::new(i_tran, false);
248        let o_prot = TBinaryOutputProtocol::new(o_tran, true);
249    
250        let mut client = IPCChainTesterSyncClient::new(i_prot, o_prot);
251        client.init_vm_api().unwrap();
252        let _ = get_vm_api_client(); //init vm api client
253
254        client.init_apply_request().unwrap();
255        let _= crate::server::get_apply_request_server(); //init apply request server
256
257        self.client = Some(client);
258
259    }
260
261    pub fn close(&mut self) {
262        if self.client.is_some() {
263            self.client = None;
264        }
265    }
266}
267
268impl Deref for ChainTesterClient {
269    type Target = IPCChainTesterSyncClient<ClientInputProtocol, ClientOutputProtocol>;
270
271    fn deref(&self) -> &IPCChainTesterSyncClient<ClientInputProtocol, ClientOutputProtocol>
272    {
273        self.client.as_ref().unwrap()
274    }
275}
276
277impl DerefMut for ChainTesterClient {
278    fn deref_mut(&mut self) -> &mut Self::Target {
279        self.client.as_mut().unwrap()
280    }
281}
282
283pub fn get_chain_tester_client() -> MutexGuard<'static, ChainTesterClient> {
284    let mut ret = CHAIN_TESTER_CLIENT.lock().unwrap();
285    if ret.client.is_none() {
286        ret.init();
287    }
288    return ret;
289}
290
291pub fn close_chain_tester_client() {
292    let mut ret = CHAIN_TESTER_CLIENT.lock().unwrap();
293    ret.close();
294}
295
296pub struct ChainTester {
297    id: i32,
298}
299
300fn parse_ret(ret: &thrift::Result<String>) -> Result<Value> {
301    match ret {
302        Ok(ret) => {
303            let tx: Value = serde_json::from_str(&ret).map_err(|err| {
304                ChainTesterError{json: None, error_string: Some(err.to_string())}
305            })?;
306
307            if tx.get("except").is_some() {
308                Err(ChainTesterError{json: Some(tx), error_string: None})
309            } else {
310                Ok(tx)
311            }
312        }
313        Err(err) => {
314            Err(ChainTesterError{
315                json: None, error_string: Some(format!("{:?}", err)),
316            })
317        }
318    }
319}
320
321fn parse_ret2(ret: &thrift::Result<Vec<u8>>) -> Result<Value> {
322    match ret {
323        Ok(ret) => {
324            let tx: Value = serde_json::from_slice(ret).map_err(|err| {
325                ChainTesterError{json: None, error_string: Some(err.to_string())}
326            })?;
327
328            if tx.get("except").is_some() {
329                Err(ChainTesterError{json: Some(tx), error_string: None})
330            } else {
331                Ok(tx)
332            }
333        }
334        Err(err) => {
335            Err(ChainTesterError{
336                json: None, error_string: Some(format!("{:?}", err)),
337            })
338        }
339    }
340}
341
342impl ChainTester {
343    pub fn new() -> Self {
344        Self { id: get_chain_tester_client().new_chain().unwrap() }
345    }
346
347    fn client(&mut self) -> MutexGuard<'static, ChainTesterClient> {
348        get_chain_tester_client()
349    }
350
351    pub fn free(&mut self) {
352        self.client().free_chain(self.id).unwrap();
353    }
354
355    pub fn produce_block(&mut self) {
356        self.client().produce_block(self.id).unwrap()
357    }
358
359    pub fn enable_debug_contract(&mut self, contract: &str, enable: bool) -> thrift::Result<()> {
360        self.client().enable_debug_contract(self.id, contract.into(), enable)
361    }
362
363    pub fn is_debug_contract_enabled(&mut self, contract: &str) -> thrift::Result<bool> {
364        self.client().is_debug_contract_enabled(self.id, contract.into())
365    }
366
367    pub fn import_key(&mut self, pub_key: &str, priv_key: &str) -> bool {
368        self.client().import_key(self.id, pub_key.into(), priv_key.into()).unwrap()
369    }
370
371    pub fn get_info(&mut self) -> Result<Value> {
372        let ret = self.client().get_info(self.id);
373        parse_ret(&ret)
374    }
375
376    pub fn get_account(&mut self, account: &str) -> Result<Value> {
377        let ret = self.client().get_account(self.id, account.into());
378        parse_ret(&ret)
379    }
380
381    pub fn push_action(&mut self, account: &str, action: &str, arguments: ActionArguments, permissions: &str) -> Result<TransactionReturn> {
382        let _account = String::from(account);
383        let _action = String::from(action);
384
385        let _arguments;
386        match &arguments {
387            ActionArguments::String(s) => {
388                _arguments = s.clone();
389            }
390            ActionArguments::Binary(b) => {
391                _arguments = hex::encode(b);
392            }
393        }
394        let _permissions = String::from(permissions);
395        match self.client().push_action(self.id, _account, _action, _arguments, _permissions) {
396            Ok(ret) => {
397                let tx: Value = serde_json::from_slice(&ret).map_err(|err| {
398                    ChainTesterError{json: None, error_string: Some(err.to_string())}
399                })?;
400        
401                if tx.get("except").is_some() {
402                    Err(ChainTesterError{json: Some(tx), error_string: None})
403                } else {
404                    Ok(TransactionReturn{value: tx})
405                }
406            }
407            Err(err) => {
408                Err(ChainTesterError{
409                    json: None, error_string: Some(format!("{:?}", err)),
410                })
411            }
412        }
413    }
414
415    pub fn deploy_contract(&mut self, account: &str, wasm_file: &str, abi_file: &str) -> Result<Value> {
416        // abi_file.is_empty()
417        let wasm = fs::read(wasm_file).unwrap();        
418        let hex_wasm = hex::encode(wasm);
419
420        let set_code_args = format!(
421            r#"
422            {{
423                "account": "{}",
424                "vmtype": 0,
425                "vmversion": 0,
426                "code": "{}"
427             }}
428            "#,
429            account,
430            hex_wasm
431        );
432
433        let permissions = format!(
434            r#"
435            {{
436                "{}": "active"
437            }}
438            "#,
439            account,
440        );
441
442        let raw_set_code_args = self.client().pack_action_args(self.id, "eosio".into(), "setcode".into(), set_code_args).unwrap();
443        let mut actions: Vec<Box<Action>> = Vec::new();
444        let setcode = Action{
445            account: Some("eosio".into()),
446            action: Some("setcode".into()),
447            permissions: Some(permissions.clone()),
448            arguments: Some(hex::encode(raw_set_code_args)),
449        };
450        actions.push(Box::new(setcode));
451
452        if !abi_file.is_empty() {
453            // let abi = fs::read(Path::new(abi_file)).unwrap();
454            let abi = fs::read_to_string(abi_file).unwrap();
455            let raw_abi = self.client().pack_abi(abi).unwrap();
456            let hex_raw_abi = hex::encode(raw_abi);
457    
458            let set_abi_args = format!(
459                r#"
460                {{
461                    "account": "{}",
462                    "abi": "{}"
463                 }}
464                "#,
465                account,
466                hex_raw_abi
467            );
468
469            let raw_setabi = self.client().pack_action_args(self.id, "eosio".into(), "setabi".into(), set_abi_args).unwrap();
470            let setabi = Action{
471                account: Some("eosio".into()),
472                action: Some("setabi".into()),
473                permissions: Some(permissions.clone()),
474                arguments: Some(hex::encode(raw_setabi)),
475            };
476
477            actions.push(Box::new(setabi));    
478        }
479
480        self.push_actions(actions)
481    }
482
483    pub fn push_actions(&mut self, actions: Vec<Box<Action>>) -> Result<Value> {
484        let ret = self.client().push_actions(self.id, actions);
485        parse_ret2(&ret)
486    }
487
488    pub fn get_table_rows<'a>(&mut self, json: bool, code: &'a str, scope: &'a str, table: &'a str, lower_bound: &'a str, upper_bound: &'a str, limit: i64) -> Result<Value> {
489        let param = GetTableRowsPrams {
490            json: json,
491            code: code,
492            scope: scope,
493            table: table,
494            lower_bound: lower_bound,
495            upper_bound: upper_bound,
496            limit: limit,
497            key_type: "",
498            index_position: "",
499            reverse: false,
500            show_payer: false,
501        };
502        return self.get_table_rows_ex(&param);
503
504    }
505
506    pub fn get_table_rows_ex(&mut self, params: &GetTableRowsPrams) -> Result<Value> {
507        let ret = self.client().get_table_rows(self.id,
508            params.json,
509            params.code.into(),
510            params.scope.into(),
511            params.table.into(),
512            params.lower_bound.into(),
513            params.upper_bound.into(),
514            params.limit,
515            params.key_type.into(),
516            params.index_position.into(),
517            params.reverse,
518            params.show_payer,
519        );
520        parse_ret(&ret)
521    }
522}
523
524pub enum ActionArguments {
525    String(String),
526    Binary(Vec<u8>),
527}
528
529impl From<String> for ActionArguments {
530    fn from(value: String) -> Self {
531        ActionArguments::String(value)
532    }
533}
534
535impl From<&str> for ActionArguments {
536    fn from(value: &str) -> Self {
537        ActionArguments::String(String::from(value))
538    }
539}
540
541impl From<Vec<u8>> for ActionArguments {
542    fn from(value: Vec<u8>) -> Self {
543        ActionArguments::Binary(value)
544    }
545}
546
547impl Drop for ChainTester {
548    fn drop(&mut self) {
549        self.free();
550    }
551}
552
553pub fn new_vm_api_client(
554    host: &str,
555    port: u16,
556) -> thrift::Result<ApplySyncClient<ClientInputProtocol, ClientOutputProtocol>> {
557    let mut c = TTcpChannel::new();
558
559    // open the underlying TCP stream
560    println!("connecting to VM API server on {}:{}", host, port);
561    //wait for vm api server to start
562    thread::sleep(Duration::from_micros(10));
563    let remote_address = format!("{}:{}", host, port);
564    for i in 0..=10 {
565        match c.open(&remote_address) {
566            Ok(()) => {
567                break;
568            }
569            Err(err) => {
570                if i == 10 {
571                    panic!("{}", err)
572                } else {
573                    println!("+++++++vm_api_client error: {}", err);
574                    thread::sleep(Duration::from_micros(200));    
575                }
576            }
577        }
578    }
579
580    println!("VM API server connected!");
581
582    // clone the TCP channel into two halves, one which
583    // we'll use for reading, the other for writing
584    let (i_chan, o_chan) = c.split()?;
585
586    // wrap the raw sockets (slow) with a buffered transport of some kind
587    let i_tran = TBufferedReadTransport::new(i_chan);
588    let o_tran = TBufferedWriteTransport::new(o_chan);
589
590    // now create the protocol implementations
591    let i_prot = TBinaryInputProtocol::new(i_tran, false);
592    let o_prot = TBinaryOutputProtocol::new(o_tran, true);
593    // we're done!
594    Ok(ApplySyncClient::new(i_prot, o_prot))
595}
596
597///
598pub fn n2s(value: u64) -> String {
599	let charmap = ".12345abcdefghijklmnopqrstuvwxyz".as_bytes();
600	// 13 dots
601	let mut s: [u8; 13] = ['.' as u8, '.'  as u8, '.' as u8, '.' as u8, '.' as u8, '.' as u8, '.' as u8, '.' as u8, '.' as u8, '.' as u8, '.' as u8, '.' as u8, '.' as u8];
602	let mut tmp = value;
603	for i in 0..13 {
604		let c: u8;
605		if i == 0 {
606			c = charmap[(tmp&0x0f) as usize];
607		} else {
608			c = charmap[(tmp&0x1f) as usize];
609		}
610		s[12-i] = c;
611		if i == 0 {
612			tmp >>= 4
613		} else {
614			tmp >>= 5
615		}
616	}
617
618	let mut i = s.len() - 1;
619	while i != 0 {
620		if s[i] != '.' as u8 {
621			break
622		}
623        i -= 1;
624	}
625
626    let r = match String::from_utf8(s[0..i+1].to_vec()) {
627        Ok(v) => v,
628        Err(_) => String::from(""),
629    };
630    return r;
631}
632