diff --git a/.gitignore b/.gitignore index 116ac90..0a2568a 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ /client/resources/config/local /client/var /var +/resources diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..726c615 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +### v0.20.0 +* Resolve be-1426 "use lite to adjust init block" +* Ni: add-get-account-state-by-transaction +* Impl #be-1317: serialize_dict +* Impl #be-1412: partialeq for invalidmesage diff --git a/Cargo.toml b/Cargo.toml index 0e9344e..c26ff28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ resolver = "2" [workspace.package] -version = "0.19.2" +version = "0.20.0" edition = "2021" description = "Rust SDK for The Open Network" license = "MIT" @@ -48,8 +48,12 @@ thiserror = "1" tokio = { version = "1", features = ["rt","macros"] } tokio-retry = "0.3" tokio-test = "0.4" +ton_liteapi = { git = "https://github.com/ston-fi/lite-client", branch = "async", version = "0.1.0" } +adnl = "2.0" tonlib-sys = "=2024.9.0" +bitvec = "1.0" +tokio-tower = "0.6.0" +tower = "0.5.1" # internal deps -tonlib-core = { version = "0" } - +tonlib-core = { version = "0", path = "core" } diff --git a/client/Cargo.toml b/client/Cargo.toml index 041f729..313bc6e 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -33,6 +33,11 @@ dashmap.workspace = true futures.workspace = true hex.workspace = true +ton_liteapi.workspace = true +adnl.workspace = true +tokio-tower.workspace = true +tower = {workspace = true, features = ["util"] } + lazy_static.workspace = true log.workspace = true log4rs.workspace = true @@ -52,7 +57,5 @@ tokio.workspace = true tokio-retry.workspace = true tokio-test.workspace = true tonlib-sys.workspace = true -tonlib-core = "0" +tonlib-core.workspace = true -[patch.crates-io] -tonlib-core = { path = "../core" } diff --git a/client/src/client.rs b/client/src/client.rs index f43b00e..0c5937f 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -4,6 +4,9 @@ use std::path::Path; use std::sync::Arc; use std::thread::JoinHandle; +use crate::client::recent_init_block::get_recent_init_block; +use crate::config::TonConfig; +use crate::tl::*; use async_trait::async_trait; pub use block_functions::*; pub use block_stream::*; @@ -19,8 +22,6 @@ use tokio_retry::strategy::FixedInterval; use tokio_retry::RetryIf; pub use types::*; -use crate::tl::*; - mod block_functions; mod block_stream; mod builder; @@ -28,7 +29,7 @@ mod callback; mod connection; mod error; mod interface; - +mod recent_init_block; mod types; /// Check on perform upon connection @@ -61,20 +62,25 @@ impl TonClient { callback: Arc, connection_check: ConnectionCheck, ) -> Result { + let patched_params = if params.update_init_block { + patch_init_block(params).await? + } else { + params.clone() + }; let mut connections = Vec::with_capacity(pool_size); for i in 0..pool_size { - let mut p = params.clone(); - if let Some(dir) = ¶ms.keystore_dir { + let mut conn_params = patched_params.clone(); + if let Some(dir) = &patched_params.keystore_dir { let keystore_prefix = Path::new(dir.as_str()); let keystore_dir = keystore_prefix.join(format!("{}", i)); fs::create_dir_all(&keystore_dir)?; let path_str = keystore_dir.into_os_string().into_string().map_err(|_| { TonClientError::InternalError("Error constructing keystore path".to_string()) })?; - p.keystore_dir = Some(path_str) + conn_params.keystore_dir = Some(path_str) }; let entry = PoolConnection { - params: p, + params: conn_params, callback: callback.clone(), conn: Mutex::new(None), connection_check: connection_check.clone(), @@ -177,6 +183,45 @@ fn retry_condition(error: &TonClientError) -> bool { } } +async fn patch_init_block( + params: &TonConnectionParams, +) -> Result { + let mut ton_config = TonConfig::from_json(¶ms.config).map_err(|e| { + let msg = format!("Fail to parse config: {}", e); + TonClientError::InternalError(msg) + })?; + + let recent_init_block = match get_recent_init_block(&ton_config.liteservers).await { + Some(block) => block, + None => { + let msg = "Failed to update init_block: update it manually in network_config.json (https://docs.ton.org/develop/howto/network-configs)"; + return Err(TonClientError::InternalError(msg.to_string())); + } + }; + + let old_seqno = ton_config.get_init_block_seqno(); + if old_seqno < recent_init_block.seqno { + ton_config.set_init_block(&recent_init_block).map_err(|e| { + let msg = format!("Fail to serialize block_id: {}", e); + TonClientError::InternalError(msg) + })?; + log::info!( + "init_block updated: old_seqno={}, new_seqno={}", + old_seqno, + recent_init_block.seqno + ); + } else { + log::info!("Init block is up to date, seqno: {}", old_seqno); + } + + let mut patched_params = params.clone(); + patched_params.config = ton_config.to_json().map_err(|e| { + let msg = format!("Fail to serialize config: {}", e); + TonClientError::InternalError(msg) + })?; + Ok(patched_params) +} + struct PoolConnection { params: TonConnectionParams, callback: Arc, diff --git a/client/src/client/connection.rs b/client/src/client/connection.rs index 7d341fb..5a39073 100644 --- a/client/src/client/connection.rs +++ b/client/src/client/connection.rs @@ -20,6 +20,7 @@ use crate::types::TonMethodId; pub const DEFAULT_NOTIFICATION_QUEUE_LENGTH: usize = 10000; pub const DEFAULT_CONNECTION_CONCURRENCY_LIMIT: usize = 100; +pub const DEFAULT_UPDATE_INIT_BLOCK: bool = true; struct RequestData { method: &'static str, diff --git a/client/src/client/recent_init_block.rs b/client/src/client/recent_init_block.rs new file mode 100644 index 0000000..95574f8 --- /dev/null +++ b/client/src/client/recent_init_block.rs @@ -0,0 +1,200 @@ +use crate::client::recent_init_block::lite::Connection; +use crate::config::LiteEndpoint; +use crate::tl::BlockIdExt; +use anyhow::bail; +use futures::future::join_all; +use ton_liteapi::tl::response::BlockData; +use tonlib_core::cell::BagOfCells; +use tonlib_core::constants::{MASTERCHAIN_ID, SHARD_FULL}; + +const BLOCK_INFO_TAG: u32 = 0x9bc7a987; + +pub(crate) async fn get_recent_init_block(endpoints: &[LiteEndpoint]) -> Option { + log::info!("Trying to update init_block..."); + let keyblocks_f = endpoints + .iter() + .map(|endpoint| get_last_keyblock(endpoint.clone())); + + let keyblocks_res = join_all(keyblocks_f).await; + + // just log errors + for (pos, res) in keyblocks_res.iter().enumerate() { + if let Err(err) = res { + log::warn!( + "Failed to get recent init block from node with ip: {}, err: {}", + endpoints[pos].ip, + err, + ); + } + } + // each endpoint may return error, but we need only 1 successful result - so ignore errors + keyblocks_res + .into_iter() + .flatten() + .max_by_key(|block| block.seqno) +} + +async fn get_last_keyblock(endpoint: LiteEndpoint) -> anyhow::Result { + let mut conn = Connection::new(endpoint)?; + let mc_info = conn.get_mc_info().await?; + let block = conn.get_block(mc_info.last).await?; + let seqno = parse_key_block_seqno(&block)?; + let header = conn.get_mc_header(seqno).await?; + + let key_block_id_lite = header.id; + let block_id = BlockIdExt { + workchain: MASTERCHAIN_ID, + shard: SHARD_FULL as i64, + seqno: seqno as i32, + root_hash: key_block_id_lite.root_hash.0.to_vec(), + file_hash: key_block_id_lite.file_hash.0.to_vec(), + }; + Ok(block_id) +} + +fn parse_key_block_seqno(block: &BlockData) -> anyhow::Result { + let boc = BagOfCells::parse(&block.data)?; + let root = boc.single_root()?; + let block_info = root.reference(0)?; + + let mut parser = block_info.parser(); + + let tag = parser.load_u32(32)?; + if tag != BLOCK_INFO_TAG { + bail!("Invalid tag: {}, expected: {}", tag, BLOCK_INFO_TAG); + } + // version(32), merge_info(8), flags(8), seqno(32), vert_seqno(32), shard(104), utime(32), start/end lt(128), + // validator_list_hash(32), catchain_seqno(32), min_ref_mc_seqno(32) + parser.skip_bits(32 + 8 + 8 + 32 + 32 + 104 + 32 + 128 + 32 + 32 + 32)?; + let key_block_seqno = parser.load_u32(32)?; + Ok(key_block_seqno) +} + +mod lite { + use crate::config::LiteEndpoint; + use adnl::AdnlPeer; + use base64::prelude::BASE64_STANDARD; + use base64::Engine; + use std::error::Error; + use std::net::{Ipv4Addr, SocketAddrV4}; + use std::time::Duration; + use tokio::net::TcpStream; + use tokio::time::timeout; + use tokio_tower::multiplex::Client; + use ton_liteapi::layers::{WrapMessagesLayer, WrapService}; + use ton_liteapi::peer::LitePeer; + use ton_liteapi::tl::adnl::Message; + use ton_liteapi::tl::common::BlockIdExt as BlockIdExtLite; + use ton_liteapi::tl::request::{ + GetBlock, LookupBlock, Request, WaitMasterchainSeqno, WrappedRequest, + }; + use ton_liteapi::tl::response::{BlockData, BlockHeader, MasterchainInfo, Response}; + use ton_liteapi::types::LiteError; + use tonlib_core::constants::{MASTERCHAIN_ID, SHARD_FULL}; + use tower::{Service, ServiceBuilder, ServiceExt}; + + const CONNECTION_TIMEOUT: Duration = Duration::from_secs(10); + const REQ_TIMEOUT: Duration = Duration::from_secs(10); + + type ConnService = + WrapService>, Box, Message>>; + + pub(super) struct Connection { + public: Vec, + addr: SocketAddrV4, + service: Option, + } + + impl Connection { + pub(super) fn new(endpoint: LiteEndpoint) -> anyhow::Result { + let LiteEndpoint { ip, port, id } = endpoint; + let ip_addr = Ipv4Addr::from(ip as u32); + let public = BASE64_STANDARD.decode(id.key)?; + let addr = SocketAddrV4::new(ip_addr, port); + let conn = Self { + public, + addr, + service: None, + }; + Ok(conn) + } + + pub(super) async fn get_block( + &mut self, + block_id: BlockIdExtLite, + ) -> anyhow::Result { + let req = WrappedRequest { + wait_masterchain_seqno: Some(WaitMasterchainSeqno { + seqno: block_id.seqno, + timeout_ms: REQ_TIMEOUT.as_millis() as u32, + }), + request: Request::GetBlock(GetBlock { id: block_id }), + }; + match self.execute(req).await? { + Response::BlockData(block) => Ok(block), + _ => Err(LiteError::UnexpectedMessage)?, + } + } + + pub(super) async fn get_mc_header(&mut self, seqno: u32) -> anyhow::Result { + let req = WrappedRequest { + wait_masterchain_seqno: None, + request: Request::LookupBlock(LookupBlock { + mode: (), + id: ton_liteapi::tl::common::BlockId { + workchain: MASTERCHAIN_ID, + shard: SHARD_FULL, + seqno, + }, + seqno: Some(()), + lt: None, + utime: None, + with_state_update: None, + with_value_flow: None, + with_extra: None, + with_shard_hashes: None, + with_prev_blk_signatures: None, + }), + }; + match self.execute(req).await? { + Response::BlockHeader(header) => Ok(header), + _ => Err(LiteError::UnexpectedMessage)?, + } + } + + pub(super) async fn get_mc_info(&mut self) -> anyhow::Result { + let req = WrappedRequest { + wait_masterchain_seqno: None, + request: Request::GetMasterchainInfo, + }; + match self.execute(req).await? { + Response::MasterchainInfo(info) => Ok(info), + _ => Err(LiteError::UnexpectedMessage)?, + } + } + + async fn execute(&mut self, req: WrappedRequest) -> anyhow::Result { + let ready_service = self.connect().await?.ready().await?; + Ok(timeout(REQ_TIMEOUT, ready_service.call(req)).await??) + } + + async fn connect(&mut self) -> anyhow::Result<&mut ConnService> { + if self.service.is_none() { + let adnl = timeout( + CONNECTION_TIMEOUT, + AdnlPeer::connect(&self.public, self.addr), + ) + .await??; + + let lite = LitePeer::new(adnl); + let service = ServiceBuilder::new() + .layer(WrapMessagesLayer) + .service(Client::<_, Box, _>::new( + lite, + )); + self.service = Some(service); + } + Ok(self.service.as_mut().unwrap()) // unwrap is safe: we initialized it in branch above + } + } +} diff --git a/client/src/client/types.rs b/client/src/client/types.rs index d40421f..96ae1e1 100644 --- a/client/src/client/types.rs +++ b/client/src/client/types.rs @@ -7,7 +7,7 @@ use tonlib_core::TonAddress; use super::{ BlocksShortTxId, TonClientError, DEFAULT_CONNECTION_CONCURRENCY_LIMIT, - DEFAULT_NOTIFICATION_QUEUE_LENGTH, + DEFAULT_NOTIFICATION_QUEUE_LENGTH, DEFAULT_UPDATE_INIT_BLOCK, }; use crate::config::MAINNET_CONFIG; use crate::tl::{InternalTransactionId, TonNotification}; @@ -54,6 +54,8 @@ pub struct TonConnectionParams { pub notification_queue_length: usize, #[serde(default = "default_connection_concurrency_limit")] pub concurrency_limit: usize, + #[serde(default = "default_update_init_block")] + pub update_init_block: bool, } impl Default for TonConnectionParams { @@ -66,6 +68,7 @@ impl Default for TonConnectionParams { keystore_dir: None, notification_queue_length: DEFAULT_NOTIFICATION_QUEUE_LENGTH, concurrency_limit: DEFAULT_CONNECTION_CONCURRENCY_LIMIT, + update_init_block: DEFAULT_UPDATE_INIT_BLOCK, } } } @@ -77,6 +80,10 @@ fn default_connection_concurrency_limit() -> usize { DEFAULT_CONNECTION_CONCURRENCY_LIMIT } +fn default_update_init_block() -> bool { + DEFAULT_UPDATE_INIT_BLOCK +} + lazy_static! { pub static ref DEFAULT_CONNECTION_PARAMS: TonConnectionParams = TonConnectionParams::default(); } diff --git a/client/src/config.rs b/client/src/config.rs index 6b62418..1421cd9 100644 --- a/client/src/config.rs +++ b/client/src/config.rs @@ -1,2 +1,56 @@ +use crate::tl::BlockIdExt; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + pub const MAINNET_CONFIG: &str = include_str!("../resources/config/global.config.json"); pub const TESTNET_CONFIG: &str = include_str!("../resources/config/testnet-global.config.json"); + +#[derive(Serialize, Deserialize)] +pub(crate) struct TonConfig { + #[serde(rename = "@type")] + conf_type: Value, + dht: Value, + pub liteservers: Vec, + validator: Validator, +} + +impl TonConfig { + pub fn from_json(config: &str) -> Result { + serde_json::from_str(config) + } + pub fn to_json(&self) -> Result { + serde_json::to_string(self) + } + + pub fn get_init_block_seqno(&self) -> i32 { + self.validator.init_block["seqno"].as_i64().unwrap_or(0) as i32 + } + + pub fn set_init_block(&mut self, block_id: &BlockIdExt) -> Result<(), serde_json::Error> { + self.validator.init_block = serde_json::to_value(block_id)?; + Ok(()) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct LiteEndpoint { + pub ip: i32, + pub port: u16, + pub id: LiteID, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct LiteID { + #[serde(rename = "@type")] + pub config_type: Value, + pub key: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct Validator { + #[serde(rename = "@type")] + pub config_type: Value, + pub zero_state: Value, + pub init_block: Value, + pub hardforks: Value, +} diff --git a/client/src/contract.rs b/client/src/contract.rs index 4ca4b00..592590e 100644 --- a/client/src/contract.rs +++ b/client/src/contract.rs @@ -80,6 +80,15 @@ impl TonContractInterface for TonContract { self.factory.get_latest_account_state(self.address()).await } + async fn get_account_state_by_transaction( + &self, + tx_id: &InternalTransactionId, + ) -> Result { + self.factory + .get_account_state_by_transaction(self.address(), tx_id) + .await + } + async fn run_get_method( &self, method: M, diff --git a/client/src/contract/interface.rs b/client/src/contract/interface.rs index 2b93f6f..5ad43d9 100644 --- a/client/src/contract/interface.rs +++ b/client/src/contract/interface.rs @@ -6,7 +6,7 @@ use tonlib_core::TonAddress; use super::TonContractError; use crate::client::TonConnection; use crate::contract::TonContractFactory; -use crate::tl::RawFullAccountState; +use crate::tl::{InternalTransactionId, RawFullAccountState}; use crate::types::{TonMethodId, TvmStackEntry, TvmSuccess}; pub struct LoadedSmcState { @@ -21,6 +21,10 @@ pub trait TonContractInterface { fn address(&self) -> &TonAddress; async fn get_account_state(&self) -> Result, TonContractError>; + async fn get_account_state_by_transaction( + &self, + tx_id: &InternalTransactionId, + ) -> Result; async fn run_get_method( &self, diff --git a/client/src/contract/state.rs b/client/src/contract/state.rs index b495ff9..671fb45 100644 --- a/client/src/contract/state.rs +++ b/client/src/contract/state.rs @@ -7,7 +7,7 @@ use tonlib_core::TonAddress; use crate::client::{TonClientError, TonClientInterface}; use crate::contract::{TonContractError, TonContractFactory, TonContractInterface}; use crate::emulator::{TvmEmulator, TvmEmulatorC7Builder}; -use crate::tl::RawFullAccountState; +use crate::tl::{InternalTransactionId, RawFullAccountState}; use crate::types::{TonMethodId, TvmMsgSuccess, TvmStackEntry, TvmSuccess}; #[derive(Clone)] @@ -251,6 +251,15 @@ impl TonContractInterface for TonContractState { Ok(self.account_state.clone()) } + async fn get_account_state_by_transaction( + &self, + tx_id: &InternalTransactionId, + ) -> Result { + self.factory + .get_account_state_by_transaction(self.address(), tx_id) + .await + } + async fn run_get_method( &self, method: M, diff --git a/client/tests/client_test.rs b/client/tests/client_test.rs index 3afee44..832f0c7 100644 --- a/client/tests/client_test.rs +++ b/client/tests/client_test.rs @@ -29,28 +29,31 @@ use tonlib_core::{TonAddress, TonTxId}; mod common; #[tokio::test] -async fn test_client_get_account_state_of_inactive() { +async fn test_client_get_masterchain_info() -> anyhow::Result<()> { common::init_logging(); let client = common::new_mainnet_client().await; - let factory = assert_ok!(TonContractFactory::builder(&client).build().await); + let (_, _) = client.get_masterchain_info().await?; + Ok(()) +} + +#[tokio::test] +async fn test_client_get_account_state_of_inactive() -> anyhow::Result<()> { + common::init_logging(); + let client = common::new_mainnet_client().await; + let factory = TonContractFactory::builder(&client).build().await?; for _ in 0..100 { - let r = factory - .get_latest_account_state(assert_ok!(&TonAddress::from_base64_url( - "EQDOUwuz-6lH-IL-hqSHQSrFhoNjTNjKp04Wb5n2nkctCJTH", - ))) - .await; - log::info!("{:?}", r); - assert!(r.is_ok()); - if r.unwrap().frozen_hash != Vec::::new() { - panic!("Expected UnInited state") - } + let addr = TonAddress::from_str("EQDk2VTvn04SUKJrW7rXahzdF8_Qi6utb0wj43InCu9vdjrR")?; + let res = assert_ok!(factory.get_latest_account_state(&addr).await); + log::debug!("{:?}", res); + assert!(res.frozen_hash.is_empty(), "Expected Uninitialized state"); } drop(factory); tokio::time::sleep(Duration::from_secs(1)).await; + Ok(()) } #[tokio::test] -async fn client_get_raw_account_state_works() { +async fn client_get_raw_account_state_works() -> anyhow::Result<()> { common::init_logging(); let client = common::new_mainnet_client().await; let r = assert_ok!( @@ -61,10 +64,11 @@ async fn client_get_raw_account_state_works() { .await ); log::info!("{:?}", r); + Ok(()) } #[tokio::test] -async fn client_get_raw_transactions_works() { +async fn client_get_raw_transactions_works() -> anyhow::Result<()> { common::init_logging(); let address = &assert_ok!(TonAddress::from_base64_url( "EQDk2VTvn04SUKJrW7rXahzdF8_Qi6utb0wj43InCu9vdjrR" @@ -74,7 +78,7 @@ async fn client_get_raw_transactions_works() { while retries < max_retries { retries += 1; let client = common::new_mainnet_client().await; - let state = client.get_raw_account_state(address).await.unwrap(); + let state = client.get_raw_account_state(address).await?; let r = client .get_raw_transactions(address, &state.last_transaction_id) .await; @@ -86,15 +90,16 @@ async fn client_get_raw_transactions_works() { .await; println!("{:?}", r); if r.is_ok() { - assert_eq!(r.unwrap().transactions.len(), cnt); - return; + assert_eq!(r?.transactions.len(), cnt); + return Ok(()); } } } + Ok(()) } #[tokio::test] -async fn client_smc_run_get_method_works() { +async fn client_smc_run_get_method_works() -> anyhow::Result<()> { common::init_logging(); let client = common::new_mainnet_client().await; let address = &assert_ok!(TonAddress::from_base64_url( @@ -119,10 +124,11 @@ async fn client_smc_run_get_method_works() { let future = conn.smc_run_get_method(id2, &method_id, stack); let r = assert_ok!(timeout(Duration::from_secs(2), future).await); log::info!("{:?}", r); + Ok(()) } #[tokio::test] -async fn client_smc_load_by_transaction_works() { +async fn client_smc_load_by_transaction_works() -> anyhow::Result<()> { common::init_logging(); let address = &assert_ok!(TonAddress::from_base64_url( @@ -138,7 +144,7 @@ async fn client_smc_load_by_transaction_works() { retries += 1; let client = common::new_mainnet_client().await; - let state = client.get_raw_account_state(address).await.unwrap(); + let state = client.get_raw_account_state(address).await?; log::info!("TRANSACTION_ID{}", &state.last_transaction_id); @@ -151,13 +157,14 @@ async fn client_smc_load_by_transaction_works() { .await; if res.is_ok() { - return; + return Ok(()); } } + Ok(()) } #[tokio::test] -async fn client_smc_get_code_works() { +async fn client_smc_get_code_works() -> anyhow::Result<()> { common::init_logging(); let client = common::new_mainnet_client().await; let address = &assert_ok!(TonAddress::from_base64_url( @@ -166,10 +173,11 @@ async fn client_smc_get_code_works() { let loaded_state = assert_ok!(client.smc_load(address).await); let cell = assert_ok!(loaded_state.conn.smc_get_code(loaded_state.id).await); log::info!("\n\r\x1b[1;35m-----------------------------------------CODE-----------------------------------------\x1b[0m:\n\r {:?}", STANDARD.encode(cell.bytes)); + Ok(()) } #[tokio::test] -async fn client_smc_get_data_works() { +async fn client_smc_get_data_works() -> anyhow::Result<()> { common::init_logging(); let client = common::new_mainnet_client().await; let address = &assert_ok!(TonAddress::from_base64_url( @@ -178,10 +186,11 @@ async fn client_smc_get_data_works() { let loaded_state = assert_ok!(client.smc_load(address).await); let cell = assert_ok!(loaded_state.conn.smc_get_data(loaded_state.id).await); log::info!("\n\r\x1b[1;35m-----------------------------------------DATA-----------------------------------------\x1b[0m:\n\r {:?}", STANDARD.encode(cell.bytes)); + Ok(()) } #[tokio::test] -async fn test_get_jetton_content_internal_uri_jusdt() { +async fn test_get_jetton_content_internal_uri_jusdt() -> anyhow::Result<()> { common::init_logging(); let client = common::new_mainnet_client().await; let address = &assert_ok!(TonAddress::from_base64_url( @@ -190,10 +199,11 @@ async fn test_get_jetton_content_internal_uri_jusdt() { let loaded_state = assert_ok!(client.smc_load(address).await); let cell = assert_ok!(loaded_state.conn.smc_get_state(loaded_state.id).await); log::info!("\n\r\x1b[1;35m-----------------------------------------STATE----------------------------------------\x1b[0m:\n\r {:?}", cell); + Ok(()) } #[tokio::test] -async fn client_get_block_header_works() { +async fn client_get_block_header_works() -> anyhow::Result<()> { common::init_logging(); let client = common::new_mainnet_client().await; let (_, info) = assert_ok!(client.get_masterchain_info().await); @@ -206,10 +216,11 @@ async fn client_get_block_header_works() { let block_id_ext = assert_ok!(client.lookup_block(1, &block_id, 0, 0).await); let r = assert_ok!(client.get_block_header(&block_id_ext).await); log::info!("{:?}", r); + Ok(()) } #[tokio::test] -async fn test_client_blocks_get_transactions() { +async fn test_client_blocks_get_transactions() -> anyhow::Result<()> { common::init_logging(); let client = common::new_mainnet_client().await; let (_, info) = assert_ok!(client.get_masterchain_info().await); @@ -250,10 +261,11 @@ async fn test_client_blocks_get_transactions() { log::info!("Tx: {:?}", tx.transactions[0]) } } + Ok(()) } #[tokio::test] -async fn test_client_blocks_get_transactions_ext() { +async fn test_client_blocks_get_transactions_ext() -> anyhow::Result<()> { common::init_logging(); let client = common::new_mainnet_client().await; let (_, info) = assert_ok!(client.get_masterchain_info().await); @@ -290,19 +302,21 @@ async fn test_client_blocks_get_transactions_ext() { log::info!("Tx: {:?}", tx.transactions[0]) } } + Ok(()) } #[tokio::test] -async fn test_client_lite_server_get_info() { +async fn test_client_lite_server_get_info() -> anyhow::Result<()> { common::init_logging(); let client = common::new_testnet_client().await; let info: LiteServerInfo = assert_ok!(client.lite_server_get_info().await); log::info!("{:?}", info); + Ok(()) } #[tokio::test] -async fn test_get_config_param() { +async fn test_get_config_param() -> anyhow::Result<()> { common::init_logging(); let client = &common::new_testnet_client().await; let info = assert_ok!(client.get_config_param(0u32, 34u32).await); @@ -311,21 +325,23 @@ async fn test_get_config_param() { let config_cell = assert_ok!(bag.single_root()); let mut parser = config_cell.parser(); let n = assert_ok!(parser.load_u8(8)); - assert!(n == 0x12u8); + assert_eq!(n, 0x12u8); + Ok(()) } #[tokio::test] -pub async fn test_get_block_header() { +pub async fn test_get_block_header() -> anyhow::Result<()> { common::init_logging(); let client = &common::new_testnet_client().await; let (_, info) = assert_ok!(client.get_masterchain_info().await); let seqno = info.last; let headers = assert_ok!(client.get_block_header(&seqno).await); log::info!("{:?}", headers); + Ok(()) } #[tokio::test] -async fn test_get_shard_tx_ids() { +async fn test_get_shard_tx_ids() -> anyhow::Result<()> { common::init_logging(); let client = &common::new_testnet_client().await; let (_, info) = assert_ok!(client.get_masterchain_info().await); @@ -333,10 +349,11 @@ async fn test_get_shard_tx_ids() { assert!(!shards.shards.is_empty()); let ids = assert_ok!(client.get_shard_tx_ids(&shards.shards[0]).await); log::info!("{:?}", ids); + Ok(()) } #[tokio::test] -async fn test_get_shard_transactions_works() { +async fn test_get_shard_transactions_works() -> anyhow::Result<()> { common::init_logging(); let client = &common::new_testnet_client().await; let (_, info) = assert_ok!(client.get_masterchain_info().await); @@ -345,24 +362,21 @@ async fn test_get_shard_transactions_works() { let txs = assert_ok!(client.get_shard_transactions(&shards.shards[0]).await); assert!(!txs.is_empty()); log::info!("{:?}", txs); + Ok(()) } #[tokio::test] -async fn test_get_shard_transactions_parse_address_correctly() { +async fn test_get_shard_transactions_parse_address_correctly() -> anyhow::Result<()> { common::init_logging(); let client = &common::new_mainnet_client().await; - assert_ok!(client.sync().await); + client.sync().await?; // manually selected block with particular addresses format in transactions let block_shard = BlockIdExt { workchain: -1, shard: -9223372036854775808, seqno: 39812357, - root_hash: STANDARD - .decode("WFgmnfd3wuQR9HydL54EjcuDvLYM/SIwDbDxbNzDyjU=") - .unwrap(), - file_hash: STANDARD - .decode("scgMz5C3n0uBeb2pdf2e8/BWlfzTB8FcRsNvvHgXKYM=") - .unwrap(), + root_hash: STANDARD.decode("WFgmnfd3wuQR9HydL54EjcuDvLYM/SIwDbDxbNzDyjU=")?, + file_hash: STANDARD.decode("scgMz5C3n0uBeb2pdf2e8/BWlfzTB8FcRsNvvHgXKYM=")?, }; let txs = assert_ok!(client.get_shard_transactions(&block_shard).await); assert!(!txs.is_empty()); @@ -372,38 +386,36 @@ async fn test_get_shard_transactions_parse_address_correctly() { workchain: -1, shard: -9223372036854775808, seqno: 39812359, - root_hash: STANDARD - .decode("WFgmnfd3wuQR9HydL54EjcuDvLYM/SIwDbDxbNzDyjU=") - .unwrap(), - file_hash: STANDARD - .decode("scgMz5C3n0uBeb2pdf2e8/BWlfzTB8FcRsNvvHgXKYM=") - .unwrap(), + root_hash: STANDARD.decode("WFgmnfd3wuQR9HydL54EjcuDvLYM/SIwDbDxbNzDyjU=")?, + file_hash: STANDARD.decode("scgMz5C3n0uBeb2pdf2e8/BWlfzTB8FcRsNvvHgXKYM=")?, }; assert!(client .get_shard_transactions(¬_a_block_shard) .await .is_err()); + Ok(()) } #[tokio::test] -async fn test_get_shards_transactions() { +async fn test_get_shards_transactions() -> anyhow::Result<()> { common::init_logging(); let client = &common::new_testnet_client().await; - let (_, info) = assert_ok!(client.get_masterchain_info().await); + let (_, info) = client.get_masterchain_info().await?; let shards = assert_ok!(client.get_block_shards(&info.last).await); assert!(!shards.shards.is_empty()); let shards_txs = assert_ok!(client.get_shards_transactions(&shards.shards).await); for s in shards_txs { log::info!("{:?} : {:?}", s.0, s.1); } + Ok(()) } #[tokio::test] -async fn test_missing_block_error() { +async fn test_missing_block_error() -> anyhow::Result<()> { common::init_logging(); let client = &common::new_testnet_client().await; - let (_, info) = assert_ok!(client.get_masterchain_info().await); + let (_, info) = client.get_masterchain_info().await?; let block_id = BlockId { workchain: info.last.workchain, shard: info.last.shard, @@ -417,13 +429,14 @@ async fn test_missing_block_error() { break; }; } + Ok(()) } #[tokio::test] -async fn test_first_block_error() { +async fn test_first_block_error() -> anyhow::Result<()> { common::init_logging(); let client = &common::new_archive_testnet_client().await; - let (_, info) = assert_ok!(client.get_masterchain_info().await); + let (_, info) = client.get_masterchain_info().await?; let block_id = BlockId { workchain: info.last.workchain, shard: info.last.shard, @@ -431,13 +444,15 @@ async fn test_first_block_error() { }; let res = client.lookup_block(1, &block_id, 0, 0).await; log::info!("{:?}", res); + assert_ok!(res); + Ok(()) } #[tokio::test] -async fn test_keep_connection_alive() { +async fn test_keep_connection_alive() -> anyhow::Result<()> { common::init_logging(); let client = &common::new_archive_testnet_client().await; - let (_, info) = assert_ok!(client.get_masterchain_info().await); + let (_, info) = client.get_masterchain_info().await?; let next_block_id = BlockId { workchain: info.last.workchain, shard: info.last.shard, @@ -456,10 +471,11 @@ async fn test_keep_connection_alive() { let r3 = conn.lookup_block(1, &first_block_id, 0, 0).await; log::info!("R1: {:?}", r3); tokio::time::sleep(Duration::from_secs(1)).await; + Ok(()) } #[tokio::test] -async fn client_mainnet_works() { +async fn client_mainnet_works() -> anyhow::Result<()> { common::init_logging(); let client = assert_ok!( TonClient::builder() @@ -468,11 +484,11 @@ async fn client_mainnet_works() { .build() .await ); - let (_, info) = assert_ok!(client.get_masterchain_info().await); - let shards = assert_ok!(client.get_block_shards(&info.last).await); - let blocks_header = assert_ok!(client.get_block_header(&info.last).await); + let (_, info) = client.get_masterchain_info().await?; + let shards = client.get_block_shards(&info.last).await?; + let blocks_header = client.get_block_header(&info.last).await?; assert!(!shards.shards.is_empty()); - let shards_txs = assert_ok!(client.get_shards_transactions(&shards.shards).await); + let shards_txs = client.get_shards_transactions(&shards.shards).await?; for s in shards_txs { log::info!(" BlockId: {:?}\n Transactions: {:?}", s.0, s.1.len()); } @@ -481,10 +497,11 @@ async fn client_mainnet_works() { info.last.seqno, blocks_header ); + Ok(()) } #[tokio::test] -async fn client_testnet_works() { +async fn client_testnet_works() -> anyhow::Result<()> { common::init_logging(); let client = assert_ok!( TonClient::builder() @@ -493,11 +510,11 @@ async fn client_testnet_works() { .build() .await ); - let (_, info) = assert_ok!(client.get_masterchain_info().await); - let shards = assert_ok!(client.get_block_shards(&info.last).await); + let (_, info) = client.get_masterchain_info().await?; + let shards = client.get_block_shards(&info.last).await?; assert!(!shards.shards.is_empty()); - let shards_txs = assert_ok!(client.get_shards_transactions(&shards.shards).await); - let blocks_header = assert_ok!(client.get_block_header(&info.last).await); + let shards_txs = client.get_shards_transactions(&shards.shards).await?; + let blocks_header = client.get_block_header(&info.last).await?; for s in shards_txs { log::info!(" BlockId: {:?}\n Transactions: {:?}", s.0, s.1); } @@ -507,19 +524,20 @@ async fn client_testnet_works() { info.last.seqno, blocks_header ); + Ok(()) } #[tokio::test] -async fn client_smc_get_libraries() { +async fn client_smc_get_libraries() -> anyhow::Result<()> { common::init_logging(); let client = common::new_mainnet_client().await; let library_hash_str = "TwFxJywhW4v4/urEaoV2iKS2X0/mH4IoYx9ifQ7anQA="; let library_hash = TonLibraryId { - id: assert_ok!(STANDARD.decode(library_hash_str)), + id: STANDARD.decode(library_hash_str)?, }; let library_list = &[library_hash]; - let smc_library_result = assert_ok!(client.smc_get_libraries(library_list).await); + let smc_library_result = client.smc_get_libraries(library_list).await?; log::info!( "smc_library_result {:?}", @@ -531,14 +549,13 @@ async fn client_smc_get_libraries() { ); // we just test that library code is a valid boc: - let boc = assert_ok!(BagOfCells::parse( - smc_library_result.result[0].data.as_slice() - )); + let boc = BagOfCells::parse(smc_library_result.result[0].data.as_slice())?; log::info!("smc_library_result {:?}", boc); + Ok(()) } #[tokio::test] -async fn client_smc_get_libraries_ext() { +async fn client_smc_get_libraries_ext() -> anyhow::Result<()> { common::init_logging(); let client = common::new_mainnet_client().await; @@ -546,7 +563,7 @@ async fn client_smc_get_libraries_ext() { let address = assert_ok!(TonAddress::from_base64_url( "EQDqVNU7Jaf85MhIba1lup0F7Mr3rGigDV8RxMS62RtFr1w8" )); //jetton master - let factory = assert_ok!(TonContractFactory::builder(&client).build().await); + let factory = TonContractFactory::builder(&client).build().await?; let contract = factory.get_contract(&address); let code = &assert_ok!(contract.get_account_state().await).code; let library_query = SmcLibraryQueryExt::ScanBoc { @@ -567,15 +584,16 @@ async fn client_smc_get_libraries_ext() { assert_ok!(STANDARD.decode(library_hash)) ); - let boc = assert_ok!(BagOfCells::parse(&smc_libraries_ext_result.dict_boc)); - let cell = assert_ok!(boc.single_root()); + let boc = BagOfCells::parse(&smc_libraries_ext_result.dict_boc)?; + let cell = boc.single_root()?; let dict_loader = GenericDictLoader::new(key_extractor_256bit, value_extractor_cell, 256); let dict = assert_ok!(cell.load_generic_dict(&dict_loader)); log::info!("DICT: {:?}", dict); assert_eq!(dict.len(), 1); - assert!(dict.contains_key(assert_ok!(STANDARD.decode(library_hash)).as_slice())); + assert!(dict.contains_key(STANDARD.decode(library_hash)?.as_slice())); + Ok(()) } // This test fails on tonlib 2023.6, 2024.1 and 2024.3 either with: @@ -590,13 +608,13 @@ async fn client_smc_get_libraries_ext() { // (signal: 11, SIGSEGV: invalid memory reference) #[ignore] #[tokio::test] -async fn dropping_invoke_test() { +async fn dropping_invoke_test() -> anyhow::Result<()> { common::init_logging(); let client = common::new_mainnet_client().await; let address = assert_ok!(TonAddress::from_base64_url( "EQDk2VTvn04SUKJrW7rXahzdF8_Qi6utb0wj43InCu9vdjrR" )); - assert_ok!(client.get_raw_account_state(&address).await); + client.get_raw_account_state(&address).await?; let f = [ abort_batch_invoke_get_raw_account_state(&client, Duration::from_millis(100)), @@ -605,9 +623,13 @@ async fn dropping_invoke_test() { ]; join_all(f).await; + Ok(()) } -async fn abort_batch_invoke_get_raw_account_state(client: &TonClient, dt: Duration) { +async fn abort_batch_invoke_get_raw_account_state( + client: &TonClient, + dt: Duration, +) -> anyhow::Result<()> { let address = assert_ok!(TonAddress::from_base64_url( "EQDk2VTvn04SUKJrW7rXahzdF8_Qi6utb0wj43InCu9vdjrR" )); @@ -622,12 +644,13 @@ async fn abort_batch_invoke_get_raw_account_state(client: &TonClient, dt: Durati let res = result.iter().map(|r| r.is_ok()).collect::>(); log::info!("{:?}", res); + Ok(()) } #[tokio::test] -async fn archive_node_client_test() { +async fn archive_node_client_test() -> anyhow::Result<()> { let tonlib_work_dir = "./var/tonlib"; - create_dir_all(Path::new(tonlib_work_dir)).unwrap(); + create_dir_all(Path::new(tonlib_work_dir))?; TonClient::set_log_verbosity_level(2); let mut client_builder = TonClientBuilder::new(); @@ -635,7 +658,8 @@ async fn archive_node_client_test() { .with_config(MAINNET_CONFIG) .with_keystore_dir(String::from(tonlib_work_dir)) .with_connection_check(tonlib_client::client::ConnectionCheck::Archive); - let client = client_builder.build().await.unwrap(); - let (_, master_info) = client.get_masterchain_info().await.unwrap(); + let client = client_builder.build().await?; + let (_, master_info) = client.get_masterchain_info().await?; log::info!("master_info: {:?}", master_info); + Ok(()) } diff --git a/client/tests/common/mod.rs b/client/tests/common/mod.rs index 99d9424..9b92d9c 100644 --- a/client/tests/common/mod.rs +++ b/client/tests/common/mod.rs @@ -55,7 +55,7 @@ pub fn init_logging() { let config = Config::builder() .appender(Appender::builder().build("stderr", Box::new(stderr))) - .build(Root::builder().appender("stderr").build(LevelFilter::Trace)) + .build(Root::builder().appender("stderr").build(LevelFilter::Info)) .unwrap(); log4rs::init_config(config).unwrap(); diff --git a/client/tests/jetton_test.rs b/client/tests/jetton_test.rs index caf85c3..6f80507 100644 --- a/client/tests/jetton_test.rs +++ b/client/tests/jetton_test.rs @@ -94,22 +94,23 @@ async fn test_get_jetton_content_ipfs_uri() { } #[tokio::test] -async fn test_get_semi_chain_layout_jetton_content() { +async fn test_get_semi_chain_layout_jetton_content() -> anyhow::Result<()> { common::init_logging(); let client = common::new_mainnet_client().await; - let factory = assert_ok!(TonContractFactory::builder(&client).build().await); - let contract = factory.get_contract(&assert_ok!( - "EQB-MPwrd1G6WKNkLz_VnV6WqBDd142KMQv-g1O-8QUA3728".parse() - )); // jUSDC jetton - let res = assert_ok!(contract.get_jetton_data().await); - let meta_loader = assert_ok!(JettonMetaLoader::default()); - let content_res = assert_ok!(meta_loader.load(&res.content).await); + let factory = TonContractFactory::builder(&client).build().await?; + let addr = "EQB-MPwrd1G6WKNkLz_VnV6WqBDd142KMQv-g1O-8QUA3728".parse()?; // jUSDC jetton + let contract = factory.get_contract(&addr); + let res = contract.get_jetton_data().await?; + let meta_loader = JettonMetaLoader::default()?; + let content_res = meta_loader.load(&res.content).await?; + log::info!("content_res: {:?}", content_res); assert_eq!(content_res.symbol.as_ref().unwrap(), &String::from("jUSDC")); assert_eq!( content_res.name.as_ref().unwrap(), &String::from("USD Coin") ); assert_eq!(content_res.decimals.unwrap(), 0x6); + Ok(()) } #[tokio::test] diff --git a/core/Cargo.toml b/core/Cargo.toml index 2a6abfc..8be7590 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -23,3 +23,7 @@ serde.workspace = true serde_json.workspace = true sha2.workspace = true thiserror.workspace = true +bitvec.workspace = true + +[dev-dependencies] +tokio-test.workspace = true diff --git a/core/src/cell.rs b/core/src/cell.rs index 5ef429f..94b846a 100644 --- a/core/src/cell.rs +++ b/core/src/cell.rs @@ -11,6 +11,7 @@ use base64::Engine; use bit_string::*; use bitstream_io::{BigEndian, BitWrite, BitWriter}; pub use builder::*; +pub use dict_builder::*; pub use dict_loader::*; pub use error::*; use hmac::digest::Digest; @@ -34,6 +35,7 @@ mod bit_string; mod builder; mod cell_type; +mod dict_builder; mod dict_loader; mod error; mod level_mask; diff --git a/core/src/cell/bit_string.rs b/core/src/cell/bit_string.rs index 0dfc670..f563538 100644 --- a/core/src/cell/bit_string.rs +++ b/core/src/cell/bit_string.rs @@ -3,7 +3,7 @@ use std::ops::{Add, ShlAssign}; use num_bigint::BigUint; use num_traits::Zero; -#[derive(Clone)] +#[derive(Debug, Clone)] pub(crate) struct BitString { value: BigUint, bit_len: usize, diff --git a/core/src/cell/builder.rs b/core/src/cell/builder.rs index 86c0be1..5f04912 100644 --- a/core/src/cell/builder.rs +++ b/core/src/cell/builder.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::ops::Add; use std::sync::Arc; @@ -6,7 +7,7 @@ use num_bigint::{BigInt, BigUint, Sign}; use num_traits::{One, Zero}; use crate::cell::error::{MapTonCellError, TonCellError}; -use crate::cell::{ArcCell, Cell, CellParser}; +use crate::cell::{serialize_dict, ArcCell, Cell, CellParser, ValueWriter}; use crate::TonAddress; pub(crate) const MAX_CELL_BITS: usize = 1023; @@ -303,6 +304,19 @@ impl CellBuilder { Ok(self) } + pub fn store_dict( + &mut self, + data: HashMap, + key_len_bits: usize, + value_writer: ValueWriter, + ) -> Result<&mut Self, TonCellError> + where + BigUint: From, + { + let dict_cell = serialize_dict(data, key_len_bits, value_writer)?; + self.store_cell(&dict_cell) + } + pub fn remaining_bits(&self) -> usize { MAX_CELL_BITS - self.bits_to_write } @@ -376,13 +390,16 @@ impl Default for CellBuilder { #[cfg(test)] mod tests { + use std::collections::HashMap; use std::str::FromStr; use num_bigint::{BigInt, BigUint, Sign}; use num_traits::Zero; use crate::cell::builder::extend_and_invert_bits; - use crate::cell::{CellBuilder, TonCellError}; + use crate::cell::{ + key_extractor_u8, value_extractor_uint, CellBuilder, GenericDictLoader, TonCellError, + }; use crate::types::TonAddress; #[test] @@ -649,4 +666,23 @@ mod tests { } Ok(()) } + + #[test] + fn test_store_dict() -> Result<(), TonCellError> { + let mut writer = CellBuilder::new(); + let mut data = HashMap::new(); + data.insert(1u8, BigUint::from(2u8)); + data.insert(3u8, BigUint::from(4u8)); + + let value_writer = |writer: &mut CellBuilder, value: &BigUint| { + writer.store_uint(8, value)?; + Ok(()) + }; + writer.store_dict(data.clone(), 8, value_writer)?; + let cell = writer.build()?; + let loader = GenericDictLoader::new(key_extractor_u8, value_extractor_uint, 8); + let parsed = cell.load_generic_dict(&loader)?; + assert_eq!(data, parsed); + Ok(()) + } } diff --git a/core/src/cell/dict_builder.rs b/core/src/cell/dict_builder.rs new file mode 100644 index 0000000..7065ef3 --- /dev/null +++ b/core/src/cell/dict_builder.rs @@ -0,0 +1,340 @@ +use std::collections::HashMap; +use std::ops::BitXorAssign; +use std::sync::Arc; + +use bitvec::prelude::{BitVec, Msb0}; +use num_bigint::BigUint; + +use crate::cell::{Cell, CellBuilder, TonCellError}; + +pub type ValueWriter = fn(&mut CellBuilder, &V) -> Result<(), TonCellError>; + +pub(crate) fn serialize_dict( + data: HashMap, + key_len_bits: usize, + value_writer: ValueWriter, +) -> Result +where + BigUint: From, +{ + let data_big_keys = data + .into_iter() + .map(|(k, v)| (BigUint::from(k), v)) + .collect(); + let root = build_tree(data_big_keys, key_len_bits); + let mut builder = CellBuilder::new(); + if let Some(root) = root { + store_node(&root, key_len_bits, &mut builder, value_writer)?; + } + builder.build() +} + +fn store_node( + node: &TreeNode, + key_len_bits: usize, + builder: &mut CellBuilder, + value_writer: ValueWriter, +) -> Result<(), TonCellError> { + store_label(builder, &node.prefix, key_len_bits)?; + + match &node.data { + Data::Edge { left, right } => { + let mut left_builder = CellBuilder::new(); + let mut right_builder = CellBuilder::new(); + // branch implicitly contains 0/1 bit, so we need to subtract 1 more bit + let sub_key_len = key_len_bits - node.prefix.len() - 1; + store_node(left, sub_key_len, &mut left_builder, value_writer)?; + store_node(right, sub_key_len, &mut right_builder, value_writer)?; + builder.store_reference(&Arc::new(left_builder.build()?))?; + builder.store_reference(&Arc::new(right_builder.build()?))?; + } + Data::Leaf(val) => value_writer(builder, val)?, + } + Ok(()) +} + +fn store_label( + builder: &mut CellBuilder, + prefix: &BitVecBE, + key_len_bits: usize, +) -> Result<(), TonCellError> { + let prefix_len = prefix.len(); + let prefix_zero_count = prefix.count_zeros(); + let prefix_stored_len = (key_len_bits as f32 + 1.0).log2().ceil() as usize; + + let short_label_len = 2 + prefix_len * 2; + let long_label_len = 2 + prefix_stored_len + prefix_len; + let same_label_len = if prefix_zero_count == prefix_len || prefix_zero_count == 0 { + 3 + prefix_stored_len + } else { + usize::MAX + }; + + enum LabelType { + Short, + Long, + Same, + } + + let mut label_type = LabelType::Short; + if long_label_len < short_label_len { + label_type = LabelType::Long; + } + if same_label_len < short_label_len { + label_type = LabelType::Same; + } + + match label_type { + LabelType::Short => { + builder.store_bit(false)?; + for _ in 0..prefix_len { + builder.store_bit(true)?; + } + builder.store_bit(false)?; + for bit in prefix.iter() { + builder.store_bit(*bit)?; + } + } + LabelType::Long => { + builder.store_bit(true)?; + builder.store_bit(false)?; + builder.store_u32(prefix_stored_len, prefix_len as u32)?; + for bit in prefix.iter() { + builder.store_bit(*bit)?; + } + } + LabelType::Same => { + builder.store_bit(true)?; + builder.store_bit(true)?; + builder.store_bit(prefix[0])?; + builder.store_u32(prefix_stored_len, prefix_len as u32)?; + } + } + Ok(()) +} + +type BitVecBE = BitVec; + +struct TreeNode { + prefix: BitVecBE, // common subtree prefix + data: Data, +} + +enum Data { + Edge { + left: Box>, // 0-prefix subtree + right: Box>, // 1-prefix subtree + }, + Leaf(V), +} + +impl TreeNode { + fn new(prefix: BitVecBE, data: Data) -> Self { + TreeNode { prefix, data } + } + + fn build( + keys: Vec, + origin_keys: Vec, + data: &mut HashMap, + ) -> Self { + if keys.len() == 1 { + let value = data.remove(&origin_keys[0]).unwrap(); + return TreeNode::new(keys[0].clone(), Data::Leaf(value)); + } + let common_prefix_len = calc_common_prefix_len(&keys[0], keys.last().unwrap()); + let cur_prefix = keys[0][..common_prefix_len].into(); + + let mut left_keys = Vec::with_capacity(keys.len() / 2); + let mut left_origin_keys = Vec::with_capacity(keys.len() / 2); + let mut right_keys = Vec::with_capacity(keys.len() / 2); + let mut right_origin_keys = Vec::with_capacity(keys.len() / 2); + + for (key, origin_key) in keys.into_iter().zip(origin_keys.into_iter()) { + let is_right = key[common_prefix_len]; + let new_key: BitVecBE = key[common_prefix_len + 1..].into(); + if is_right { + right_keys.push(new_key); + right_origin_keys.push(origin_key); + } else { + left_keys.push(new_key); + left_origin_keys.push(origin_key); + } + } + + TreeNode::new( + cur_prefix, + Data::Edge { + left: Box::new(TreeNode::build(left_keys, left_origin_keys, data)), + right: Box::new(TreeNode::build(right_keys, right_origin_keys, data)), + }, + ) + } +} + +#[allow(dead_code)] +fn traverse(node: &TreeNode, func: &mut F, prev_prefix: &mut BitVecBE) +where + F: FnMut(&TreeNode, &BitVecBE), +{ + func(node, prev_prefix); + if let Data::Edge { left, right } = &node.data { + let init_len = prev_prefix.len(); + + prev_prefix.extend(node.prefix.iter()); + prev_prefix.push(false); + traverse(left, func, prev_prefix); + *prev_prefix = prev_prefix[..init_len].into(); // backtrack + + prev_prefix.extend(node.prefix.iter()); + prev_prefix.push(true); + traverse(right, func, prev_prefix); + *prev_prefix = prev_prefix[..init_len].into(); // backtrack + } +} + +fn build_tree(mut data: HashMap, key_len_bits: usize) -> Option> { + if data.is_empty() { + return None; + } + let mut origin_keys = data.keys().cloned().collect::>(); + origin_keys.sort(); + let keys: Vec<_> = origin_keys + .iter() + .map(|k| { + let mut val = BitVecBE::from_vec(k.to_u32_digits()); + if key_len_bits > val.len() { + let padding = key_len_bits - val.len(); + val.extend(vec![false; padding]); + val.shift_right(padding); + } + BitVecBE::from(&val[val.len() - key_len_bits..]) + }) + .collect(); + Some(TreeNode::build(keys.clone(), origin_keys, &mut data)) +} + +fn calc_common_prefix_len(a: &BitVecBE, b: &BitVecBE) -> usize { + let mut res = a.clone(); + res.bitxor_assign(b); + res.leading_zeros() +} + +#[cfg(test)] +mod tests { + use tokio_test::assert_ok; + + use super::*; + use crate::cell::{key_extractor_u8, value_extractor_uint, BagOfCells, GenericDictLoader}; + + #[test] + fn test_build_uint_key() { + let data = HashMap::from([ + (BigUint::from(0x1212u32), 89), + (BigUint::from(0x1111u32), 16), + (BigUint::from(307u32), 42), + ]); + + let root = build_tree(data.clone(), 32); + assert!(root.is_some()); + let root = root.unwrap(); + assert_eq!(root.prefix, BitVecBE::from_slice(&[0])[..19]); + if let Data::Leaf(_) = &root.data {} + let (left, right) = match root.data { + Data::Edge { left, right } => (left, right), + _ => unreachable!("Expected edge, got leaf"), + }; + assert_eq!( + left.prefix, + BitVecBE::from_slice(&[0b0000100110011])[32 - 12..] + ); + match left.data { + Data::Leaf(val) => assert_eq!(val, 42), + _ => unreachable!("Expected leaf, got edge"), + }; + + assert_eq!(right.prefix, BitVecBE::from_slice(&[0b100])[32 - 2..]); + let (left, right) = match right.data { + Data::Edge { left, right } => (left, right), + _ => unreachable!("Expected edge, got leaf"), + }; + assert_eq!(left.prefix, BitVecBE::from_slice(&[0b0100010001])[32 - 9..]); + match left.data { + Data::Leaf(val) => assert_eq!(val, 16), + _ => unreachable!("Expected leaf, got edge"), + }; + + assert_eq!( + right.prefix, + BitVecBE::from_slice(&[0b1000010010])[32 - 9..] + ); + match left.data { + Data::Leaf(val) => assert_eq!(val, 16), + _ => unreachable!("Expected leaf, got edge"), + }; + } + + #[test] + fn test_traverse() { + let data = HashMap::from([ + (BigUint::from(4626u32), 89), + (BigUint::from(4369u32), 16), + (BigUint::from(307u32), 42), + (BigUint::from(86u32), 57), + (BigUint::from(4660u32), 73), + ]); + + let root = build_tree(data.clone(), 256).unwrap(); + + let mut traversed_data = HashMap::new(); + let mut func = |node: &TreeNode /* Type */, prev_prefix: &BitVecBE| { + let padding = "_".repeat(prev_prefix.len()); + match &node.data { + Data::Edge { .. } => { + println!("{}{:0b}", padding, node.prefix); + } + Data::Leaf(val) => { + let mut key = prev_prefix.clone(); + key.extend(node.prefix.iter()); + key.set_uninitialized(false); + let key = BitVecBE::from(&key[key.leading_zeros()..]); + let origin_key = BigUint::from_slice(&key.into_vec()); + println!( + "{}{:0b} key_u256: {}, val: {}", + padding, node.prefix, origin_key, val + ); + traversed_data.insert(origin_key, *val); + } + } + }; + traverse(&root, &mut func, &mut BitVecBE::new()); + assert_eq!(data, traversed_data); + // for pretty print + // assert!(false); + } + + #[test] + fn test_serialize_dict() -> Result<(), TonCellError> { + let data: HashMap = HashMap::from([ + (0, BigUint::from(25965603044000000000u128)), + (1, BigUint::from(5173255344000000000u64)), + (2, BigUint::from(344883687000000000u64)), + ]); + + let writer = |builder: &mut CellBuilder, val: &BigUint| { + builder.store_uint(150, val)?; + Ok(()) + }; + let cell = assert_ok!(serialize_dict(data.clone(), 8, writer)); + + let dict_loader = GenericDictLoader::new(key_extractor_u8, value_extractor_uint, 8); + let parsed_data = cell.load_generic_dict(&dict_loader)?; + assert_eq!(data, parsed_data); + + let bc_boc_b64 = "te6cckEBBgEAWgABGccNPKUADZm5MepOjMABAgHNAgMCASAEBQAnQAAAAAAAAAAAAAABMlF4tR2RgCAAJgAAAAAAAAAAAAABaFhaZZhr6AAAJgAAAAAAAAAAAAAAR8sYU4eC4AA1PIC5"; + let bc_cell = BagOfCells::parse_base64(bc_boc_b64)?; + let bc_cell = bc_cell.single_root()?.references[0].clone(); + assert_eq!(cell, *bc_cell); + Ok(()) + } +} diff --git a/core/src/cell/dict_loader.rs b/core/src/cell/dict_loader.rs index b391272..6e9cce9 100644 --- a/core/src/cell/dict_loader.rs +++ b/core/src/cell/dict_loader.rs @@ -31,7 +31,8 @@ pub fn key_extractor_u8(bit_len: usize, key: &[u8]) -> Result pub fn key_extractor_u16(bit_len: usize, key: &[u8]) -> Result { if bit_len == 16 { let arr: &[u8; 2] = key.try_into().map_err(|_| { - TonCellError::CellParserError("Insufficient bytes in the dictionary key.".to_string()) + let msg = format!("Got {} bytes in key, expected 2", key.len()); + TonCellError::CellParserError(msg) })?; Ok(u16::from_be_bytes(*arr)) } else { @@ -45,7 +46,8 @@ pub fn key_extractor_u16(bit_len: usize, key: &[u8]) -> Result Result { if bit_len == 32 { let arr: &[u8; 4] = key.try_into().map_err(|_| { - TonCellError::CellParserError("Insufficient bytes in the dictionary key.".to_string()) + let msg = format!("Got {} bytes in key, expected 4", key.len()); + TonCellError::CellParserError(msg) })?; Ok(u32::from_be_bytes(*arr)) } else { @@ -59,7 +61,8 @@ pub fn key_extractor_u32(bit_len: usize, key: &[u8]) -> Result Result { if bit_len == 64 { let arr: &[u8; 8] = key.try_into().map_err(|_| { - TonCellError::CellParserError("Insufficient bytes in the dictionary key.".to_string()) + let msg = format!("Got {} bytes in key, expected 8", key.len()); + TonCellError::CellParserError(msg) })?; Ok(u64::from_be_bytes(*arr)) } else { diff --git a/core/src/constants.rs b/core/src/constants.rs new file mode 100644 index 0000000..dba166a --- /dev/null +++ b/core/src/constants.rs @@ -0,0 +1,2 @@ +pub const MASTERCHAIN_ID: i32 = -1; +pub const SHARD_FULL: u64 = 0x8000_0000_0000_0000u64; diff --git a/core/src/lib.rs b/core/src/lib.rs index 4aa9d2f..3bfd13c 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,4 +1,5 @@ pub mod cell; +pub mod constants; pub mod message; pub mod mnemonic; pub mod types;