diff --git a/Cargo.lock b/Cargo.lock index 0327adb7..f072acf4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,7 +105,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.39", ] [[package]] @@ -116,7 +116,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.39", ] [[package]] @@ -258,7 +258,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.39", ] [[package]] @@ -295,6 +295,15 @@ dependencies = [ "serde", ] +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -395,7 +404,7 @@ checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.39", ] [[package]] @@ -422,7 +431,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.39", ] [[package]] @@ -537,7 +546,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.39", ] [[package]] @@ -982,7 +991,7 @@ checksum = "ddece26afd34c31585c74a4db0630c376df271c285d682d1e55012197830b6df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.39", ] [[package]] @@ -1049,9 +1058,9 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "nom" -version = "7.1.1" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", @@ -1169,7 +1178,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.39", ] [[package]] @@ -1212,11 +1221,21 @@ dependencies = [ "syn 1.0.103", ] +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn 2.0.39", +] + [[package]] name = "proc-macro2" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] @@ -1244,7 +1263,7 @@ dependencies = [ "log", "multimap", "petgraph", - "prettyplease", + "prettyplease 0.1.24", "prost", "prost-types", "regex", @@ -1581,7 +1600,7 @@ checksum = "d4fe589678c688e44177da4f27152ee2d190757271dc7f1d5b6b9f68d869d641" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.39", ] [[package]] @@ -1629,7 +1648,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.39", ] [[package]] @@ -1716,9 +1735,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.23" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -1809,6 +1828,14 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +[[package]] +name = "tl_parser" +version = "0.1.0" +dependencies = [ + "anyhow", + "nom", +] + [[package]] name = "tokio" version = "1.34.0" @@ -1846,7 +1873,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.39", ] [[package]] @@ -1958,7 +1985,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6fdaae4c2c638bb70fe42803a26fbd6fc6ac8c72f5c59f67ecc2a2dcabf4b07" dependencies = [ - "prettyplease", + "prettyplease 0.1.24", "proc-macro2", "prost-build", "quote", @@ -2001,6 +2028,7 @@ dependencies = [ "base64", "bytes", "config", + "convert_case", "crc", "dashmap", "derive-new", @@ -2009,12 +2037,16 @@ dependencies = [ "itertools 0.12.0", "metrics", "pin-project", + "prettyplease 0.2.15", "quick_cache", + "quote", "rand", "reqwest", "serde", "serde_json", "serial_test", + "syn 2.0.39", + "tl_parser", "tokio", "tokio-retry", "tokio-stream", @@ -2091,7 +2123,7 @@ checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.39", ] [[package]] @@ -2264,6 +2296,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "untrusted" version = "0.7.1" diff --git a/Cargo.toml b/Cargo.toml index c0b7d583..a9f32694 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] resolver = "2" members = [ + "tl_parser", "tonlibjson-sys", "tonlibjson-client", "ton-grpc", diff --git a/tl_parser/Cargo.toml b/tl_parser/Cargo.toml new file mode 100644 index 00000000..c3588964 --- /dev/null +++ b/tl_parser/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "tl_parser" +version = "0.1.0" +edition = "2021" +authors = ["Andrei Kostylev "] + +[dependencies] +anyhow = { workspace = true } +nom = "7.1.3" diff --git a/tl_parser/src/lib.rs b/tl_parser/src/lib.rs new file mode 100644 index 00000000..77293326 --- /dev/null +++ b/tl_parser/src/lib.rs @@ -0,0 +1,878 @@ +use anyhow::{anyhow, bail}; +use nom::{AsChar}; +use nom::branch::alt; +use nom::bytes::complete::{tag, take_till, take_until, take_while, take_while1, take_while_m_n}; +use nom::character::complete::{line_ending, multispace0, multispace1, satisfy, space0}; +use nom::combinator::{map, opt, recognize}; +use nom::error::Error; +use nom::multi::{many0, many1, separated_list1}; +use nom::sequence::{delimited, pair, preceded, separated_pair, terminated}; +use crate::FieldType::{Plain, Repetition}; + +pub type ConstructorNumber = u32; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Combinator { + functional: bool, + builtin: bool, + id: String, + r#type: String, + constructor_number: Option, + optional_fields: Vec, + fields: Vec, +} + +impl Combinator { + pub fn id(&self) -> &str { + &self.id + } + + pub fn result_type(&self) -> &str { + &self.r#type + } + + pub fn fields(&self) -> &Vec { + &self.fields + } + + pub fn is_functional(&self) -> bool { + self.functional + } + + pub fn is_builtin(&self) -> bool { + self.builtin + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +enum FieldType { + Plain { name: String, condition: Option }, + Repetition { multiplicity: Option, fields: Vec, }, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Field { + name: Option, + r#type: FieldType, + exclamation_point_modifier: bool, +} + + +// TODO[akostylev0] TypeDefinition +impl Field { + pub fn id(&self) -> &Option { + &self.name + } + + pub fn field_type(&self) -> Option<&str> { + let Plain { name , ..} = &self.r#type else { + return None + }; + + if !self.type_is_polymorphic() { + return Some(name.as_str()); + } + + if let Some((left, _)) = name.split_once('<') { + return Some(left) + } + + let (left, _) = name.split_once(' ').unwrap(); + Some(left) + } + + pub fn type_is_optional(&self) -> bool { + let Plain { condition, .. } = &self.r#type else { + return false + }; + + condition.is_some() + } + + pub fn type_is_polymorphic(&self) -> bool { + let Plain { name , ..} = &self.r#type else { + return false + }; + + name.contains('<') || name.contains(' ') + } + + pub fn type_variables(&self) -> Option> { + let Plain { name , ..} = &self.r#type else { + return None + }; + + if name.contains(' ') { + let Some((_, tail)) = name.split_once(' ') else { + return Some(vec![]) + }; + + Some(tail.split(' ').map(|s| s.trim().to_owned()).collect()) + } else { + let Some((_, tail)) = name.split_once('<') else { + return Some(vec![]) + }; + + Some(tail.replace('>', "").split(',').map(|s| s.trim().to_owned()).collect()) + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct OptionalField { + name: String, + r#type: String, +} + +pub fn parse(input: &str) -> anyhow::Result> { + let (input, mut types) = many0( + delimited(opt(space_or_comment), alt((combinator_decl, builtin_combinator_decl)), opt(space_or_comment)) + )(input).map_err(|e| anyhow!("parse error: {}", e))?; + + let (input, funcs) = opt(preceded( + tag("---functions---"), + many0( + delimited(opt(space_or_comment), alt((functional_combinator_decl, builtin_combinator_decl)), opt(space_or_comment)) + )))(input).map_err(|e: nom::Err>| anyhow!("parse error: {}", e))?; + + if let Some(funcs) = funcs { + types.extend(funcs); + } + + if input.is_empty() { + Ok(types) + } else { + bail!("parse error: input is not empty: \"{}\"", input) + } +} + +fn is_lc_letter(c: char) -> bool { c.is_ascii_lowercase() } + +fn is_uc_letter(c: char) -> bool { c.is_ascii_uppercase() } + +fn is_digit(c: char) -> bool { c.is_ascii_digit() } + +fn is_hex_digit(c: char) -> bool { c.is_hex_digit() } + +fn is_underscore(c: char) -> bool { c == '_' } + +fn is_letter(c: char) -> bool { is_lc_letter(c) || is_uc_letter(c) } + +fn is_ident_char(c: char) -> bool { is_letter(c) || is_digit(c) || is_underscore(c) } + +fn single_line_comment(input: &str) -> nom::IResult<&str, &str> { + preceded(tag("//"), take_till(|c| c == '\n'))(input) +} + +fn multi_line_comment(input: &str) -> nom::IResult<&str, &str> { + delimited(tag("/*"), take_until("*/"), tag("*/"))(input) +} + +fn space_or_comment(input: &str) -> nom::IResult<&str, ()> { + let (input, _) = many0(alt(( + multispace1, + single_line_comment, + multi_line_comment, + line_ending + )))(input)?; + + Ok((input, ())) +} + +fn lc_ident(input: &str) -> nom::IResult<&str, String> { + let (input, head) = satisfy(is_lc_letter)(input)?; + let (input, tail) = take_while(is_ident_char)(input)?; + + Ok((input, format!("{}{}", head, tail))) +} + +fn uc_ident(input: &str) -> nom::IResult<&str, String> { + let (input, head) = satisfy(is_uc_letter)(input)?; + let (input, tail) = take_while(is_ident_char)(input)?; + + Ok((input, format!("{}{}", head, tail))) +} + +fn namespace_ident(input: &str) -> nom::IResult<&str, String> { lc_ident(input) } + +fn lc_ident_ns(input: &str) -> nom::IResult<&str, String> { + map(separated_list1(tag("."), lc_ident), |vec| vec.join("."))(input) +} + +fn uc_ident_ns(input: &str) -> nom::IResult<&str, String> { + let (input, ns) = opt(terminated(separated_list1(tag("."), namespace_ident), tag(".")))(input)?; + let (input, head) = uc_ident(input)?; + + match ns { + None => Ok((input, head)), + Some(ns) => Ok((input, format!("{}.{}", ns.join("."), head))) + } +} + +fn lc_ident_full(input: &str) -> nom::IResult<&str, (String, Option)> { + let (input, ident) = lc_ident_ns(input)?; + let (input, combinator_number) = opt(preceded(tag("#"), take_while_m_n(8, 8, is_hex_digit)))(input)?; + + match combinator_number { + None => Ok((input, (ident, None))), + Some(combinator_number) => { + let combinator_number = ConstructorNumber::from_str_radix(combinator_number, 16).expect("invalid combinator number"); + + Ok((input, (ident, Some(combinator_number)))) + } + } +} + +fn full_combinator_id(input: &str) -> nom::IResult<&str, (String, Option)> { + alt(( + lc_ident_full, + map(tag("_"), |s: &str| (s.to_owned(), None)) + ))(input) +} + +fn boxed_type_ident(input: &str) -> nom::IResult<&str, String> { + uc_ident_ns(input) +} + +fn result_type(input: &str) -> nom::IResult<&str, (bool, String)> { + let (input, exclamation) = opt(tag("!"))(input)?; + let (input, ident) = boxed_type_ident(input)?; + let (input, exprs) = many0(delimited(space0, subexpr, space0))(input)?; + + if !exprs.is_empty() { + Ok((input, (exclamation.is_some(), format!("{} {}", ident, exprs.join(" "))))) + } else { + Ok((input, (exclamation.is_some(), ident))) + } +} + +fn expr(input: &str) -> nom::IResult<&str, String> { + map(separated_list1(tag(" "), subexpr), |vs| vs.join(" "))(input) +} + +fn type_expr(input: &str) -> nom::IResult<&str, String> { + expr(input) +} + +fn opt_args(input: &str) -> nom::IResult<&str, Vec> { + let (input, names) = preceded(tag("{"), many1(delimited(space0, var_ident, space0)))(input)?; + let (input, _) = delimited(space0, tag(":"), space0)(input)?; + let (input, type_name) = terminated(type_expr, tag("}"))(input)?; + + Ok((input, names.into_iter().map(|name| OptionalField { name, r#type: type_name.clone() }).collect())) +} + +fn combinator_decl(input: &str) -> nom::IResult<&str, Combinator> { + let (input, (combinator_id, constructor_number)) = preceded(multispace0, full_combinator_id)(input)?; + let (input, opts) = opt(delimited(space0, opt_args, space0))(input)?; + let (input, fields) = many0(delimited(space0, args, space0))(input)?; + let (input, _) = delimited(multispace0, tag("="), multispace0)(input)?; + let (input, (functional, combinator_type)) = result_type(input)?; + let (input, _) = preceded(multispace0, tag(";"))(input)?; + + Ok((input, Combinator { id: combinator_id, r#type: combinator_type, builtin: false, constructor_number, + fields: fields.into_iter().flatten().collect(), optional_fields: opts.unwrap_or_default(), functional })) +} + +fn functional_combinator_decl(input: &str) -> nom::IResult<&str, Combinator> { + let (input, (combinator_id, constructor_number)) = preceded(multispace0, full_combinator_id)(input)?; + let (input, opts) = opt(delimited(space0, opt_args, space0))(input)?; + let (input, fields) = many0(delimited(space0, args, space0))(input)?; + let (input, _) = delimited(multispace0, tag("="), multispace0)(input)?; + let (input, (_, combinator_type)) = result_type(input)?; + let (input, _) = preceded(multispace0, tag(";"))(input)?; + + Ok((input, Combinator { id: combinator_id, r#type: combinator_type, builtin: false, constructor_number, + fields: fields.into_iter().flatten().collect(), optional_fields: opts.unwrap_or_default(), functional: true })) +} + +fn builtin_combinator_decl(input: &str) -> nom::IResult<&str, Combinator> { + let (input, (combinator_id, constructor_number)) = preceded(multispace0, full_combinator_id)(input)?; + let (input, _) = delimited(multispace0, tag("?"), multispace0)(input)?; + let (input, _) = delimited(multispace0, tag("="), multispace0)(input)?; + let (input, combinator_type) = boxed_type_ident(input)?; + let (input, _) = preceded(multispace0, tag(";"))(input)?; + + Ok((input, Combinator { id: combinator_id, r#type: combinator_type, builtin: true, constructor_number, fields: vec![], optional_fields: vec![], functional: false })) +} + +fn var_ident(input: &str) -> nom::IResult<&str, String> { + alt((lc_ident, uc_ident))(input) +} + +fn var_ident_opt(input: &str) -> nom::IResult<&str, String> { + alt(( + var_ident, + map(tag("_"), |s: &str| s.to_owned()) + ))(input) +} + +fn nat_const(input: &str) -> nom::IResult<&str, &str> { + take_while1(is_digit)(input) +} + +fn conditional_def(input: &str) -> nom::IResult<&str, String> { + let (input, var) = var_ident(input)?; + let (input, opt) = opt(preceded(tag("."), nat_const))(input)?; + let (input, _) = tag("?")(input)?; + + match opt { + None => Ok((input, var)), + Some(opt) => Ok((input, format!("{}.{}", var, opt))) + } +} + +fn subexpr(input: &str) -> nom::IResult<&str, String> { + alt( + (term, map(many1(map(separated_pair( + alt((term, map(nat_const, |s: &str| s.to_owned()))), + map(tag("+"), |s: &str| s.to_owned()), + alt((term, map(nat_const, |s: &str| s.to_owned()))), + ), |(s1, s2)| format!("{} + {}", s1, s2))), |vs: Vec| vs.join("+"))) + )(input) +} + +fn type_ident(input: &str) -> nom::IResult<&str, String> { + alt((boxed_type_ident, lc_ident_ns, map(tag("#"), |s: &str| s.to_owned())))(input) +} + +fn term(input: &str) -> nom::IResult<&str, String> { + alt(( + delimited(tag("("), expr, tag(")")), + map( + recognize(pair(type_ident, delimited(tag("<"), separated_list1(tag(","), type_ident), tag(">")))), + |s| s.to_owned(), + ), + type_ident, + var_ident, + map(nat_const, |s| s.to_owned()), + preceded(tag("%"), term) + ))(input) +} + +fn type_term(input: &str) -> nom::IResult<&str, (bool, String)> { + pair( + map(opt(tag("!")), |s| s.is_some()), + term + )(input) +} + +fn args_1(input: &str) -> nom::IResult<&str, Vec> { + let (input, id) = var_ident_opt(input)?; + let (input, _) = tag(":")(input)?; + let (input, condition) = opt(conditional_def)(input)?; + let (input, (exclamation_point_modifier, name)) = type_term(input)?; + + Ok((input, vec![Field { name: Some(id), r#type: Plain { name, condition }, exclamation_point_modifier }])) +} + +fn nat_term(input: &str) -> nom::IResult<&str, String> { + term(input) +} + +fn multiplicity(input: &str) -> nom::IResult<&str, String> { + terminated(nat_term, tag("*"))(input) +} + +fn args_2(input: &str) -> nom::IResult<&str, Vec> { + let (input, id) = opt(terminated(var_ident_opt, tag(":")))(input)?; + let (input, multiplicity) = opt(multiplicity)(input)?; + let (input, _) = tag("[")(input)?; + let (input, fields) = many1(delimited(space0, args, space0))(input)?; + let (input, _) = tag("]")(input)?; + + Ok((input, vec![Field { name: id, r#type: Repetition { multiplicity, fields: fields.into_iter().flatten().collect() }, exclamation_point_modifier: false }])) +} + +fn args_3(input: &str) -> nom::IResult<&str, Vec> { + let (input, _) = tag("(")(input)?; + let (input, fields) = many1(delimited(space0, var_ident_opt, space0))(input)?; + let (input, _) = tag(":")(input)?; + let (input, (exclamation_point_modifier, type_term)) = type_term(input)?; + let (input, _) = tag(")")(input)?; + + Ok((input, fields.into_iter().map(|id| Field { + name: Some(id), + r#type: Plain { + name: type_term.clone(), + condition: None + }, + exclamation_point_modifier + }).collect())) +} + +fn args_4(input: &str) -> nom::IResult<&str, Vec> { + let (input, (exclamation_point_modifier, type_term)) = type_term(input)?; + + Ok((input, vec![Field { name: None, r#type: Plain { name: type_term, condition: None }, exclamation_point_modifier }])) +} + +fn args(input: &str) -> nom::IResult<&str, Vec> { + alt((args_1, args_2, args_3, args_4))(input) +} + +#[cfg(test)] +mod tests { + use super::*; + + impl Combinator { + fn new(name: &str, r#type: &str) -> Self { + Self { id: name.to_owned(), r#type: r#type.to_owned(), builtin: false, constructor_number: None, fields: vec![], optional_fields: vec![], functional: false } + } + + fn builtin(name: &str, r#type: &str) -> Self { + Self { id: name.to_owned(), r#type: r#type.to_owned(), builtin: true, constructor_number: None, fields: vec![], optional_fields: vec![], functional: false } + } + + fn functional(mut self) -> Self { + self.functional = true; + + self + } + + fn with_constructor_number(mut self, constructor_number: ConstructorNumber) -> Self { + self.constructor_number.replace(constructor_number); + + self + } + + fn with_fields(mut self, fields: Vec) -> Self { + self.fields = fields; + + self + } + + fn with_optional_fields(mut self, optional_fields: Vec) -> Self { + self.optional_fields = optional_fields; + + self + } + } + + impl Field { + fn plain(name: &str, r#type: &str) -> Self { + Self { name: Some(name.to_owned()), r#type: Plain { name: r#type.to_owned(), condition: None }, exclamation_point_modifier: false } + } + + fn unnamed_plain(r#type: &str) -> Self { + Self { name: None, r#type: Plain { name: r#type.to_owned(), condition: None }, exclamation_point_modifier: false } + } + + fn repetition(name: Option, multiplicity: Option, fields: Vec) -> Self { + Self { name, r#type: Repetition { multiplicity, fields }, exclamation_point_modifier: false } + } + } + + impl OptionalField { + fn new(name: &str, r#type: &str) -> Self { + OptionalField { name: name.to_owned(), r#type: r#type.to_owned() } + } + } + + #[test] + fn lc_ident_test() { + let input = "input"; + + let output = lc_ident(input); + + assert_eq!(output, Ok(("", "input".to_owned()))); + } + + #[test] + fn uc_ident_test() { + let input = "Input"; + + let output = uc_ident(input); + + assert_eq!(output, Ok(("", "Input".to_owned()))); + } + + #[test] + fn lc_ident_ns_test() { + let input = "namespace.input"; + + let output = lc_ident_ns(input); + + assert_eq!(output, Ok(("", "namespace.input".to_owned()))); + } + + #[test] + fn uc_ident_ns_test() { + let input = "namespace.Input"; + + let output = uc_ident_ns(input); + + assert_eq!(output, Ok(("", "namespace.Input".to_owned()))); + } + + #[test] + fn lc_ident_ns_partial_test() { + let input = "input"; + + let output = lc_ident_ns(input); + + assert_eq!(output, Ok(("", "input".to_owned()))); + } + + #[test] + fn lc_ident_full_test() { + let input = "input#a8509bda"; + + let output = lc_ident_full(input); + + assert_eq!(output, Ok(("", ("input".to_owned(), Some(2823855066))))); + } + + #[test] + fn full_combinator_id_skip_test() { + let input = "_"; + + let output = full_combinator_id(input); + + assert_eq!(output, Ok(("", ("_".to_owned(), None)))); + } + + #[test] + fn full_combinator_id_test() { + let input = "input#a8509bda"; + + let output = full_combinator_id(input); + + assert_eq!(output, Ok(("", ("input".to_owned(), Some(2823855066))))); + } + + #[test] + fn combinator_decl_test() { + let input = "null = Null;"; + + let output = combinator_decl(input); + + assert_eq!(output, Ok(("", Combinator::new("null", "Null")))); + } + + #[test] + fn single_line_comment_test() { + let input = "// comment"; + + let output = single_line_comment(input); + + assert_eq!(output, Ok(("", " comment"))); + } + + #[test] + fn multi_line_comment_test() { + let input = "/* multi + line +comment */"; + + let output = multi_line_comment(input); + + assert_eq!(output, Ok(("", " multi + line +comment "))); + } + + #[test] + fn args_1_test() { + let input = "first_name:fields.0?string"; + + let output = args_1(input); + + assert_eq!(output, Ok(("", vec![Field { + name: Some("first_name".to_owned()), + r#type: Plain { name: "string".to_owned(), condition: Some("fields.0".to_owned()) }, + exclamation_point_modifier: false + }]))); + } + + #[test] + fn args_2_test() { + let input = "a:m*[n*[double]]"; + + let output = args_2(input); + + assert_eq!(output, Ok(("", vec![Field { + name: Some("a".to_owned()), + r#type: Repetition { + multiplicity: Some("m".to_owned()), + fields: vec![Field { + name: None, + r#type: Repetition { + multiplicity: Some("n".to_owned()), + fields: vec![Field { + name: None, + r#type: Plain { name: "double".to_owned(), condition: None }, + exclamation_point_modifier: false + }], + }, + exclamation_point_modifier: false + }], + }, + exclamation_point_modifier: false + }]))); + } + + #[test] + fn args_3_test() { + let input = "(x y z:int32)"; + + let output = args_3(input); + + assert_eq!(output, Ok(("", vec![ + Field { + name: Some("x".to_string()), + r#type: Plain { + name: "int32".to_string(), + condition: None + }, + exclamation_point_modifier: false + }, + Field { + name: Some("y".to_string()), + r#type: Plain { + name: "int32".to_string(), + condition: None + }, + exclamation_point_modifier: false + }, + Field { + name: Some("z".to_string()), + r#type: Plain { + name: "int32".to_string(), + condition: None + }, + exclamation_point_modifier: false + }, + ]))); + } + + #[test] + fn args_4_test() { + let input = "double"; + + let output = args_4(input); + + assert_eq!(output, Ok(("", vec![Field { name: None, r#type: Plain { name: "double".to_owned(), condition: None }, exclamation_point_modifier: false }]))); + } + + + #[test] + fn empty_input() { + let input = ""; + + let output = parse(input).unwrap(); + + assert_eq!(output, vec![]); + } + + #[test] + fn boolean() { + let input = " +boolFalse = Bool; +boolTrue = Bool; +"; + + let output = parse(input).unwrap(); + + assert_eq!(output, vec![ + Combinator::new("boolFalse", "Bool"), + Combinator::new("boolTrue", "Bool"), + ]); + } + + #[test] + fn builtin() { + let input = "int#a8509bda ? = Int; +long ? = Long; +double ? = Double; +string ? = String;"; + + let output = parse(input).unwrap(); + + assert_eq!(output, vec![ + Combinator::builtin("int", "Int").with_constructor_number(2823855066), + Combinator::builtin("long", "Long"), + Combinator::builtin("double", "Double"), + Combinator::builtin("string", "String")]); + } + + #[test] + fn comments() { + let input = "///// +// +// Common Types +// +///// + +// Built-in types +int ? = Int; +long ? = Long; +double ? = Double; +string ? = String; + +/* multi + line +comment */ + +// Boolean emulation +boolFalse = Bool; +boolTrue = Bool;"; + + let output = parse(input).unwrap(); + + assert_eq!(output, vec![ + Combinator::builtin("int", "Int"), + Combinator::builtin("long", "Long"), + Combinator::builtin("double", "Double"), + Combinator::builtin("string", "String"), + Combinator::new("boolFalse", "Bool"), + Combinator::new("boolTrue", "Bool")] + ); + } + + #[test] + fn bool_stat() { + let input = "// Boolean for diagonal queries +boolStat statTrue:int statFalse:int statUnknown:int = BoolStat;"; + + let output = parse(input).unwrap(); + + assert_eq!(output, vec![ + Combinator::new("boolStat", "BoolStat") + .with_fields(vec![ + Field::plain("statTrue", "int"), + Field::plain("statFalse", "int"), + Field::plain("statUnknown", "int"), + ]) + ]); + } + + #[test] + fn type_term_hash_test() { + let input = "#"; + + let output = type_term(input).unwrap(); + + assert_eq!(output, ("", (false, "#".to_owned()))) + } + + #[test] + fn type_term_marked_hash_test() { + let input = "!A"; + + let output = type_term(input).unwrap(); + + assert_eq!(output, ("", (true, "A".to_owned()))) + } + + #[test] + fn vector_test() { + let input = "vector {t:Type} # [ t ] = Vector t;"; + + let output = parse(input).unwrap(); + + assert_eq!(output, vec![ + Combinator::new("vector", "Vector t") + .with_optional_fields(vec![ + OptionalField::new("t", "Type") + ]) + .with_fields(vec![ + Field::unnamed_plain("#"), + Field::repetition(None, None, vec![Field::unnamed_plain("t")]), + ]) + ]); + } + + #[test] + fn nested_lc_namespaces_test() { + let input = "n1.n2.n3.input"; + + let output = lc_ident_ns(input); + + assert_eq!(output, Ok(("", "n1.n2.n3.input".to_owned()))); + } + + #[test] + fn nested_uc_namespaces_test() { + let input = "n1.n2.n3.Input"; + + let output = uc_ident_ns(input); + + assert_eq!(output, Ok(("", "n1.n2.n3.Input".to_owned()))); + } + + #[test] + fn field_vector_of_test() { + let input = "exportedKey word_list:vector = ExportedKey;"; + + let output = parse(input).unwrap(); + + assert_eq!(output, vec![ + Combinator::new("exportedKey", "ExportedKey") + .with_fields(vec![ + Field::plain("word_list", "vector") + ]) + ]); + } + + #[test] + fn field_vector_of_test_spaces() { + let input = "smc.libraryResult result:(vector smc.libraryEntry) = smc.LibraryResult;"; + + let output = parse(input).unwrap(); + + assert_eq!(output, vec![ + Combinator::new("smc.libraryResult", "smc.LibraryResult") + .with_fields(vec![ + Field::plain("result", "vector smc.libraryEntry") + ]) + ]); + } + + #[test] + fn functional_combinator_test() { + let input = "a = A; +c = !C; +---functions--- +b = B; +d = !D;"; + + let output = parse(input).unwrap(); + + assert_eq!(output, vec![ + Combinator::new("a", "A"), + Combinator::new("c", "C").functional(), + Combinator::new("b", "B").functional(), + Combinator::new("d", "D").functional() + ]); + } + + #[test] + fn functional_combinator_ok_test() { + let input = "ok = Ok;"; + + let output = parse(input).unwrap(); + + assert_eq!(output, vec![ + Combinator::new("ok", "Ok"), + ]); + } + + #[test] + fn vector_parse_test() { + let input = "blocks.shardBlockProof from:ton.blockIdExt mc_id:ton.blockIdExt links:(vector blocks.shardBlockLink) mc_proof:(vector blocks.blockLinkBack) = blocks.ShardBlockProof;"; + + let output = parse(input).unwrap(); + + assert_eq!(output, vec![ + Combinator::new("blocks.shardBlockProof", "blocks.ShardBlockProof").with_fields( + vec![ + Field::plain("from", "ton.blockIdExt"), + Field::plain("mc_id", "ton.blockIdExt"), + Field::plain("links", "vector blocks.shardBlockLink"), + Field::plain("mc_proof", "vector blocks.blockLinkBack"), + ] + ), + ]); + } +} diff --git a/ton-grpc/src/account.rs b/ton-grpc/src/account.rs index 6b8cc7a8..13be324d 100644 --- a/ton-grpc/src/account.rs +++ b/ton-grpc/src/account.rs @@ -6,7 +6,7 @@ use anyhow::Result; use futures::{Stream, StreamExt, try_join, TryStreamExt, TryFutureExt}; use derive_new::new; use tonlibjson_client::address::AccountAddressData; -use tonlibjson_client::block::{BlockIdExt, Cell, RawFullAccountState}; +use tonlibjson_client::block::{RawFullAccountState, TonBlockIdExt, TvmCell}; use crate::helpers::{extend_block_id, extend_from_tx_id, extend_to_tx_id}; use crate::ton::account_service_server::AccountService as BaseAccountService; use crate::ton::{GetAccountStateRequest, GetAccountStateResponse, GetAccountTransactionsRequest, GetShardAccountCellRequest, GetShardAccountCellResponse, Transaction}; @@ -130,7 +130,7 @@ impl AccountService { Ok(state) } - async fn fetch_shard_account_cell(&self, msg: &GetShardAccountCellRequest) -> Result<(BlockIdExt, Cell)> { + async fn fetch_shard_account_cell(&self, msg: &GetShardAccountCellRequest) -> Result<(TonBlockIdExt, TvmCell)> { let (block_id, cell) = match &msg.criteria { None => { let block_id = self.client.get_masterchain_info().await?.last; diff --git a/ton-grpc/src/helpers.rs b/ton-grpc/src/helpers.rs index 673c9a91..17274b4e 100644 --- a/ton-grpc/src/helpers.rs +++ b/ton-grpc/src/helpers.rs @@ -9,9 +9,9 @@ use crate::ton::get_account_transactions_request::bound::Bound::{BlockId, Transa use crate::ton::get_account_transactions_request::bound::Type; #[tracing::instrument(skip_all, err)] -pub async fn extend_block_id(client: &TonClient, block_id: &ton::BlockId) -> Result { +pub async fn extend_block_id(client: &TonClient, block_id: &ton::BlockId) -> Result { if let (Some(root_hash), Some(file_hash)) = (&block_id.root_hash, &block_id.file_hash) { - Ok(block::BlockIdExt::new( + Ok(block::TonBlockIdExt::new( block_id.workchain, block_id.shard, block_id.seqno, @@ -24,7 +24,7 @@ pub async fn extend_block_id(client: &TonClient, block_id: &ton::BlockId) -> Res } #[tracing::instrument(skip_all, err)] -pub async fn prev_block_id(client: &TonClient, block_id: &ton::BlockId) -> Result { +pub async fn prev_block_id(client: &TonClient, block_id: &ton::BlockId) -> Result { client.look_up_block_by_seqno(block_id.workchain, block_id.shard, block_id.seqno - 1).await } diff --git a/ton-grpc/src/ton.rs b/ton-grpc/src/ton.rs index 4582ea06..033e467f 100644 --- a/ton-grpc/src/ton.rs +++ b/ton-grpc/src/ton.rs @@ -1,5 +1,6 @@ use tonlibjson_client::address::AccountAddressData; use tonlibjson_client::block; +use tonlibjson_client::block::{MsgBoxedData, MsgDataDecryptedText, MsgDataEncryptedText, MsgDataRaw, MsgDataText}; use crate::ton::get_account_state_response::AccountState; use crate::ton::message::MsgData; @@ -7,8 +8,8 @@ tonic::include_proto!("ton"); pub(crate) const FILE_DESCRIPTOR_SET: &[u8] = tonic::include_file_descriptor_set!("ton_descriptor"); -impl From for BlockIdExt { - fn from(value: block::BlockIdExt) -> Self { +impl From for BlockIdExt { + fn from(value: block::TonBlockIdExt) -> Self { Self { workchain: value.workchain, shard: value.shard, @@ -19,7 +20,7 @@ impl From for BlockIdExt { } } -impl From for block::BlockIdExt { +impl From for block::TonBlockIdExt { fn from(value: BlockIdExt) -> Self { Self { workchain: value.workchain, @@ -31,9 +32,9 @@ impl From for block::BlockIdExt { } } -impl From<(i32, block::ShortTxId)> for TransactionId { - fn from((chain_id, value): (i32, block::ShortTxId)) -> Self { - let address = value.account.into_internal(chain_id).to_string(); +impl From<(i32, block::BlocksShortTxId)> for TransactionId { + fn from((chain_id, value): (i32, block::BlocksShortTxId)) -> Self { + let address = value.clone().into_internal_string(chain_id); Self { account_address: address, @@ -73,14 +74,14 @@ impl From for block::InternalTransactionId { impl From for AccountState { fn from(value: block::RawFullAccountState) -> Self { - if value.code.is_some() { + if !value.code.is_empty() { AccountState::Active(ActiveAccountState { - code: value.code.unwrap_or_default(), - data: value.data.unwrap_or_default() + code: value.code, + data: value.data }) - } else if value.frozen_hash.is_some() { + } else if !value.frozen_hash.is_empty() { AccountState::Frozen(FrozenAccountState { - frozen_hash: value.frozen_hash.unwrap_or_default() + frozen_hash: value.frozen_hash }) } else { AccountState::Uninitialized(UninitializedAccountState {}) @@ -88,21 +89,22 @@ impl From for AccountState { } } -impl From for TvmCell { - fn from(value: block::Cell) -> Self { +impl From for TvmCell { + fn from(value: block::TvmCell) -> Self { Self { bytes: value.bytes } } } -impl From for MsgData { - fn from(value: block::MessageData) -> Self { +impl From for MsgData { + fn from(value: MsgBoxedData) -> Self { + match value { - block::MessageData::Raw { body, init_state } => { Self::Raw(MessageDataRaw { body, init_state }) } - block::MessageData::Text { text } => { Self::Text(MessageDataText { text }) } - block::MessageData::DecryptedText { text } => { Self::DecryptedText(MessageDataDecryptedText { text }) } - block::MessageData::EncryptedText { text } => { Self::EncryptedText(MessageDataEncryptedText { text }) } + MsgBoxedData::MsgDataRaw(MsgDataRaw { body, init_state }) => { Self::Raw(MessageDataRaw { body, init_state })} + MsgBoxedData::MsgDataText(MsgDataText { text }) => { Self::Text(MessageDataText { text })} + MsgBoxedData::MsgDataDecryptedText(MsgDataDecryptedText { text }) => { Self::DecryptedText(MessageDataDecryptedText { text }) } + MsgBoxedData::MsgDataEncryptedText(MsgDataEncryptedText { text }) => { Self::EncryptedText(MessageDataEncryptedText { text }) } } } } diff --git a/tonlibjson-client/Cargo.toml b/tonlibjson-client/Cargo.toml index fb5b423b..f76d094f 100644 --- a/tonlibjson-client/Cargo.toml +++ b/tonlibjson-client/Cargo.toml @@ -38,5 +38,13 @@ metrics = "0.21.1" tracing-test = { workspace = true } serial_test = "2.0.0" +[build-dependencies] +tl_parser = { path = "../tl_parser" } +anyhow = { workspace = true } +quote = "1.0" +syn = "2.0.39" +prettyplease = "0.2.15" +convert_case = "0.6.0" + [features] testnet = ["tonlibjson-sys/testnet"] diff --git a/tonlibjson-client/build.rs b/tonlibjson-client/build.rs new file mode 100644 index 00000000..afc3d2a2 --- /dev/null +++ b/tonlibjson-client/build.rs @@ -0,0 +1,406 @@ +use std::collections::HashMap; +use std::{env, fs}; +use std::path::{Path, PathBuf}; +use syn::{GenericArgument, Ident, MetaList}; +use quote::{format_ident, quote, ToTokens}; +use convert_case::{Case, Casing}; +use convert_case::Case::UpperCamel; +use tl_parser::Combinator; + +fn main() -> Result<(), Box> { + let scheme_path = if cfg!(testnet) { + Path::new("../tonlibjson-sys/ton-testnet/tl/generate/scheme/tonlib_api.tl") + } else { + Path::new("../tonlibjson-sys/ton/tl/generate/scheme/tonlib_api.tl") + }; + + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed={}", scheme_path.to_string_lossy()); + + Generator::from(scheme_path, "generated.rs") + .configure("ok", vec!["Deserialize"]) + .configure("sync", vec!["Default", "Serialize"]) + .configure_full("accountAddress", configure_type() + .derives(vec!["Clone", "Deserialize", "Serialize"]) + .field("account_address", configure_field() + .optional() + .serialize_with("serialize_none_as_empty") + .deserialize_with("deserialize_empty_as_none") + .build()) + .build() + ) + .configure("ton.blockId", vec!["Clone", "Serialize", "Deserialize", "Eq", "PartialEq", "Hash", "new"]) + .configure("ton.blockIdExt", vec!["Clone", "Serialize", "Deserialize", "Eq", "PartialEq", "Hash", "new"]) + .configure("blocks.masterchainInfo", vec!["Clone", "Deserialize", "Eq", "PartialEq"]) + .configure("internal.transactionId", vec!["Clone", "Serialize", "Deserialize", "Eq", "PartialEq"]) + .configure_full("raw.transactions", configure_type() + .derives(vec!["Deserialize"]) + .field("previous_transaction_id", configure_field() + .optional() + .deserialize_with("deserialize_default_as_none") + .build()) + .build()) + .configure_full("raw.fullAccountState", configure_type() + .derives(vec!["Deserialize"]) + .field("balance", configure_field() + .optional() + .deserialize_with("deserialize_ton_account_balance") + .build()) + .field("last_transaction_id", configure_field() + .optional() + .deserialize_with("deserialize_default_as_none") + .build() + ) + .build() + ) + + .configure("blocks.getBlockHeader", vec!["Clone", "Serialize", "Hash", "PartialEq", "Eq", "new"]) + .configure("getShardAccountCell", vec!["Clone", "Serialize", "new"]) + .configure("getShardAccountCellByTransaction", vec!["Clone", "Serialize", "new"]) + .configure("raw.getAccountState", vec!["Clone", "Serialize", "new"]) + .configure("raw.getAccountStateByTransaction", vec!["Clone", "Serialize", "new"]) + .configure("getAccountState", vec!["Clone", "Serialize", "new"]) + .configure("blocks.getMasterchainInfo", vec!["Clone", "Default", "Serialize", "new"]) + .configure("blocks.lookupBlock", vec!["Clone", "Serialize", "new", "Hash", "Eq", "PartialEq"]) + .configure("blocks.getShards", vec!["Clone", "Serialize", "new"]) + .configure("blocks.getTransactions", vec!["Clone", "Serialize", "new"]) + .configure("raw.sendMessage", vec!["Serialize", "new"]) + .configure("raw.sendMessageReturnHash", vec!["Serialize", "new"]) + + .configure("smc.load", vec!["Clone", "Serialize", "new"]) + .configure("smc.runGetMethod", vec!["Clone", "Serialize", "new"]) + + .configure_full("raw.getTransactionsV2", configure_type().derives(vec!["Clone", "Serialize", "new"]) + .field("private_key", configure_field().skip().build()) + .build() + ) + // .add_type("withBlock", vec!["Clone", "Serialize", "new"]) + + .generate()?; + + Ok(()) +} + +struct Generator { + input: PathBuf, + output: PathBuf, + types: HashMap, +} + +fn configure_type() -> TypeConfigurationBuilder { Default::default() } +fn configure_field() -> FieldConfigurationBuilder { Default::default() } + +#[derive(Default)] +struct TypeConfigurationBuilder { + derives: Vec, + fields: HashMap +} + +struct TypeConfiguration { + pub derives: Vec, + pub fields: HashMap +} + +impl Default for TypeConfiguration { + fn default() -> Self { + Self { derives: vec!["Debug".to_owned(), "Clone".to_owned(), "Serialize".to_owned(), "Deserialize".to_owned()], fields: HashMap::new() } + } +} + +#[derive(Default)] +struct FieldConfigurationBuilder { + skip: bool, + optional: bool, + deserialize_with: Option, + serialize_with: Option +} + +#[derive(Default)] +struct FieldConfiguration { + pub skip: bool, + pub optional: bool, + pub deserialize_with: Option, + pub serialize_with: Option +} + +impl FieldConfigurationBuilder { + fn skip(mut self) -> Self { + self.skip = true; + + self + } + fn optional(mut self) -> Self { + self.optional = true; + + self + } + + fn deserialize_with(mut self, deserialize_with: &str) -> Self { + self.deserialize_with = Some(deserialize_with.to_owned()); + + self + } + + fn serialize_with(mut self, serialize_with: &str) -> Self { + self.serialize_with = Some(serialize_with.to_owned()); + + self + } + + fn build(self) -> FieldConfiguration { + FieldConfiguration { skip: self.skip, optional: self.optional, deserialize_with: self.deserialize_with, serialize_with: self.serialize_with } + } +} + +impl TypeConfigurationBuilder { + fn derives(mut self, derives: Vec<&str>) -> Self { + self.derives = derives.into_iter().map(|s| s.to_owned()).collect(); + self.derives.push("Debug".to_owned()); + + self + } + + fn field(mut self, field: &str, configuration: FieldConfiguration) -> Self { + self.fields.insert(field.to_owned(), configuration); + + self + } + + fn build(self) -> TypeConfiguration { + TypeConfiguration { derives: self.derives, fields: self.fields } + } +} + + +impl Generator { + fn from, O: AsRef>(input: I, output: O) -> Self { + let input: PathBuf = input.as_ref().to_path_buf(); + let output: PathBuf = output.as_ref().to_path_buf(); + + Self { input, output, types: Default::default() } + } + + fn configure(mut self, name: &str, derives: Vec<&str>) -> Self { + self.types.insert(name.to_owned(), configure_type().derives(derives).build()); + + self + } + + fn configure_full(mut self, name: &str, configuration: TypeConfiguration) -> Self { + self.types.insert(name.to_owned(), configuration); + + self + } + + fn generate(self) -> anyhow::Result<()> { + let content = fs::read_to_string(self.input)?; + + let combinators = tl_parser::parse(&content)?; + + let mut map: HashMap> = HashMap::default(); + for combinator in combinators.iter() { + map.entry(combinator.result_type().to_owned()) + .or_default() + .push(combinator.to_owned()); + } + + let mut formatted = String::new(); + + let skip_list: Vec = vec!["Vector t", "Bool", "Int32", "Int53", "Int64", "Int256", "Bytes", "SecureString", "SecureBytes", "Object", "Function"] + .into_iter().map(|s| s.to_owned()).collect(); + + for (type_ident, types) in map { + eprintln!("type_ident = {:}", type_ident); + if skip_list.contains(&&type_ident) { + continue; + } + + let output_name = generate_type_name(&type_ident); + let struct_name = format_ident!("{}", output_name); + + let output = if types.iter().filter(|combinator| !combinator.is_functional()).count() == 1 { + let bare_type = types.first().unwrap().id(); + let name = format_ident!("{}", generate_type_name(bare_type)); + + quote! { + pub type #struct_name = #name; + } + } else { + let fields: Vec<_> = types + .iter() + .filter(|combinator| !combinator.is_functional()) + .map(|combinator| { + let rename = combinator.id(); + let field_name = format_ident!("{}", generate_type_name(rename)); + + quote! { + #field_name(#field_name) + } + }) + .collect(); + + quote! { + #[derive(Deserialize, Serialize, Clone, Debug)] + #[serde(untagged)] + pub enum #struct_name { + #(#fields),* + } + } + }; + + eprintln!("tokens = {}", output); + + let syntax_tree = syn::parse2(output.clone()).unwrap(); + formatted += &prettyplease::unparse(&syntax_tree); + + eprintln!("tokens = {}", output); + + for definition in types.into_iter() { + if definition.is_builtin() || definition.id() == "vector" || definition.id() == "int256" { + continue; + } + + let default = TypeConfiguration::default(); + let configuration = self.types.get(definition.id()).unwrap_or(&default); + + eprintln!("definition = {:?}", definition); + + let id = definition.id(); + let struct_name = structure_ident(definition.id()); + + let derives = format!("derive({})", configuration.derives.join(",")); + let t = syn::parse_str::(&derives)?; + + let fields: Vec<_> = definition.fields() + .iter() + .filter(|field| { + let default_configuration = FieldConfiguration::default(); + let field_name = field.id().clone().unwrap(); + let field_configuration = configuration.fields.get(&field_name).unwrap_or(&default_configuration); + + !field_configuration.skip + }) + .map(|field| { + let default_configuration = FieldConfiguration::default(); + let field_name = field.id().clone().unwrap().to_case(Case::Snake); + let field_configuration = configuration.fields.get(&field_name).unwrap_or(&default_configuration); + + eprintln!("field = {:?}", field); + let field_name = format_ident!("{}", &field_name); + let mut deserialize_number_from_string = false; // TODO[akostylev0] + let field_type: Box = if field.field_type().is_some_and(|typ| typ == "#") { + deserialize_number_from_string = true; + if field_configuration.optional { + Box::new(syn::parse_str::("Option").unwrap()) + } else { + Box::new(format_ident!("{}", "Int31")) + } + } else if field.type_is_polymorphic() { + let type_name = generate_type_name(field.field_type().unwrap()); + let type_variables = field.type_variables().unwrap(); + let args: Vec<_> = type_variables + .into_iter() + .map(|s| generate_type_name(&s)) + .collect(); + + let mut gen = format!("{}<{}>", type_name, args.join(",")); + if field.type_is_optional() || field_configuration.optional { + gen = format!("Option<{}>", gen); + } + Box::new(syn::parse_str::(&gen).unwrap()) + } else { + let field_type = field.field_type(); + if field_type.is_some_and(|s| s == "int32" || s == "int64" || s == "int53" || s == "int256") { + deserialize_number_from_string = true; + } + + if field_configuration.optional { + let id = format!("Option<{}>", structure_ident(field_type.unwrap())); + Box::new(syn::parse_str::(&id).unwrap()) + } else { + Box::new(format_ident!("{}", structure_ident(field_type.unwrap()))) + } + }; + + let serialize_with = if let Some(serialize_with) = &field_configuration.serialize_with { + quote! { + #[serde(serialize_with = #serialize_with)] + } + } else { quote! {} }; + let deserialize_with = if let Some(deserialize_with) = &field_configuration.deserialize_with { + quote! { + #[serde(deserialize_with = #deserialize_with)] + } + } else { quote! {} }; + + // // TODO[akostylev0]: just write custom wrappers for primitive types + if deserialize_number_from_string && deserialize_with.is_empty() { + quote! { + #serialize_with + #[serde(deserialize_with = "deserialize_number_from_string")] + pub #field_name: #field_type + } + } else { + quote! { + #serialize_with + #deserialize_with + pub #field_name: #field_type + } + }}).collect(); + + let traits = if definition.is_functional() { + let result_name = format_ident!("{}", generate_type_name(definition.result_type())); + quote! { + impl Functional for #struct_name { + type Result = #result_name; + } + } + } else { + quote! {} + }; + + let output = quote! { + #[#t] + #[serde(tag = "@type", rename = #id)] + pub struct #struct_name { + #(#fields),* + } + + #traits + }; + + let syntax_tree = syn::parse2(output.clone()).unwrap(); + formatted += &prettyplease::unparse(&syntax_tree); + } + } + + let out_dir = env::var_os("OUT_DIR").unwrap(); + let dest_path = Path::new(&out_dir) + .join(self.output); + + eprintln!("dest_path = {:?}", dest_path); + + fs::write(dest_path, formatted).unwrap(); + + Ok(()) + } +} + +fn generate_type_name(s: &str) -> String { + let (ns, name) = s.rsplit_once('.').unwrap_or(("", s)); + + let boxed_prefix = if name.starts_with(|c: char| c.is_uppercase()) { + "Boxed" + } else { "" }; + + let ns_prefix = ns.split('.') + .map(|f| f.to_case(UpperCamel)) + .collect::>() + .join(""); + + format!("{}{}{}", ns_prefix, boxed_prefix, name.to_case(UpperCamel)) +} + +fn structure_ident(s: &str) -> Ident { + format_ident!("{}", generate_type_name(s)) +} diff --git a/tonlibjson-client/examples/last_n_transactions.rs b/tonlibjson-client/examples/last_n_transactions.rs index 196a1e40..1ab573bf 100644 --- a/tonlibjson-client/examples/last_n_transactions.rs +++ b/tonlibjson-client/examples/last_n_transactions.rs @@ -30,9 +30,9 @@ async fn main() -> anyhow::Result<()> { tracing::info!(tx = ?tx); - let address = tx.account.into_internal(block.workchain).to_string(); + let address = tx.into_internal_string(block.workchain); match ton.get_account_state(&address).await { - Ok(account) => tracing::info!("{}: {}", &address, account["balance"].as_str().unwrap()), + Ok(account) => tracing::info!("{}: {}", &address, account.balance), Err(e) => tracing::error!("{:?}", e) } }).await; diff --git a/tonlibjson-client/src/balance.rs b/tonlibjson-client/src/balance.rs index 4d3251a3..46f7054b 100644 --- a/tonlibjson-client/src/balance.rs +++ b/tonlibjson-client/src/balance.rs @@ -4,7 +4,7 @@ use futures::{TryFutureExt, FutureExt}; use derive_new::new; use tower::{Service, ServiceExt}; use tower::discover::ServiceList; -use crate::block::{BlockIdExt, BlocksGetShards, BlocksLookupBlock, BlocksShards, GetMasterchainInfo, MasterchainInfo}; +use crate::block::{BlocksGetMasterchainInfo, BlocksGetShards, BlocksLookupBlock, BlocksMasterchainInfo, BlocksShards, TonBlockIdExt}; use crate::cursor_client::InnerClient; use crate::error::ErrorService; use crate::request::{Callable, Specialized}; @@ -31,8 +31,8 @@ impl Service for Balance where R: Routable + Callable { } } -impl Service> for Balance { - type Response = MasterchainInfo; +impl Service> for Balance { + type Response = BlocksMasterchainInfo; type Error = anyhow::Error; type Future = Pin> + Send>>; @@ -40,11 +40,11 @@ impl Service> for Balance { self.router.poll_ready(cx) } - fn call(&mut self, req: Specialized) -> Self::Future { + fn call(&mut self, req: Specialized) -> Self::Future { self.router .call(&req.route()) .and_then(|svc| ErrorService::new(tower::balance::p2c::Balance::new( - ServiceList::new::>(svc))).oneshot(req)) + ServiceList::new::>(svc))).oneshot(req)) .boxed() } } @@ -70,7 +70,7 @@ impl Service> for Balance { // TODO[akostylev0] generics impl Service> for Balance { - type Response = BlockIdExt; + type Response = TonBlockIdExt; type Error = anyhow::Error; type Future = Pin> + Send>>; diff --git a/tonlibjson-client/src/block.rs b/tonlibjson-client/src/block.rs index f7977670..fe3a966a 100644 --- a/tonlibjson-client/src/block.rs +++ b/tonlibjson-client/src/block.rs @@ -1,37 +1,34 @@ +use std::any::{TypeId}; use std::cmp::Ordering; -use std::error::Error; +use std::error::Error as StdError; use std::fmt::{Display, Formatter}; use std::time::Duration; use std::str::FromStr; use derive_new::new; use serde::{Serialize, Deserialize}; -use serde_json::Value; -use crate::address::{AccountAddressData, ShardContextAccountAddress}; -use crate::deserialize::{deserialize_number_from_string, deserialize_default_as_none, deserialize_ton_account_balance, deserialize_empty_as_none, serialize_none_as_empty}; +use serde::de::DeserializeOwned; +use crate::address::{AccountAddressData, InternalAccountAddress, ShardContextAccountAddress}; use crate::router::{BlockCriteria, Route, Routable}; use crate::request::Requestable; +use crate::deserialize::{deserialize_number_from_string, deserialize_default_as_none, deserialize_ton_account_balance, serialize_none_as_empty, deserialize_empty_as_none}; -#[derive(Debug, Serialize, Default, Clone)] -#[serde(tag = "@type", rename = "sync")] -pub struct Sync {} - -impl Requestable for Sync { - type Response = BlockIdExt; - - fn timeout(&self) -> Duration { - Duration::from_secs(5 * 60) - } +pub trait Functional { + type Result; } -#[derive(Debug, Serialize, Clone, Hash, PartialEq, Eq)] -#[serde(tag = "@type", rename = "blocks.getBlockHeader")] -pub struct BlocksGetBlockHeader { - pub id: BlockIdExt -} +type Double = f64; +type Int31 = i32; // "#" / nat type +type Int32 = i32; +type Int53 = i64; +type Int64 = i64; +type Int256 = String; // TODO[akostylev0] idk actually +type BoxedBool = bool; +type Bytes = String; +type SecureString = String; +type SecureBytes = String; +type Vector = Vec; -impl Requestable for BlocksGetBlockHeader { - type Response = BlockHeader; -} +include!(concat!(env!("OUT_DIR"), "/generated.rs")); impl Routable for BlocksGetBlockHeader { fn route(&self) -> Route { @@ -39,41 +36,9 @@ impl Routable for BlocksGetBlockHeader { } } -impl BlocksGetBlockHeader { - pub fn new(id: BlockIdExt) -> Self { - Self { - id - } - } -} - -#[derive(Debug, Hash, Serialize, Deserialize, Clone, Eq, PartialEq, new)] -#[serde(tag = "@type", rename = "ton.blockIdExt")] -pub struct BlockIdExt { - #[serde(deserialize_with = "deserialize_number_from_string")] - pub workchain: i32, - #[serde(deserialize_with = "deserialize_number_from_string")] - pub shard: i64, - #[serde(deserialize_with = "deserialize_number_from_string")] - pub seqno: i32, - pub root_hash: String, - pub file_hash: String, -} - -#[derive(Debug, Hash, Serialize, Deserialize, Clone, Eq, PartialEq, new)] -#[serde(tag = "@type", rename = "ton.blockId")] -pub struct BlockId { - #[serde(deserialize_with = "deserialize_number_from_string")] - pub workchain: i32, - #[serde(deserialize_with = "deserialize_number_from_string")] - pub shard: i64, - #[serde(deserialize_with = "deserialize_number_from_string")] - pub seqno: i32 -} - -impl From for BlockId { - fn from(block: BlockIdExt) -> Self { - BlockId { +impl From for TonBlockId { + fn from(block: TonBlockIdExt) -> Self { + TonBlockId { workchain: block.workchain, shard: block.shard, seqno: block.seqno @@ -81,33 +46,9 @@ impl From for BlockId { } } -#[derive(Debug, Deserialize, Clone)] -#[serde(tag = "@type", rename = "blocks.header")] -pub struct BlockHeader { - pub id: BlockIdExt, - pub global_id: i32, - pub version: i32, - pub after_merge: bool, - pub after_split: bool, - pub before_split: bool, - pub want_merge: bool, - pub validator_list_hash_short: i32, - pub catchain_seqno: i32, - pub min_ref_mc_seqno: i32, - pub is_key_block: bool, - pub prev_key_block_seqno: i32, - #[serde(deserialize_with = "deserialize_number_from_string")] - pub start_lt: i64, - #[serde(deserialize_with = "deserialize_number_from_string")] - pub end_lt: i64, - #[serde(deserialize_with = "deserialize_number_from_string")] - pub gen_utime: i64, - pub prev_blocks: Vec -} - -impl From for BlockId { - fn from(header: BlockHeader) -> Self { - BlockId { +impl From for TonBlockId { + fn from(header: BlocksHeader) -> Self { + TonBlockId { workchain: header.id.workchain, shard: header.id.shard, seqno: header.id.seqno @@ -115,270 +56,81 @@ impl From for BlockId { } } -#[derive(Debug, Deserialize, Clone)] -#[serde(tag = "@type", rename = "blocks.shortTxId")] -pub struct ShortTxId { - pub account: ShardContextAccountAddress, - pub hash: String, - #[serde(deserialize_with = "deserialize_number_from_string")] - pub lt: i64, - pub mode: u8, -} +impl BlocksShortTxId { + pub fn account(&self) -> &str { + &self.account + } -impl PartialEq for ShortTxId { - fn eq(&self, other: &Self) -> bool { - self.account == other.account - && self.hash == other.hash - && self.lt == other.lt + pub fn into_internal(self, chain_id: i32) -> InternalAccountAddress { + ShardContextAccountAddress::from_str(&self.account).unwrap().into_internal(chain_id) + } + + pub fn into_internal_string(self, chain_id: i32) -> String { + self.into_internal(chain_id).to_string() } } -#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] -#[serde(tag = "@type", rename = "blocks.masterchainInfo")] -pub struct MasterchainInfo { - pub init: BlockIdExt, - pub last: BlockIdExt, - pub state_root_hash: String, +impl PartialEq for BlocksShortTxId { + fn eq(&self, other: &Self) -> bool { + self.account == other.account && self.hash == other.hash && self.lt == other.lt + } } -impl PartialOrd for MasterchainInfo { +impl PartialOrd for BlocksMasterchainInfo { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl Ord for MasterchainInfo { +impl Ord for BlocksMasterchainInfo { fn cmp(&self, other: &Self) -> Ordering { self.last.seqno.cmp(&other.last.seqno) } } -#[derive(new, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] -#[serde(tag = "@type", rename = "internal.transactionId")] -pub struct InternalTransactionId { - pub hash: String, - #[serde(deserialize_with = "deserialize_number_from_string")] - pub lt: i64, -} - impl Default for InternalTransactionId { fn default() -> Self { - Self { - hash: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned(), - lt: 0 - } + Self { hash: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned(), lt: 0 } } } -#[derive(Deserialize, Serialize, Debug, Clone)] -#[serde(tag = "@type", rename = "accountAddress")] -pub struct AccountAddress { - #[serde(deserialize_with = "deserialize_empty_as_none", serialize_with = "serialize_none_as_empty")] - pub account_address: Option, -} - impl AccountAddress { + // TODO[akostylev0] pub fn new(account_address: &str) -> anyhow::Result { - Ok(Self { - account_address: Some(AccountAddressData::from_str(account_address)?) - }) + AccountAddressData::from_str(account_address)?; // validate + + Ok(Self { account_address: Some(account_address.to_owned()) }) } + // TODO[akostylev0] pub fn chain_id(&self) -> i32 { - // TODO[akostylev0] - self.account_address.as_ref().map(|d| d.chain_id).unwrap_or(-1) + self.account_address + .as_ref() + .and_then(|a| AccountAddressData::from_str(a).ok()) + .map(|d| d.chain_id) + .unwrap_or(-1) } } -#[derive(new, Debug, Serialize, Clone)] -#[serde(tag = "@type")] -#[serde(rename = "getShardAccountCell")] -pub struct GetShardAccountCell { - pub account_address: AccountAddress -} - -impl Requestable for GetShardAccountCell { - type Response = Cell; -} - -impl Routable for GetShardAccountCell { - fn route(&self) -> Route { Route::Latest } -} - -#[derive(new, Debug, Serialize, Clone)] -#[serde(tag = "@type")] -#[serde(rename = "getShardAccountCellByTransaction")] -pub struct GetShardAccountCellByTransaction { - pub account_address: AccountAddress, - pub transaction_id: InternalTransactionId -} - -impl Requestable for GetShardAccountCellByTransaction { - type Response = Cell; -} - +impl Routable for GetShardAccountCell {} impl Routable for GetShardAccountCellByTransaction { fn route(&self) -> Route { Route::Block { chain: self.account_address.chain_id(), criteria: BlockCriteria::LogicalTime(self.transaction_id.lt) } } } - -#[derive(new, Debug, Serialize, Clone)] -#[serde(tag = "@type")] -#[serde(rename = "raw.getAccountState")] -pub struct RawGetAccountState { - account_address: AccountAddress -} - -#[derive(Deserialize, Debug)] -#[serde(tag = "@type", rename = "raw.FullAccountState")] -pub struct RawFullAccountState { - #[serde(deserialize_with = "deserialize_ton_account_balance")] - pub balance: Option, - #[serde(deserialize_with = "deserialize_default_as_none")] - pub code: Option, - #[serde(deserialize_with = "deserialize_default_as_none")] - pub data: Option, - #[serde(deserialize_with = "deserialize_default_as_none")] - pub last_transaction_id: Option, - pub block_id: BlockIdExt, - #[serde(deserialize_with = "deserialize_default_as_none")] - pub frozen_hash: Option, - pub sync_utime: i64 -} - -impl Requestable for RawGetAccountState { - type Response = RawFullAccountState; -} - -impl Routable for RawGetAccountState { - fn route(&self) -> Route { - Route::Latest - } -} - -#[derive(new, Debug, Serialize, Clone)] -#[serde(tag = "@type")] -#[serde(rename = "raw.getAccountStateByTransaction")] -pub struct RawGetAccountStateByTransaction { - account_address: AccountAddress, - transaction_id: InternalTransactionId -} - -impl Requestable for RawGetAccountStateByTransaction { - type Response = RawFullAccountState; -} - +impl Routable for RawGetAccountState {} impl Routable for RawGetAccountStateByTransaction { fn route(&self) -> Route { Route::Block { chain: self.account_address.chain_id(), criteria: BlockCriteria::LogicalTime(self.transaction_id.lt) } } } - -#[derive(new, Debug, Serialize, Clone)] -#[serde(tag = "@type")] -#[serde(rename = "getAccountState")] -pub struct GetAccountState { - account_address: AccountAddress -} - -impl Requestable for GetAccountState { - type Response = Value; -} - -impl Routable for GetAccountState { - fn route(&self) -> Route { Route::Latest } -} - -#[derive(Deserialize, Serialize, Clone, Debug)] -#[serde(tag = "@type")] -pub enum MessageData { - #[serde(rename = "msg.dataRaw")] - Raw { body: String, init_state: String }, - #[serde(rename = "msg.dataText")] - Text { text: String }, - #[serde(rename = "msg.dataDecryptedText")] - DecryptedText { text: String }, - #[serde(rename = "msg.dataEncryptedText")] - EncryptedText { text: String } -} - -#[derive(Deserialize, Debug)] -#[serde(tag = "@type", rename = "raw.message")] -pub struct RawMessage { - pub source: AccountAddress, - pub destination: AccountAddress, - #[serde(deserialize_with = "deserialize_number_from_string")] - pub value: i64, - #[serde(deserialize_with = "deserialize_number_from_string")] - pub fwd_fee: i64, - #[serde(deserialize_with = "deserialize_number_from_string")] - pub ihr_fee: i64, - #[serde(deserialize_with = "deserialize_number_from_string")] - pub created_lt: i64, - pub body_hash: String, - pub msg_data: MessageData -} - -#[derive(Deserialize, Debug)] -#[serde(tag = "@type", rename = "raw.transaction")] -pub struct RawTransaction { - #[serde(deserialize_with = "deserialize_number_from_string")] - pub utime: i64, - pub data: String, - pub transaction_id: InternalTransactionId, - - #[serde(deserialize_with = "deserialize_number_from_string")] - pub fee: i64, - #[serde(deserialize_with = "deserialize_number_from_string")] - pub storage_fee: i64, - #[serde(deserialize_with = "deserialize_number_from_string")] - pub other_fee: i64, - - pub in_msg: RawMessage, - pub out_msgs: Vec, -} - -#[derive(Deserialize, Debug)] -pub struct RawTransactions { - pub transactions: Vec, - #[serde(deserialize_with = "deserialize_default_as_none")] - pub previous_transaction_id: Option -} - -#[derive(Debug, Serialize, Deserialize, Default, Clone)] -#[serde(tag = "@type", rename = "blocks.getMasterchainInfo")] -pub struct GetMasterchainInfo {} - -impl Requestable for GetMasterchainInfo { - type Response = MasterchainInfo; -} - -impl Routable for GetMasterchainInfo { - fn route(&self) -> Route { - Route::Latest - } -} - -#[derive(Debug, Serialize, Clone, Hash, Eq, PartialEq)] -#[serde(tag = "@type", rename = "blocks.lookupBlock")] -pub struct BlocksLookupBlock { - pub mode: i32, - pub id: BlockId, - pub lt: i64, - pub utime: i32 -} - -impl Requestable for BlocksLookupBlock { - type Response = BlockIdExt; -} - +impl Routable for GetAccountState {} +impl Routable for BlocksGetMasterchainInfo {} impl Routable for BlocksLookupBlock { fn route(&self) -> Route { let criteria = match self.mode { - 1 => BlockCriteria::Seqno { shard: self.id.shard, seqno: self.id.seqno }, 2 => BlockCriteria::LogicalTime(self.lt), - _ => BlockCriteria::Seqno { shard: self.id.shard, seqno: self.id.seqno } + 1 | _ => BlockCriteria::Seqno { shard: self.id.shard, seqno: self.id.seqno } }; Route::Block { chain: self.id.workchain, criteria } @@ -386,63 +138,23 @@ impl Routable for BlocksLookupBlock { } impl BlocksLookupBlock { - pub fn seqno(id: BlockId) -> Self { - let mode = 1; - - Self { - mode, - id, - lt: 0, - utime: 0 - } + pub fn seqno(id: TonBlockId) -> Self { + Self { mode: 1, id, lt: 0, utime: 0 } } - pub fn logical_time(id: BlockId, lt: i64) -> Self { - let mode = 2; - - Self { - mode, - id, - lt, - utime: 0 - } + pub fn logical_time(id: TonBlockId, lt: i64) -> Self { + Self { mode: 2, id, lt, utime: 0 } } } -#[derive(new, Debug, Serialize, Clone)] -#[serde(tag = "@type", rename = "blocks.getShards")] -pub struct BlocksGetShards { - pub id: BlockIdExt -} - -impl Requestable for BlocksGetShards { - type Response = BlocksShards; -} - impl Routable for BlocksGetShards { fn route(&self) -> Route { Route::Block { chain: self.id.workchain, criteria: BlockCriteria::Seqno { shard: self.id.shard, seqno: self.id.seqno } } } } -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(tag = "@type", rename = "blocks.shards")] -pub struct BlocksShards { - pub shards: Vec, -} - -#[derive(Debug, Serialize, Clone)] -#[serde(tag = "@type")] -#[serde(rename = "blocks.getTransactions")] -pub struct BlocksGetTransactions { - id: BlockIdExt, - mode: i32, - count: i32, - after: AccountTransactionId -} - impl BlocksGetTransactions { - pub fn unverified(block_id: BlockIdExt, after: Option, reverse: bool, count: i32) -> Self { + pub fn unverified(block_id: TonBlockIdExt, after: Option, reverse: bool, count: i32) -> Self { let count = if count > 256 { 256 } else { count }; let mode = 1 + 2 + 4 + if after.is_some() { 128 } else { 0 } @@ -456,7 +168,7 @@ impl BlocksGetTransactions { } } - pub fn verified(block_id: BlockIdExt, after: Option, reverse: bool, count: i32) -> Self { + pub fn verified(block_id: TonBlockIdExt, after: Option, reverse: bool, count: i32) -> Self { let count = if count > 256 { 256 } else { count }; let mode = 32 + 1 + 2 + 4 + if after.is_some() { 128 } else { 0 } @@ -471,216 +183,45 @@ impl BlocksGetTransactions { } } -impl Requestable for BlocksGetTransactions { - type Response = BlocksTransactions; -} - impl Routable for BlocksGetTransactions { fn route(&self) -> Route { Route::Block { chain: self.id.workchain, criteria: BlockCriteria::Seqno { shard: self.id.shard, seqno: self.id.seqno } } } } -#[derive(Debug, Deserialize)] -pub struct BlocksTransactions { - pub id: BlockIdExt, - pub incomplete: bool, - pub req_count: u32, - pub transactions: Vec, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(tag = "@type", rename = "blocks.accountTransactionId")] -pub struct AccountTransactionId { - pub account: String, - #[serde(deserialize_with = "deserialize_number_from_string")] - pub lt: i64, -} - -impl Default for AccountTransactionId { +impl Default for BlocksAccountTransactionId { fn default() -> Self { - Self { - account: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_string(), - lt: 0, - } - } -} - -impl From<&ShortTxId> for AccountTransactionId { - fn from(v: &ShortTxId) -> Self { - AccountTransactionId { - account: v.account.to_string(), - lt: v.lt, - } + Self { account: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_string(), lt: 0, } } } -#[derive(new, Debug, Serialize, Deserialize)] -#[serde(tag = "@type", rename = "raw.sendMessage")] -pub struct RawSendMessage { - pub body: String, -} - -impl Requestable for RawSendMessage { - // TODO[akostylev0] - type Response = Value; -} - -impl Routable for RawSendMessage { - fn route(&self) -> Route { - Route::Latest +impl From<&BlocksShortTxId> for BlocksAccountTransactionId { + fn from(v: &BlocksShortTxId) -> Self { + Self { account: v.account.to_string(), lt: v.lt } } } +impl Routable for RawSendMessage {} +impl Routable for RawSendMessageReturnHash {} +impl Routable for SmcLoad {} -#[derive(new, Debug, Serialize, Deserialize)] -#[serde(tag = "@type", rename = "raw.sendMessageReturnHash")] -pub struct RawSendMessageReturnHash { - pub body: String, +impl SmcBoxedMethodId { + pub fn by_name(name: &str) -> Self { Self::SmcMethodIdName(SmcMethodIdName { name: name.to_owned() })} } -impl Requestable for RawSendMessageReturnHash { - type Response = RawExtMessageInfo; -} -impl Routable for RawSendMessageReturnHash { - fn route(&self) -> Route { Route::Latest } -} - -#[derive(Deserialize)] -pub struct RawExtMessageInfo { - pub hash: String -} - -#[derive(Debug, Serialize, Clone)] -#[serde(tag = "@type", rename = "smc.load")] -pub struct SmcLoad { - pub account_address: AccountAddress -} - -impl Requestable for SmcLoad { - type Response = SmcInfo; -} - -impl Routable for SmcLoad { - fn route(&self) -> Route { Route::Latest } -} - -impl SmcLoad { - pub fn new(address: AccountAddress) -> Self { - Self { - account_address: address - } - } -} - -#[derive(Debug, Serialize, Clone)] -#[serde(tag = "@type", rename = "smc.runGetMethod")] -pub struct SmcRunGetMethod { - id: i64, - method: SmcMethodId, - stack: SmcStack -} - -impl Requestable for SmcRunGetMethod { - type Response = Value; -} - -impl SmcRunGetMethod { - pub fn new(contract_id: i64, method: SmcMethodId, stack: SmcStack) -> Self { - Self { - id: contract_id, - method, - stack +// TODO[akostylev0] +impl Requestable for T where T: Functional + Serialize + Send + std::marker::Sync + 'static, + T::Result: DeserializeOwned + Send + std::marker::Sync + 'static { + type Response = T::Result; + fn timeout(&self) -> Duration { + if TypeId::of::() == TypeId::of::() { + Duration::from_secs(5 * 60) + } else { + Duration::from_secs(3) } } } -pub type SmcStack = Vec; - -#[derive(new, Debug, Serialize, Clone)] -#[serde(tag = "@type")] -pub enum SmcMethodId { - #[serde(rename = "smc.methodIdNumber")] - Number { number: i32 }, - #[serde(rename = "smc.methodIdName")] - Name { name: String } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(tag = "@type")] -#[serde(rename = "tvm.slice")] -pub struct Slice { - pub bytes: String -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(tag = "@type")] -#[serde(rename = "tvm.cell")] -pub struct Cell { - pub bytes: String -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(tag = "@type")] -#[serde(rename = "tvm.numberDecimal")] -pub struct Number { - pub number: String -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(tag = "@type")] -#[serde(rename = "tvm.tuple")] -pub struct Tuple { - pub elements: Vec -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(tag = "@type")] -#[serde(rename = "tvm.list")] -pub struct List { - pub elements: Vec -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(tag = "@type")] -pub enum StackEntry { - #[serde(rename = "tvm.stackEntrySlice")] - Slice { slice: Slice }, - #[serde(rename = "tvm.stackEntryCell")] - Cell { cell: Cell }, - #[serde(rename = "tvm.stackEntryNumber")] - Number { number: Number }, - #[serde(rename = "tvm.stackEntryTuple")] - Tuple { tuple: Tuple }, - #[serde(rename = "tvm.stackEntryList")] - List { list: List }, - - #[serde(rename = "tvm.stackEntryUnsupported")] - Unsupported -} - -#[derive(Debug, Deserialize)] -#[serde(tag = "smc.info")] -pub struct SmcInfo { - pub id: i64 -} - -#[derive(new, Debug, Serialize, Clone)] -#[serde(tag = "@type")] -#[serde(rename = "raw.getTransactionsV2")] -pub struct RawGetTransactionsV2 { - pub account_address: AccountAddress, - from_transaction_id: InternalTransactionId, - #[new(value = "16")] - count: i8, - #[new(value = "false")] - try_decode_messages: bool -} - -impl Requestable for RawGetTransactionsV2 { - type Response = RawTransactions; -} - impl Routable for RawGetTransactionsV2 { fn route(&self) -> Route { Route::Block { @@ -706,25 +247,25 @@ impl Display for TonError { } } -impl Error for TonError { - fn source(&self) -> Option<&(dyn Error + 'static)> { +impl StdError for TonError { + fn source(&self) -> Option<&(dyn StdError + 'static)> { None } } #[derive(new, Serialize, Clone)] -#[serde(tag = "@type")] -#[serde(rename = "withBlock")] -pub struct WithBlock { - pub id: BlockIdExt, +#[serde(tag = "@type", rename = "withBlock")] +pub struct WithBlock where T : Functional { + pub id: TonBlockIdExt, pub function: T } -impl Requestable for WithBlock where T : Requestable { +impl Requestable for WithBlock where T : Requestable { type Response = T::Response; + fn timeout(&self) -> Duration { self.function.timeout() } } -impl Routable for WithBlock { +impl Routable for WithBlock { fn route(&self) -> Route { Route::Block { chain: self.id.workchain, @@ -735,7 +276,7 @@ impl Routable for WithBlock { #[cfg(test)] mod tests { - use crate::block::{Cell, List, Number, Slice, StackEntry, Tuple, SmcMethodId, AccountAddress}; + use super::*; use serde_json::json; use tracing_test::traced_test; @@ -774,32 +315,32 @@ mod tests { #[test] fn slice_correct_json() { - let slice = Slice { bytes: "test".to_string() }; + let slice = TvmSlice { bytes: "test".to_string() }; assert_eq!(serde_json::to_string(&slice).unwrap(), "{\"@type\":\"tvm.slice\",\"bytes\":\"test\"}") } #[test] fn cell_correct_json() { - let cell = Cell { bytes: "test".to_string() }; + let cell = TvmCell { bytes: "test".to_string() }; assert_eq!(serde_json::to_string(&cell).unwrap(), "{\"@type\":\"tvm.cell\",\"bytes\":\"test\"}") } #[test] fn number_correct_json() { - let number = Number { number: "100.2".to_string() }; + let number = TvmNumberDecimal { number: "100.2".to_string() }; assert_eq!(serde_json::to_string(&number).unwrap(), "{\"@type\":\"tvm.numberDecimal\",\"number\":\"100.2\"}") } #[test] fn stack_entry_correct_json() { - let slice = StackEntry::Slice { slice: Slice { bytes: "test".to_string() }}; - let cell = StackEntry::Cell { cell: Cell { bytes: "test".to_string() }}; - let number = StackEntry::Number { number: Number { number: "123".to_string() }}; - let tuple = StackEntry::Tuple { tuple: Tuple { elements: vec![slice.clone(), cell.clone()] }}; - let list = StackEntry::List { list: List { elements: vec![slice.clone(), tuple.clone()] }}; + let slice = TvmBoxedStackEntry::TvmStackEntrySlice(TvmStackEntrySlice { slice: TvmSlice { bytes: "test".to_string() } }); + let cell = TvmBoxedStackEntry::TvmStackEntryCell(TvmStackEntryCell { cell: TvmCell { bytes: "test".to_string() } }); + let number = TvmBoxedStackEntry::TvmStackEntryNumber(TvmStackEntryNumber { number: TvmNumberDecimal { number: "123".to_string() } }); + let tuple = TvmBoxedStackEntry::TvmStackEntryTuple(TvmStackEntryTuple { tuple: TvmTuple { elements: vec![slice.clone(), cell.clone()] } }); + let list = TvmBoxedStackEntry::TvmStackEntryList(TvmStackEntryList { list: TvmList { elements: vec![slice.clone(), tuple.clone()] } }); assert_eq!(serde_json::to_string(&slice).unwrap(), "{\"@type\":\"tvm.stackEntrySlice\",\"slice\":{\"@type\":\"tvm.slice\",\"bytes\":\"test\"}}"); assert_eq!(serde_json::to_string(&cell).unwrap(), "{\"@type\":\"tvm.stackEntryCell\",\"cell\":{\"@type\":\"tvm.cell\",\"bytes\":\"test\"}}"); @@ -810,8 +351,8 @@ mod tests { #[test] fn smc_method_id() { - let number = SmcMethodId::Number { number: 123 }; - let name = SmcMethodId::Name { name: "getOwner".to_owned() }; + let number = SmcBoxedMethodId::SmcMethodIdNumber(SmcMethodIdNumber { number: 123 }) ; + let name = SmcBoxedMethodId::SmcMethodIdName(SmcMethodIdName { name: "getOwner".to_owned() }); assert_eq!(serde_json::to_value(number).unwrap(), json!({ "@type": "smc.methodIdNumber", diff --git a/tonlibjson-client/src/client.rs b/tonlibjson-client/src/client.rs index 07e407f3..4fee094b 100644 --- a/tonlibjson-client/src/client.rs +++ b/tonlibjson-client/src/client.rs @@ -177,7 +177,7 @@ mod tests { use tower::ServiceExt; use tracing_test::traced_test; use uuid::Uuid; - use crate::block::GetMasterchainInfo; + use crate::block::BlocksGetMasterchainInfo; use crate::client::{Client, Request}; #[tokio::test] @@ -185,7 +185,7 @@ mod tests { async fn not_initialized_call() { let mut client = Client::default(); - let resp = (&mut client).oneshot(GetMasterchainInfo::default()).await; + let resp = (&mut client).oneshot(BlocksGetMasterchainInfo::default()).await; assert_eq!("Ton error occurred with code 400, message library is not inited", resp.unwrap_err().to_string()) } diff --git a/tonlibjson-client/src/cursor_client.rs b/tonlibjson-client/src/cursor_client.rs index fd355e22..9a6a83a8 100644 --- a/tonlibjson-client/src/cursor_client.rs +++ b/tonlibjson-client/src/cursor_client.rs @@ -23,8 +23,8 @@ use tracing::{instrument, trace}; use metrics::{absolute_counter, describe_counter, describe_gauge, gauge}; use quick_cache::sync::Cache; use crate::router::BlockCriteria; -use crate::block::{BlockIdExt, BlocksGetShards, BlocksShards, Sync}; -use crate::block::{BlockHeader, BlockId, BlocksLookupBlock, BlocksGetBlockHeader, GetMasterchainInfo, MasterchainInfo}; +use crate::block::{BlocksGetMasterchainInfo, BlocksGetShards, BlocksHeader, BlocksMasterchainInfo, BlocksShards, Sync, TonBlockId, TonBlockIdExt}; +use crate::block::{BlocksLookupBlock, BlocksGetBlockHeader}; use crate::client::Client; use crate::metric::ConcurrencyMetric; use crate::request::{Specialized, Callable}; @@ -37,19 +37,19 @@ type ShardId = (i32, i64); type Seqno = i32; #[derive(Debug, Clone, Default)] struct ShardBounds { - left: Option, - right: Option + left: Option, + right: Option } impl ShardBounds { - fn left(left: BlockHeader) -> Self { + fn left(left: BlocksHeader) -> Self { Self { left: Some(left), right: None } } - fn right(right: BlockHeader) -> Self { + fn right(right: BlocksHeader) -> Self { Self { left: None, right: Some(right) @@ -119,7 +119,7 @@ impl Registry { .and_then(|s| s.right.as_ref().map(|h| h.id.seqno)) } - fn upsert_left(&self, header: &BlockHeader) { + fn upsert_left(&self, header: &BlocksHeader) { let shard_id = (header.id.workchain, header.id.shard); self.update_shard_registry(&shard_id); @@ -132,7 +132,7 @@ impl Registry { .or_insert_with(|| ShardBounds::left(header.clone())); } - fn upsert_right(&self, header: &BlockHeader) { + fn upsert_right(&self, header: &BlocksHeader) { let shard_id = (header.id.workchain, header.id.shard); self.update_shard_registry(&shard_id); @@ -236,12 +236,12 @@ pub(crate) struct CursorClient { id: Cow<'static, str>, client: InnerClient, - masterchain_info_rx: Receiver>, + masterchain_info_rx: Receiver>, registry: Arc } impl CursorClient { - pub(crate) fn subscribe_masterchain_info(&self) -> Receiver> { + pub(crate) fn subscribe_masterchain_info(&self) -> Receiver> { self.masterchain_info_rx.clone() } @@ -304,7 +304,7 @@ impl CursorClient { _self } - fn last_block_loop(&self, mtx: Sender>) -> impl Future { + fn last_block_loop(&self, mtx: Sender>) -> impl Future { let id = self.id.clone(); let client = self.client.clone(); let registry = self.registry.clone(); @@ -325,8 +325,8 @@ impl CursorClient { } } -impl Service> for CursorClient { - type Response = MasterchainInfo; +impl Service> for CursorClient { + type Response = BlocksMasterchainInfo; type Error = anyhow::Error; type Future = Pin> + Send>>; @@ -340,7 +340,7 @@ impl Service> for CursorClient { } } - fn call(&mut self, _: Specialized) -> Self::Future { + fn call(&mut self, _: Specialized) -> Self::Future { let response = self.masterchain_info_rx.borrow().as_ref().unwrap().clone(); return ready(Ok(response)).boxed() @@ -366,7 +366,7 @@ impl Service> for CursorClient { // TODO[akostylev0] generics impl Service> for CursorClient { - type Response = BlockIdExt; + type Response = TonBlockIdExt; type Error = anyhow::Error; type Future = Pin> + Send>>; @@ -388,7 +388,7 @@ impl> Service for CursorClient { fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { if self.edges_defined() { - return Service::::poll_ready(&mut self.client, cx); + return Service::::poll_ready(&mut self.client, cx); } cx.waker().wake_by_ref(); @@ -409,7 +409,7 @@ impl Load for CursorClient { } } -async fn check_block_available(client: &mut InnerClient, block_id: BlockId) -> Result<(BlockHeader, Vec)> { +async fn check_block_available(client: &mut InnerClient, block_id: TonBlockId) -> Result<(BlocksHeader, Vec)> { let block_id = cached_block_id_ext(block_id, client).await?; let shards = cached_get_shards(&BlocksGetShards::new(block_id.clone()), client).await?; @@ -426,7 +426,7 @@ async fn check_block_available(client: &mut InnerClient, block_id: BlockId) -> R } #[instrument(skip_all, err, level = "trace")] -async fn find_first_blocks(client: &mut InnerClient, start: &BlockIdExt, lhs: Option, cur: Option) -> Result<(BlockHeader, Vec)> { +async fn find_first_blocks(client: &mut InnerClient, start: &TonBlockIdExt, lhs: Option, cur: Option) -> Result<(BlocksHeader, Vec)> { let length = start.seqno; let mut rhs = length; let mut lhs = lhs.unwrap_or(1); @@ -435,7 +435,7 @@ async fn find_first_blocks(client: &mut InnerClient, start: &BlockIdExt, lhs: Op let workchain = start.workchain; let shard = start.shard; - let mut block = check_block_available(client, BlockId::new(workchain, shard, cur)).await; + let mut block = check_block_available(client, TonBlockId::new(workchain, shard, cur)).await; let mut success = None; let mut hops = 0; @@ -451,7 +451,7 @@ async fn find_first_blocks(client: &mut InnerClient, start: &BlockIdExt, lhs: Op cur = (lhs + rhs) / 2; if cur == 0 { break; } - block = check_block_available(client, BlockId::new(workchain, shard, cur)).await; + block = check_block_available(client, TonBlockId::new(workchain, shard, cur)).await; if block.is_ok() { success = Some(block.as_ref().unwrap().clone()); } @@ -475,7 +475,7 @@ async fn find_first_blocks(client: &mut InnerClient, start: &BlockIdExt, lhs: Op Ok((master, work)) } -async fn fetch_last_headers(client: &mut InnerClient) -> Result<(BlockHeader, Vec)> { +async fn fetch_last_headers(client: &mut InnerClient) -> Result<(BlocksHeader, Vec)> { let master_chain_last_block_id = client.oneshot(Sync::default()).await?; let shards = cached_get_shards(&BlocksGetShards::new(master_chain_last_block_id.clone()), client).await?; @@ -494,8 +494,8 @@ async fn fetch_last_headers(client: &mut InnerClient) -> Result<(BlockHeader, Ve } -fn shards_cache() -> &'static Cache { - static CACHE: OnceLock> = OnceLock::new(); +fn shards_cache() -> &'static Cache { + static CACHE: OnceLock> = OnceLock::new(); CACHE.get_or_init(|| Cache::new(1024)) } @@ -505,33 +505,33 @@ async fn cached_get_shards(req: &BlocksGetShards, client: &mut InnerClient) -> R shards_cache().get_or_insert_async(&key, async { client.oneshot(req.clone()).await }).await } -fn block_cache() -> &'static Cache { - static CACHE: OnceLock> = OnceLock::new(); +fn block_cache() -> &'static Cache { + static CACHE: OnceLock> = OnceLock::new(); CACHE.get_or_init(|| Cache::new(1024)) } -async fn cached_block_id_ext(block_id: BlockId, client: &mut InnerClient) -> Result { +async fn cached_block_id_ext(block_id: TonBlockId, client: &mut InnerClient) -> Result { let req = BlocksLookupBlock::seqno(block_id); cached_lookup_block(&req, client).await } -async fn cached_lookup_block(req: &BlocksLookupBlock, client: &mut InnerClient) -> Result { +async fn cached_lookup_block(req: &BlocksLookupBlock, client: &mut InnerClient) -> Result { block_cache().get_or_insert_async(req, async { client.oneshot(req.clone()).await }).await } -fn block_header_cache() -> &'static Cache { - static CACHE: OnceLock> = OnceLock::new(); +fn block_header_cache() -> &'static Cache { + static CACHE: OnceLock> = OnceLock::new(); CACHE.get_or_init(|| Cache::new(1024)) } -async fn cached_block_header(req: &BlocksGetBlockHeader, client: &mut InnerClient) -> Result { +async fn cached_block_header(req: &BlocksGetBlockHeader, client: &mut InnerClient) -> Result { block_header_cache().get_or_insert_async(req, async { client.oneshot(req.clone()).await }).await } -async fn wait_for_block_header(block_id: BlockIdExt, client: InnerClient) -> Result { +async fn wait_for_block_header(block_id: TonBlockIdExt, client: InnerClient) -> Result { let retry = FibonacciBackoff::from_millis(512) .max_delay(Duration::from_millis(4096)) .map(jitter) @@ -549,12 +549,12 @@ struct FirstBlockDiscover { id: Cow<'static, str>, client: InnerClient, registry: Arc, - rx: Receiver>, - current: Option, + rx: Receiver>, + current: Option, } impl FirstBlockDiscover { - fn new(id: Cow<'static, str>, client: InnerClient, registry: Arc, rx: Receiver>) -> Self { + fn new(id: Cow<'static, str>, client: InnerClient, registry: Arc, rx: Receiver>) -> Self { Self { id, client, @@ -582,7 +582,7 @@ impl FirstBlockDiscover { } } - async fn next(&mut self, start: BlockIdExt) -> Result> { + async fn next(&mut self, start: TonBlockIdExt) -> Result> { if let Some(ref mfb) = self.current { if let Err(e) = (&mut self.client).oneshot(BlocksGetShards::new(mfb.id.clone())).await { trace!(seqno = mfb.id.seqno, e = ?e, "first block not available anymore"); @@ -612,8 +612,8 @@ struct LastBlockDiscover { id: Cow<'static, str>, client: InnerClient, registry: Arc, - current: Option, - mtx: Sender> + current: Option, + mtx: Sender> } impl LastBlockDiscover { @@ -621,7 +621,7 @@ impl LastBlockDiscover { let mut timer = interval(Duration::new(2, 1_000_000_000 / 2)); timer.set_missed_tick_behavior(MissedTickBehavior::Skip); - let mut current: Option = None; + let mut current: Option = None; loop { timer.tick().await; @@ -634,8 +634,8 @@ impl LastBlockDiscover { } } - async fn next(&mut self) -> Result> { - let mut masterchain_info = (&mut self.client).oneshot(GetMasterchainInfo::default()).await?; + async fn next(&mut self) -> Result> { + let mut masterchain_info = (&mut self.client).oneshot(BlocksGetMasterchainInfo::default()).await?; if let Some(ref current) = self.current { if current == &masterchain_info { return Ok(None); diff --git a/tonlibjson-client/src/make.rs b/tonlibjson-client/src/make.rs index 6c41eb9b..a96d8a45 100644 --- a/tonlibjson-client/src/make.rs +++ b/tonlibjson-client/src/make.rs @@ -6,7 +6,7 @@ use tower::limit::ConcurrencyLimitLayer; use tower::{Layer, Service, ServiceExt}; use tower::load::PeakEwma; use tracing::debug; -use crate::block::GetMasterchainInfo; +use crate::block::BlocksGetMasterchainInfo; use crate::client::Client; use crate::cursor_client::CursorClient; use crate::shared::SharedLayer; @@ -31,7 +31,7 @@ impl Service for ClientFactory { .build() .await?; - let _ = (&mut client).oneshot(GetMasterchainInfo::default()).await?; + let _ = (&mut client).oneshot(BlocksGetMasterchainInfo::default()).await?; Ok(client) }) diff --git a/tonlibjson-client/src/router.rs b/tonlibjson-client/src/router.rs index 1d107cc5..8c1e4ccc 100644 --- a/tonlibjson-client/src/router.rs +++ b/tonlibjson-client/src/router.rs @@ -10,18 +10,18 @@ use itertools::Itertools; use tokio::select; use tokio_stream::StreamMap; use tokio_stream::wrappers::WatchStream; -use crate::block::MasterchainInfo; +use crate::block::BlocksMasterchainInfo; use crate::cursor_client::CursorClient; use crate::discover::CursorClientDiscover; pub(crate) trait Routable { - fn route(&self) -> Route; + fn route(&self) -> Route { Route::Latest } } pub(crate) struct Router { discover: CursorClientDiscover, services: DashMap, - last_block: MergeStreamMap + last_block: MergeStreamMap } impl Router { diff --git a/tonlibjson-client/src/session.rs b/tonlibjson-client/src/session.rs index dfc4fa60..a2aaa2ae 100644 --- a/tonlibjson-client/src/session.rs +++ b/tonlibjson-client/src/session.rs @@ -4,7 +4,7 @@ use futures::TryFutureExt; use futures::FutureExt; use tower::{Service, ServiceExt}; use crate::router::Route; -use crate::block::{AccountAddress, SmcLoad, SmcMethodId, SmcRunGetMethod, SmcStack}; +use crate::block::{AccountAddress, SmcBoxedMethodId, SmcLoad, SmcRunGetMethod, TvmBoxedStackEntry}; use crate::error::Error; use crate::request::{Requestable, Callable}; use crate::router::Routable; @@ -12,8 +12,8 @@ use crate::router::Routable; #[derive(new, Clone)] pub struct RunGetMethod { address: AccountAddress, - method: SmcMethodId, - stack: SmcStack + method: SmcBoxedMethodId, + stack: Vec } impl + Send + 'static> Callable for RunGetMethod diff --git a/tonlibjson-client/src/ton.rs b/tonlibjson-client/src/ton.rs index 316f0022..b2b627e5 100644 --- a/tonlibjson-client/src/ton.rs +++ b/tonlibjson-client/src/ton.rs @@ -20,10 +20,10 @@ use tracing::{instrument, trace}; use url::Url; use std::str::FromStr; use tower::util::Either; -use crate::address::{InternalAccountAddress, ShardContextAccountAddress}; +use crate::address::InternalAccountAddress; use crate::balance::Balance; use crate::router::{BlockCriteria, Route, Router}; -use crate::block::{InternalTransactionId, RawTransaction, RawTransactions, MasterchainInfo, BlocksShards, BlockIdExt, AccountTransactionId, BlocksTransactions, ShortTxId, RawSendMessage, SmcStack, AccountAddress, BlocksGetTransactions, BlocksLookupBlock, BlockId, BlocksGetShards, BlocksGetBlockHeader, BlockHeader, RawGetTransactionsV2, RawGetAccountState, GetAccountState, GetMasterchainInfo, SmcMethodId, GetShardAccountCell, Cell, RawFullAccountState, WithBlock, RawGetAccountStateByTransaction, GetShardAccountCellByTransaction, RawSendMessageReturnHash}; +use crate::block::{InternalTransactionId, RawTransaction, RawTransactions, BlocksShards, BlocksTransactions, RawSendMessage, AccountAddress, BlocksGetTransactions, BlocksLookupBlock, BlocksGetShards, BlocksGetBlockHeader, RawGetTransactionsV2, RawGetAccountState, GetAccountState, GetShardAccountCell, RawFullAccountState, WithBlock, RawGetAccountStateByTransaction, GetShardAccountCellByTransaction, RawSendMessageReturnHash, BlocksMasterchainInfo, BlocksGetMasterchainInfo, TonBlockIdExt, TonBlockId, BlocksHeader, FullAccountState, BlocksAccountTransactionId, BlocksShortTxId, TvmBoxedStackEntry, SmcRunResult, SmcBoxedMethodId, TvmCell}; use crate::discover::{ClientDiscover, CursorClientDiscover}; use crate::error::ErrorService; use crate::helper::Side; @@ -214,10 +214,10 @@ impl TonClient { Ok(()) } - pub async fn get_masterchain_info(&self) -> anyhow::Result { + pub async fn get_masterchain_info(&self) -> anyhow::Result { self.client .clone() - .oneshot(Specialized::new(GetMasterchainInfo::default())) + .oneshot(Specialized::new(BlocksGetMasterchainInfo::default())) .await } @@ -227,14 +227,14 @@ impl TonClient { chain: i32, shard: i64, seqno: i32, - ) -> anyhow::Result { + ) -> anyhow::Result { if seqno <= 0 { return Err(anyhow!("seqno must be greater than 0")); } self.client .clone() - .oneshot(Specialized::new(BlocksLookupBlock::seqno(BlockId::new(chain, shard, seqno)))) + .oneshot(Specialized::new(BlocksLookupBlock::seqno(TonBlockId::new(chain, shard, seqno)))) .await } @@ -243,14 +243,14 @@ impl TonClient { chain: i32, shard: i64, lt: i64, - ) -> anyhow::Result { + ) -> anyhow::Result { if lt <= 0 { return Err(anyhow!("lt must be greater than 0")); } self.client .clone() - .oneshot(Specialized::new(BlocksLookupBlock::logical_time(BlockId::new(chain, shard, 0), lt))) + .oneshot(Specialized::new(BlocksLookupBlock::logical_time(TonBlockId::new(chain, shard, 0), lt))) .await } @@ -265,7 +265,7 @@ impl TonClient { .await } - pub async fn get_shards_by_block_id(&self, block_id: BlockIdExt) -> anyhow::Result> { + pub async fn get_shards_by_block_id(&self, block_id: TonBlockIdExt) -> anyhow::Result> { if block_id.workchain != -1 { return Err(anyhow!("workchain must be -1")) } @@ -282,7 +282,7 @@ impl TonClient { chain: i32, shard: i64, seqno: i32, - ) -> anyhow::Result { + ) -> anyhow::Result { let id = self.look_up_block_by_seqno(chain, shard, seqno).await?; self.client @@ -302,7 +302,7 @@ impl TonClient { } #[instrument(skip_all, err)] - pub async fn raw_get_account_state_on_block(&self, address: &str, block_id: BlockIdExt) -> anyhow::Result { + pub async fn raw_get_account_state_on_block(&self, address: &str, block_id: TonBlockIdExt) -> anyhow::Result { let account_address = AccountAddress::new(address)?; self.client @@ -312,7 +312,7 @@ impl TonClient { } // TODO[akostylev0]: (optimization) use BlockId instead of BlockIdExt - pub async fn raw_get_account_state_at_least_block(&self, address: &str, block_id: &BlockIdExt) -> anyhow::Result { + pub async fn raw_get_account_state_at_least_block(&self, address: &str, block_id: &TonBlockIdExt) -> anyhow::Result { let route = Route::Block { chain: block_id.workchain, criteria: BlockCriteria::Seqno { shard: block_id.shard, seqno: block_id.seqno } }; let account_address = AccountAddress::new(address)?; @@ -332,7 +332,7 @@ impl TonClient { .await } - pub async fn get_account_state(&self, address: &str) -> anyhow::Result { + pub async fn get_account_state(&self, address: &str) -> anyhow::Result { let account_address = AccountAddress::new(address)?; self.client @@ -351,14 +351,14 @@ impl TonClient { self.client .clone() - .oneshot(RawGetTransactionsV2::new(address, from_tx.clone())) + .oneshot(RawGetTransactionsV2::new(address, from_tx.clone(), 16, false)) .await } pub async fn blocks_get_transactions( &self, - block: &BlockIdExt, - tx: Option, + block: &TonBlockIdExt, + tx: Option, reverse: bool, count: i32 ) -> anyhow::Result { @@ -375,8 +375,8 @@ impl TonClient { pub async fn blocks_get_transactions_verified( &self, - block: &BlockIdExt, - tx: Option, + block: &TonBlockIdExt, + tx: Option, reverse: bool, count: i32 ) -> anyhow::Result { @@ -391,11 +391,13 @@ impl TonClient { .await } - pub async fn send_message(&self, message: &str) -> anyhow::Result { + pub async fn send_message(&self, message: &str) -> anyhow::Result<()> { self.client .clone() .oneshot(RawSendMessage::new(message.to_string())) - .await + .await?; + + Ok(()) } pub async fn send_message_returning_hash(&self, message: &str) -> anyhow::Result { @@ -406,7 +408,7 @@ impl TonClient { .await } - pub fn get_block_tx_stream_unordered(&self, block: &BlockIdExt) -> impl Stream> + 'static { + pub fn get_block_tx_stream_unordered(&self, block: &TonBlockIdExt) -> impl Stream> + 'static { let streams = Side::values().map(move |side| { (side, self.get_block_tx_stream(block, side.is_right()).boxed()) }); @@ -430,13 +432,13 @@ impl TonClient { pub fn get_block_tx_stream( &self, - block: &BlockIdExt, + block: &TonBlockIdExt, reverse: bool - ) -> impl Stream> + 'static { + ) -> impl Stream> + 'static { struct State { - last_tx: Option, + last_tx: Option, incomplete: bool, - block: BlockIdExt, + block: TonBlockIdExt, this: TonClient, exp: u32 } @@ -638,9 +640,9 @@ impl TonClient { }).try_flatten() } - pub async fn run_get_method(&self, address: String, method: String, stack: SmcStack) -> anyhow::Result { + pub async fn run_get_method(&self, address: String, method: String, stack: Vec) -> anyhow::Result { let address = AccountAddress::new(&address)?; - let method = SmcMethodId::new_name(method); + let method = SmcBoxedMethodId::by_name(&method); self.client .clone() @@ -648,7 +650,7 @@ impl TonClient { .await } - pub async fn get_shard_account_cell(&self, address: &str) -> anyhow::Result { + pub async fn get_shard_account_cell(&self, address: &str) -> anyhow::Result { let address = AccountAddress::new(address)?; self.client @@ -657,7 +659,7 @@ impl TonClient { .await } - pub async fn get_shard_account_cell_on_block(&self, address: &str, block: BlockIdExt) -> anyhow::Result { + pub async fn get_shard_account_cell_on_block(&self, address: &str, block: TonBlockIdExt) -> anyhow::Result { let address = AccountAddress::new(address)?; self.client @@ -667,7 +669,7 @@ impl TonClient { } // TODO[akostylev0]: (optimization) use BlockId instead of BlockIdExt - pub async fn get_shard_account_cell_at_least_block(&self, address: &str, block_id: &BlockIdExt) -> anyhow::Result { + pub async fn get_shard_account_cell_at_least_block(&self, address: &str, block_id: &TonBlockIdExt) -> anyhow::Result { let route = Route::Block { chain: block_id.workchain, criteria: BlockCriteria::Seqno { shard: block_id.shard, seqno: block_id.seqno } }; let address = AccountAddress::new(address)?; @@ -677,7 +679,7 @@ impl TonClient { .await } - pub async fn get_shard_account_cell_by_transaction(&self, address: &str, transaction: InternalTransactionId) -> anyhow::Result { + pub async fn get_shard_account_cell_by_transaction(&self, address: &str, transaction: InternalTransactionId) -> anyhow::Result { let address = AccountAddress::new(address)?; self.client @@ -686,33 +688,34 @@ impl TonClient { .await } - pub fn get_accounts_in_block_stream(&self, block: &BlockIdExt) -> impl TryStream + 'static { + pub fn get_accounts_in_block_stream(&self, block: &TonBlockIdExt) -> impl TryStream + 'static { let chain = block.workchain; let streams = Side::values().map(move |side| { (side, self.get_block_tx_stream(block, side.is_right()).boxed()) }); let stream_map = StreamMap::from_iter(streams); - let stream = async_stream::try_stream! { + let stream = try_stream! { let mut last = HashMap::with_capacity(2); for await (key, tx) in stream_map { let tx = tx?; if let Some(addr) = last.get(&key.opposite()) { - if addr == &tx.account { return } + if addr == tx.account() { return } } if let Some(addr) = last.get(&key) { - if addr == &tx.account { continue } + if addr == tx.account() { continue } } - last.insert(key, tx.account.clone()); - yield tx.account; + last.insert(key, tx.account().to_owned()); + + yield tx.into_internal(chain); } }; - stream.map_ok(move |a: ShardContextAccountAddress| a.into_internal(chain)) + stream } #[instrument(skip_all, err)] @@ -727,7 +730,7 @@ impl TonClient { let workchain = start.workchain; let shard = start.shard; - let mut tx = self.check_account_available(account, &BlockId::new(workchain, shard, cur)).await; + let mut tx = self.check_account_available(account, &TonBlockId::new(workchain, shard, cur)).await; while lhs < rhs { // TODO[akostylev0] specify error @@ -745,7 +748,7 @@ impl TonClient { trace!("lhs: {}, rhs: {}, cur: {}", lhs, rhs, cur); - tx = self.check_account_available(account, &BlockId::new(workchain, shard, cur)).await; + tx = self.check_account_available(account, &TonBlockId::new(workchain, shard, cur)).await; } let tx = tx?; @@ -755,7 +758,7 @@ impl TonClient { Ok(tx) } - async fn check_account_available(&self, account: &str, block: &BlockId) -> anyhow::Result { + async fn check_account_available(&self, account: &str, block: &TonBlockId) -> anyhow::Result { let block = self .look_up_block_by_seqno(block.workchain, block.shard, block.seqno).await?; let state = self