Skip to content

Commit 5e2d010

Browse files
authored
Merge pull request #129 from tonlabs/check-sync
Check sync
2 parents 7d14f6f + 3f6ebcd commit 5e2d010

File tree

8 files changed

+140
-19
lines changed

8 files changed

+140
-19
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Release Notes
22
All notable changes to this project will be documented in this file.
33

4+
## Jun 05, 2020
5+
### New
6+
- time sync check while initializing
7+
48
## Jun 03, 2020
59
### New
610
- local run functions return updated contract state when running with `full_run = true`

ton_client/client/src/setup.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ pub(crate) struct SetupParams {
4040
pub message_processing_timeout_grow_factor: Option<f32>,
4141
pub wait_for_timeout: Option<u32>,
4242
pub access_key: Option<String>,
43+
pub out_of_sync_threshold: Option<i64>,
4344
}
4445

4546
impl Into<NodeClientConfig> for SetupParams {
@@ -53,6 +54,7 @@ impl Into<NodeClientConfig> for SetupParams {
5354
message_processing_timeout: self.message_processing_timeout.unwrap_or(default.message_processing_timeout),
5455
message_processing_timeout_grow_factor: self.message_processing_timeout_grow_factor.unwrap_or(default.message_processing_timeout_grow_factor),
5556
wait_for_timeout: self.wait_for_timeout.unwrap_or(default.wait_for_timeout),
57+
out_of_sync_threshold: self.out_of_sync_threshold.unwrap_or(default.out_of_sync_threshold),
5658
}),
5759
#[cfg(feature = "node_interaction")]
5860
base_url: self.base_url,
@@ -73,7 +75,7 @@ fn setup(context: &mut ClientContext, config: SetupParams) -> ApiResult<()> {
7375
.map_err(|err| ApiError::cannot_create_runtime(err))?;
7476

7577
let client = runtime.block_on(ton_sdk::init(config.into()))
76-
.map_err(|err|ApiError::config_init_failed(err))?;
78+
.map_err(|err| crate::types::apierror_from_sdkerror(&err, ApiError::config_init_failed))?;
7779

7880
context.client = Some(client);
7981
context.runtime = Some(runtime);

ton_client/client/src/tests/mod.rs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -285,19 +285,20 @@ fn test_wallet_deploy() {
285285
}),
286286
).unwrap();
287287

288-
let deployed = client.request("contracts.deploy",
288+
let deployed = parse_object(client.request("contracts.deploy",
289289
json!({
290290
"abi": abi.clone(),
291291
"constructorParams": json!({}),
292292
"imageBase64": WALLET_CODE_BASE64,
293293
"keyPair": keys,
294294
"workchainId": 0,
295295
}),
296-
).unwrap();
296+
));
297297

298-
assert_eq!(format!("{{\"address\":\"{}\",\"alreadyDeployed\":false}}", address), deployed);
298+
assert_eq!(deployed["address"], address.to_string());
299+
assert_eq!(deployed["alreadyDeployed"], false);
299300

300-
let result = client.request("contracts.run",
301+
let result = parse_object(client.request("contracts.run",
301302
json!({
302303
"address": address.to_string(),
303304
"abi": abi.clone(),
@@ -307,8 +308,8 @@ fn test_wallet_deploy() {
307308
}),
308309
"keyPair": keys,
309310
}),
310-
).unwrap();
311-
assert_eq!("{\"output\":{\"value0\":\"0x0\"}}", result);
311+
));
312+
assert_eq!(result["output"]["value0"], "0x0");
312313
}
313314

314315
const GIVER_ADDRESS: &str = "0:841288ed3b55d9cdafa806807f02a0ae0c169aa5edfe88a789a6482429756a94";
@@ -482,3 +483,19 @@ fn test_address_parsing() {
482483
})).unwrap(),
483484
base64_url);
484485
}
486+
487+
488+
#[test]
489+
fn test_out_of_sync() {
490+
let client = TestClient::new();
491+
492+
let result = client.request("setup",
493+
json!({
494+
"baseUrl": "http://localhost",
495+
"outOfSyncThreshold": -1
496+
})).unwrap_err();
497+
498+
let value: Value = serde_json::from_str(&result).unwrap();
499+
500+
assert_eq!(value["code"], 1013);
501+
}

ton_client/client/src/types.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ impl ApiError {
188188
let mut error = ApiError::new(
189189
ApiErrorSource::Node,
190190
&ApiSdkErrorCode::TransactionsLag,
191-
"Existing block transaction not found".to_owned(),
191+
"Existing block transaction not found (no transaction appeared for the masterchain block with gen_utime > message expiration time)".to_owned(),
192192
);
193193

194194
error.data = serde_json::json!({
@@ -270,6 +270,22 @@ impl ApiError {
270270
error
271271
}
272272

273+
pub fn clock_out_of_sync(delta_ms: i64, threshold: i64, expiration_timout: u32) -> Self {
274+
let mut error = ApiError::new(
275+
ApiErrorSource::Node,
276+
&ApiSdkErrorCode::ClockOutOfSync,
277+
"Device clock is out of sync with server time".to_owned(),
278+
);
279+
280+
error.data = serde_json::json!({
281+
"delta_ms": delta_ms,
282+
"threshold_ms": threshold,
283+
"expiration_timout_ms": expiration_timout,
284+
"tip": "Synchronize your device clock with internet time"
285+
});
286+
error
287+
}
288+
273289
// SDK Cell
274290

275291
pub fn cell_invalid_query<E: Display>(s: E) -> Self {
@@ -816,6 +832,8 @@ where
816832
ApiError::transactions_lag(msg_id.to_string(), *send_time, block_id.clone(), *timeout),
817833
Some(SdkError::TransactionWaitTimeout{msg_id, msg: _, send_time, timeout}) =>
818834
ApiError::transaction_wait_timeout(msg_id.to_string(), *send_time, *timeout),
835+
Some(SdkError::ClockOutOfSync{delta_ms, threshold_ms, expiration_timeout}) =>
836+
ApiError::clock_out_of_sync(*delta_ms, *threshold_ms, *expiration_timeout),
819837
_ => default_err(err.to_string())
820838
}
821839
}

ton_sdk/src/contract.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,7 @@ impl Contract {
612612
msg_id: message_id.clone(),
613613
send_time: now,
614614
block_id: block["id"].as_str().unwrap_or("").to_owned(),
615-
timeout: block_timeout
615+
timeout: 5000
616616
}.into(),
617617
_ => err
618618
})?;

ton_sdk/src/error.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,14 @@ pub enum SdkError {
145145
send_time: u32,
146146
timeout: u32
147147
},
148+
149+
#[fail(display = "Invalid server response: {}", 0)]
150+
InvalidServerResponse(String),
151+
152+
#[fail(display = "Clock out of sync: {}", delta_ms)]
153+
ClockOutOfSync {
154+
delta_ms: i64,
155+
threshold_ms: i64,
156+
expiration_timeout: u32
157+
},
148158
}

ton_sdk/src/node_client.rs

Lines changed: 77 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use reqwest::{ClientBuilder};
2121
use reqwest::StatusCode;
2222
use reqwest::redirect::Policy;
2323
use reqwest::header::LOCATION;
24+
use chrono::prelude::Utc;
2425
use ton_types::{error, Result};
2526

2627
#[derive(Serialize, Deserialize)]
@@ -43,9 +44,31 @@ pub struct MutationRequest {
4344
pub body: String
4445
}
4546

47+
#[allow(dead_code)]
4648
pub struct NodeClient {
4749
client: Option<GqlClient>,
48-
timeouts: TimeoutsConfig
50+
timeouts: TimeoutsConfig,
51+
server_info: Option<ServerInfo>,
52+
}
53+
54+
struct ServerInfo {
55+
pub version: u64,
56+
pub supports_time: bool
57+
}
58+
59+
impl ServerInfo {
60+
pub fn from_version(version: &str) -> Result<Self> {
61+
let mut vec: Vec<&str> = version.split(".").collect();
62+
vec.resize(3, "0");
63+
let version = u64::from_str_radix(vec[0], 10)? * 1000000
64+
+ u64::from_str_radix(vec[1], 10)? * 1000
65+
+ u64::from_str_radix(vec[2], 10)?;
66+
67+
Ok(ServerInfo {
68+
version,
69+
supports_time: version >= 26003,
70+
})
71+
}
4972
}
5073

5174
impl NodeClient {
@@ -97,25 +120,69 @@ impl NodeClient {
97120

98121
(queries_url, subscriptions_url)
99122
}
100-
123+
124+
async fn query_server_info(client: &GqlClient) -> Result<ServerInfo> {
125+
let response = client.query("%7Binfo%7Bversion%7D%7D".to_owned()).await?;
126+
let version = response["data"]["info"]["version"]
127+
.as_str()
128+
.ok_or(SdkError::InvalidServerResponse(
129+
format!("No version in response: {}", response)))?;
130+
131+
ServerInfo::from_version(version)
132+
}
133+
134+
async fn get_time_delta(client: &GqlClient) -> Result<i64>{
135+
let start = Utc::now().timestamp_millis();
136+
let response = client.query("%7Binfo%7Btime%7D%7D".to_owned()).await?;
137+
let end = Utc::now().timestamp_millis();
138+
let server_time = response["data"]["info"]["time"]
139+
.as_i64()
140+
.ok_or(SdkError::InvalidServerResponse(
141+
format!("No time in response: {}", response)))?;
142+
143+
Ok(server_time - (start + (end - start) / 2))
144+
}
145+
146+
async fn check_time_delta(client: &GqlClient, timeouts: &TimeoutsConfig) -> Result<()> {
147+
let delta = Self::get_time_delta(client).await?;
148+
if delta.abs() >= timeouts.out_of_sync_threshold || delta.abs() >= timeouts.message_expiration_timeout as i64 {
149+
Err(SdkError::ClockOutOfSync {
150+
delta_ms: delta,
151+
threshold_ms: timeouts.out_of_sync_threshold,
152+
expiration_timeout: timeouts.message_expiration_timeout
153+
}.into())
154+
} else {
155+
Ok(())
156+
}
157+
}
158+
101159
// Globally initializes client with server address
102160
pub async fn new(config: NodeClientConfig) -> Result<NodeClient> {
103-
let client = if let Some(base_url) = config.base_url {
161+
let timeouts = config.timeouts.unwrap_or_default();
162+
let (client, server_info) = if let Some(base_url) = config.base_url {
104163
let (mut queries_server, mut subscriptions_server) = Self::expand_address(base_url);
105164
if let Some(redirected) = Self::check_redirect(&queries_server).await? {
106165
queries_server = redirected.clone();
107166
subscriptions_server = redirected
108167
.replace("https://", "wss://")
109168
.replace("http://", "ws://");
110169
}
111-
Some(GqlClient::new(&queries_server, &subscriptions_server)?)
170+
171+
let client = GqlClient::new(&queries_server, &subscriptions_server)?;
172+
let server_info = Self::query_server_info(&client).await?;
173+
if server_info.supports_time {
174+
Self::check_time_delta(&client, &timeouts).await?;
175+
}
176+
177+
(Some(client), Some(server_info))
112178
} else {
113-
None
179+
(None, None)
114180
};
115181

116182
Ok(NodeClient {
117183
client,
118-
timeouts: config.timeouts.unwrap_or_default()
184+
timeouts: timeouts,
185+
server_info
119186
})
120187
}
121188

@@ -141,9 +208,9 @@ impl NodeClient {
141208
let record_value = &value["payload"]["data"][&closure_table];
142209

143210
if record_value.is_null() {
144-
Err(error!(SdkError::InvalidData {
145-
msg: format!("Invalid subscription answer: {}", value)
146-
}).into())
211+
Err(error!(SdkError::InvalidServerResponse(
212+
format!("Invalid subscription answer: {}", value)
213+
)).into())
147214
} else {
148215
Ok(record_value.clone())
149216
}
@@ -187,7 +254,7 @@ impl NodeClient {
187254
// try to extract the record value from the answer
188255
let records_array = &result["data"][&table];
189256
if records_array.is_null() {
190-
Err(SdkError::InvalidData { msg: format!("Invalid query answer: {}", result) }.into())
257+
Err(SdkError::InvalidServerResponse(format!("Invalid query answer: {}", result)).into())
191258
} else {
192259
Ok(records_array.clone())
193260
}

ton_sdk/src/types.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub const DEFAULT_EXPIRATION_TIMEOUT: u32 = 10000;
2727
pub const DEFAULT_PROCESSING_TIMEOUT: u32 = 40000;
2828
pub const DEFAULT_TIMEOUT_GROW_FACTOR: f32 = 1.5;
2929
pub const DEFAULT_WAIT_TIMEOUT: u32 = 40000;
30+
pub const DEFAULT_OUT_OF_SYNC_THRESHOLD: i64 = 15000;
3031

3132

3233
#[derive(Debug, Deserialize, Serialize)]
@@ -38,6 +39,7 @@ pub struct TimeoutsConfig {
3839
pub message_processing_timeout: u32,
3940
pub message_processing_timeout_grow_factor: f32,
4041
pub wait_for_timeout: u32,
42+
pub out_of_sync_threshold: i64
4143
}
4244

4345
impl Default for TimeoutsConfig {
@@ -49,6 +51,7 @@ impl Default for TimeoutsConfig {
4951
message_processing_timeout: DEFAULT_PROCESSING_TIMEOUT,
5052
message_processing_timeout_grow_factor: DEFAULT_TIMEOUT_GROW_FACTOR,
5153
wait_for_timeout: DEFAULT_WAIT_TIMEOUT,
54+
out_of_sync_threshold: DEFAULT_OUT_OF_SYNC_THRESHOLD
5255
}
5356
}
5457
}

0 commit comments

Comments
 (0)