Skip to content

Commit bd42bdf

Browse files
committed
e2e: add client tests
Signed-off-by: Sam Batschelet <[email protected]>
1 parent 59ddab1 commit bd42bdf

File tree

4 files changed

+124
-54
lines changed

4 files changed

+124
-54
lines changed

spaces-cli/src/bin/spaces-cli/main.rs

+10-25
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,8 @@ use std::error;
33
use clap::{Parser, Subcommand};
44
use jsonrpc_core::futures;
55
use spacesvm::{
6-
api::{
7-
client::{claim_tx, delete_tx, get_or_create_pk, set_tx, Client, Uri},
8-
DecodeTxArgs, IssueTxArgs, ResolveArgs,
9-
},
10-
chain::tx::{decoder, unsigned::TransactionData},
6+
api::client::{claim_tx, delete_tx, get_or_create_pk, set_tx, Client, Uri},
7+
chain::tx::unsigned::TransactionData,
118
};
129

1310
#[derive(Subcommand, Debug)]
@@ -47,20 +44,16 @@ struct Cli {
4744
command: Command,
4845
}
4946

50-
#[tokio::main]
51-
async fn main() -> Result<(), Box<dyn error::Error>> {
47+
fn main() -> Result<(), Box<dyn error::Error>> {
5248
let cli = Cli::parse();
5349

54-
let secret_key = get_or_create_pk(&cli.private_key_file)?;
50+
let private_key = get_or_create_pk(&cli.private_key_file)?;
5551
let uri = cli.endpoint.parse::<Uri>()?;
56-
let mut client = Client::new(uri);
52+
let mut client = Client::new(uri).set_private_key(private_key);
5753

5854
if let Command::Get { space, key } = &cli.command {
59-
let resp = futures::executor::block_on(client.resolve(ResolveArgs {
60-
space: space.as_bytes().to_vec(),
61-
key: key.as_bytes().to_vec(),
62-
}))
63-
.map_err(|e| e.to_string())?;
55+
let resp =
56+
futures::executor::block_on(client.resolve(space, key)).map_err(|e| e.to_string())?;
6457
log::debug!("resolve response: {:?}", resp);
6558

6659
println!("{}", serde_json::to_string(&resp)?);
@@ -76,21 +69,13 @@ async fn main() -> Result<(), Box<dyn error::Error>> {
7669

7770
// decode tx
7871
let tx_data = command_to_tx(cli.command)?;
79-
let resp = futures::executor::block_on(client.decode_tx(DecodeTxArgs { tx_data }))
80-
.map_err(|e| e.to_string())?;
72+
let resp = futures::executor::block_on(client.decode_tx(tx_data)).map_err(|e| e.to_string())?;
8173

8274
let typed_data = &resp.typed_data;
8375

84-
// create signature
85-
let dh = decoder::hash_structured_data(typed_data)?;
86-
let sig = secret_key.sign_digest(&dh.as_bytes())?;
87-
8876
// issue tx
89-
let resp = futures::executor::block_on(client.issue_tx(IssueTxArgs {
90-
typed_data: resp.typed_data,
91-
signature: sig.to_bytes().to_vec(),
92-
}))
93-
.map_err(|e| e.to_string())?;
77+
let resp =
78+
futures::executor::block_on(client.issue_tx(typed_data)).map_err(|e| e.to_string())?;
9479
println!("{}", serde_json::to_string(&resp)?);
9580

9681
Ok(())

spacesvm/src/api/client.rs

+73-24
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::{
22
fs::File,
3-
io::{Result, Write},
3+
io::{Error, ErrorKind, Result, Write},
44
path::Path,
55
};
66

@@ -9,12 +9,19 @@ use crate::{
99
DecodeTxArgs, DecodeTxResponse, IssueTxArgs, IssueTxResponse, PingResponse, ResolveArgs,
1010
ResolveResponse,
1111
},
12-
chain::tx::{tx::TransactionType, unsigned::TransactionData},
12+
chain::tx::{
13+
decoder::{self, TypedData},
14+
tx::TransactionType,
15+
unsigned::TransactionData,
16+
},
17+
};
18+
use avalanche_types::key::{
19+
self,
20+
secp256k1::{private_key::Key, signature::Sig},
1321
};
14-
use avalanche_types::key;
1522
use http::{Method, Request};
1623
use hyper::{body, client::HttpConnector, Body, Client as HyperClient};
17-
use jsonrpc_core::{Call, Id, MethodCall, Params, Version};
24+
use jsonrpc_core::{Call, Id, MethodCall, Params, Value, Version};
1825
use serde::de;
1926

2027
pub use http::Uri;
@@ -23,13 +30,19 @@ pub use http::Uri;
2330
pub struct Client<C> {
2431
id: u64,
2532
client: HyperClient<C>,
26-
pub uri: Uri,
33+
endpoint: Uri,
34+
private_key: Option<Key>,
2735
}
2836

2937
impl Client<HttpConnector> {
30-
pub fn new(uri: Uri) -> Self {
38+
pub fn new(endpoint: Uri) -> Self {
3139
let client = HyperClient::new();
32-
Self { id: 0, client, uri }
40+
Self {
41+
id: 0,
42+
client,
43+
endpoint,
44+
private_key: None,
45+
}
3346
}
3447
}
3548

@@ -40,6 +53,16 @@ impl Client<HttpConnector> {
4053
Id::Num(id)
4154
}
4255

56+
pub fn set_endpoint(mut self, endpoint: Uri) -> Self {
57+
self.endpoint = endpoint;
58+
self
59+
}
60+
61+
pub fn set_private_key(mut self, private_key: Key) -> Self {
62+
self.private_key = Some(private_key);
63+
self
64+
}
65+
4366
/// Returns a serialized json request as string and the request id.
4467
pub fn raw_request(&mut self, method: &str, params: &Params) -> (Id, String) {
4568
let id = self.next_id();
@@ -55,6 +78,14 @@ impl Client<HttpConnector> {
5578
)
5679
}
5780

81+
/// Returns a recoverable signature from bytes.
82+
pub fn sign_digest(&self, dh: &[u8]) -> Result<Sig> {
83+
if let Some(pk) = &self.private_key {
84+
pk.sign_digest(dh)?;
85+
}
86+
Err(Error::new(ErrorKind::Other, "private key not set"))
87+
}
88+
5889
/// Returns a PingResponse from client request.
5990
pub async fn ping(&mut self) -> Result<PingResponse> {
6091
let (_id, json_request) = self.raw_request("ping", &Params::None);
@@ -64,40 +95,48 @@ impl Client<HttpConnector> {
6495
}
6596

6697
/// Returns a DecodeTxResponse from client request.
67-
pub async fn decode_tx(&mut self, args: DecodeTxArgs) -> Result<DecodeTxResponse> {
68-
let arg_bytes = serde_json::to_vec(&args)?;
69-
let params: Params = serde_json::from_slice(&arg_bytes)?;
70-
let (_id, json_request) = self.raw_request("decodeTx", &params);
98+
pub async fn decode_tx(&mut self, tx_data: TransactionData) -> Result<DecodeTxResponse> {
99+
let arg_value = serde_json::to_value(&DecodeTxArgs { tx_data })?;
100+
let (_id, json_request) = self.raw_request("decodeTx", &Params::Array(vec![arg_value]));
71101
let resp = self.post_de::<DecodeTxResponse>(&json_request).await?;
72102

73103
Ok(resp)
74104
}
75105

76106
/// Returns a IssueTxResponse from client request.
77-
pub async fn issue_tx(&mut self, args: IssueTxArgs) -> Result<IssueTxResponse> {
78-
let arg_bytes = serde_json::to_vec(&args)?;
79-
let params: Params = serde_json::from_slice(&arg_bytes)?;
80-
let (_id, json_request) = self.raw_request("issueTx", &params);
107+
pub async fn issue_tx(&mut self, typed_data: &TypedData) -> Result<IssueTxResponse> {
108+
let dh = decoder::hash_structured_data(typed_data)?;
109+
let sig = self.sign_digest(&dh.as_bytes())?.to_bytes().to_vec();
110+
log::debug!("signature: {:?}", sig);
111+
112+
let arg_value = serde_json::to_value(&IssueTxArgs {
113+
typed_data: typed_data.to_owned(),
114+
signature: sig,
115+
})?;
116+
let (_id, json_request) = self.raw_request("issueTx", &Params::Array(vec![arg_value]));
81117
let resp = self.post_de::<IssueTxResponse>(&json_request).await?;
82118

83119
Ok(resp)
84120
}
85121

86122
/// Returns a ResolveResponse from client request.
87-
pub async fn resolve(&mut self, args: ResolveArgs) -> Result<ResolveResponse> {
88-
let arg_bytes = serde_json::to_vec(&args)?;
89-
let params: Params = serde_json::from_slice(&arg_bytes)?;
90-
let (_id, json_request) = self.raw_request("resolve", &params);
123+
pub async fn resolve(&mut self, space: &str, key: &str) -> Result<ResolveResponse> {
124+
let arg_value = serde_json::to_value(&ResolveArgs {
125+
space: space.as_bytes().to_vec(),
126+
key: key.as_bytes().to_vec(),
127+
})?;
128+
let (_id, json_request) = self.raw_request("issueTx", &Params::Array(vec![arg_value]));
91129
let resp = self.post_de::<ResolveResponse>(&json_request).await?;
92130

93131
Ok(resp)
94132
}
95133

96134
/// Returns a deserialized response from client request.
97135
pub async fn post_de<T: de::DeserializeOwned>(&self, json: &str) -> Result<T> {
136+
println!("json: {}", json);
98137
let req = Request::builder()
99138
.method(Method::POST)
100-
.uri(self.uri.to_string())
139+
.uri(self.endpoint.to_string())
101140
.header("content-type", "application/json-rpc")
102141
.body(Body::from(json.to_owned()))
103142
.map_err(|e| {
@@ -107,20 +146,30 @@ impl Client<HttpConnector> {
107146
)
108147
})?;
109148

110-
let resp = self.client.request(req).await.map_err(|e| {
149+
let mut resp = self.client.request(req).await.map_err(|e| {
111150
std::io::Error::new(
112151
std::io::ErrorKind::Other,
113152
format!("client post request failed: {}", e),
114153
)
115154
})?;
116155

117-
let bytes = body::to_bytes(resp.into_body())
156+
let bytes = body::to_bytes(resp.body_mut())
118157
.await
119158
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
120-
let resp = serde_json::from_slice(&bytes).map_err(|e| {
159+
160+
// deserialize bytes to value
161+
let v: Value = serde_json::from_slice(&bytes).map_err(|e| {
162+
std::io::Error::new(
163+
std::io::ErrorKind::Other,
164+
format!("failed to deserialize response to value: {}", e),
165+
)
166+
})?;
167+
168+
// deserialize result to T
169+
let resp = serde_json::from_value(v["result"].to_owned()).map_err(|e| {
121170
std::io::Error::new(
122171
std::io::ErrorKind::Other,
123-
format!("failed to create client request: {}", e),
172+
format!("failed to deserialize response: {}", e),
124173
)
125174
})?;
126175

spacesvm/tests/vm/mod.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,10 @@ async fn test_api() {
139139
log::info!("ping response {}", body);
140140

141141
let tx_data = claim_tx("test_claim".to_owned());
142-
let arg_bytes = serde_json::to_value(&DecodeTxArgs { tx_data }).unwrap();
142+
let arg_value = serde_json::to_value(&DecodeTxArgs { tx_data }).unwrap();
143143

144-
let (_id, json_str) = client.raw_request("decodeTx", &Params::Array(vec![arg_bytes]));
144+
let (_id, json_str) = client.raw_request("decodeTx", &Params::Array(vec![arg_value]));
145+
log::info!("decodeTx request: {}", json_str);
145146
let req = http::request::Builder::new()
146147
.body(json_str.as_bytes().to_vec())
147148
.unwrap();

tests/e2e/src/tests/mod.rs

+38-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ use std::{
77

88
use avalanche_network_runner_sdk::{BlockchainSpec, Client, GlobalConfig, StartRequest};
99
use avalanche_types::subnet;
10-
use spacesvm;
10+
use spacesvm::{
11+
self,
12+
api::{
13+
client::{claim_tx, get_or_create_pk, Uri},
14+
DecodeTxArgs,
15+
},
16+
};
1117

1218
#[tokio::test]
1319
async fn e2e() {
@@ -47,7 +53,7 @@ async fn e2e() {
4753
// keep this in sync with "proto" crate
4854
// ref. https://github.com/ava-labs/avalanchego/blob/v1.9.2/version/constants.go#L15-L17
4955
let (exec_path, plugins_dir) =
50-
avalanche_installer::avalanchego::download(None, None, Some("v1.9.2".to_string()))
56+
avalanche_installer::avalanchego::download(None, None, Some("v1.9.3".to_string()))
5157
.await
5258
.unwrap();
5359
avalanchego_exec_path = exec_path;
@@ -92,7 +98,7 @@ async fn e2e() {
9298
.unwrap(),
9399
),
94100
blockchain_specs: vec![BlockchainSpec {
95-
vm_name: String::from("minikvvm"),
101+
vm_name: String::from("spacesvm"),
96102
genesis: genesis_file_path.to_string(),
97103
..Default::default()
98104
}],
@@ -178,6 +184,35 @@ async fn e2e() {
178184
rpc_eps.push(iv.uri.clone());
179185
}
180186

187+
let ep = format!(
188+
"{}/{}",
189+
rpc_eps[0].to_owned(),
190+
spacesvm::vm::PUBLIC_API_ENDPOINT
191+
);
192+
let private_key = get_or_create_pk("/tmp/.spacesvm-cli-pk").expect("generate new private key");
193+
194+
let mut scli = spacesvm::api::client::Client::new(ep.parse::<Uri>().expect("valid endpoint"))
195+
.set_private_key(private_key);
196+
log::info!("ping request...");
197+
let resp = scli.ping().await.expect("ping success");
198+
log::info!("ping response from {}: {:?}", ep, resp);
199+
200+
log::info!("decode claim tx request...");
201+
let resp = scli
202+
.decode_tx(DecodeTxArgs {
203+
tx_data: claim_tx("test".to_owned()),
204+
})
205+
.await
206+
.expect("decodeTx success");
207+
log::info!("decode claim response from {}: {:?}", ep, resp);
208+
209+
log::info!("issue claim tx request...");
210+
let resp = scli
211+
.issue_tx(&resp.typed_data)
212+
.await
213+
.expect("issue_tx success");
214+
log::info!("issue claim tx response from {}: {:?}", ep, resp);
215+
181216
if crate::get_network_runner_enable_shutdown() {
182217
log::info!("shutdown is enabled... stopping...");
183218
let _resp = cli.stop().await.expect("failed stop");

0 commit comments

Comments
 (0)