diff --git a/CHANGELOG.md b/CHANGELOG.md index 599c6ac9..e943fb50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,30 @@ For Helm chart changes, see [helm/ton-rust-node/CHANGELOG.md](helm/ton-rust-node The format is based on [Keep a Changelog](https://keepachangelog.com/). Versions follow the node release tags (e.g. `v0.1.2-mainnet`). +## [v0.5.2] - 2026-04-21 + +Image: `ghcr.io/rsquad/ton-rust-node/node:v0.5.2` + +### Changed + +- Optimized fast-sync overlay operation over QUIC + +### Fixed + +- TVM stack slice TL-serialization fix for LiteServer + +## [v0.5.1] - 2026-04-17 + +Image: `ghcr.io/rsquad/ton-rust-node/node:v0.5.1` + +### Added + +- Switch to ubuntu base image and add console binary + +### Fixed + +- Fix LiteServer response handling for listBlockTransactions so clients accept it correctly + ## [v0.5.0] - 2026-04-11 Image: `ghcr.io/rsquad/ton-rust-node/node:v0.5.0` diff --git a/src/adnl/src/overlay/mod.rs b/src/adnl/src/overlay/mod.rs index 0a9fabd6..b6e34c4e 100644 --- a/src/adnl/src/overlay/mod.rs +++ b/src/adnl/src/overlay/mod.rs @@ -26,7 +26,7 @@ use num_traits::pow::Pow; use std::{ borrow::Borrow, cmp::min, - collections::HashMap, + collections::{HashMap, HashSet}, convert::TryInto, sync::{ atomic::{AtomicU32, AtomicU64, Ordering}, @@ -359,13 +359,16 @@ enum OverlayType { }, // Overlay with externally certified members CertifiedMembers { - key: Option>, - // KeyId -> (Slot -> SlaveInfo) - root_members: HashMap, lockfree::map::Map>, - max_slaves: usize, // Prefix to use in broadcasts instead of Overlay::message_prefix bcast_prefix: Vec, certificate: Option, + key: Option>, + max_slaves: usize, + // Validator ADNL IDs, bypassing the certificate check + root_adnl_ids: HashSet>, + // Validator Signing KeyId -> (Slot -> SlaveInfo) + root_public_keys: HashMap, lockfree::map::Map>, + use_quic: bool, }, } @@ -378,7 +381,11 @@ impl OverlayType { } fn quic_requested(&self) -> bool { - matches!(self, OverlayType::Private { use_quic: true, .. }) + matches!( + self, + OverlayType::Private { use_quic: true, .. } + | OverlayType::CertifiedMembers { use_quic: true, .. } + ) } fn calc_message_prefix(&self, overlay_id: &OverlayShortId) -> Result> { @@ -583,10 +590,6 @@ impl Overlay { } } - pub(crate) fn calc_broadcast_twostep_neighbours(&self) -> u32 { - self.neighbours.count() as u32 - } - pub(crate) fn select_broadcast_neighbours( &self, count: u32, @@ -599,14 +602,33 @@ impl Overlay { &self, skip: Option<&Arc>, ) -> Vec> { + let root_adnl_ids = match &self.overlay_type { + OverlayType::CertifiedMembers { root_adnl_ids, .. } => Some(root_adnl_ids), + OverlayType::Private { .. } => None, + _ => { + let mut neighbours = Vec::new(); + let (mut iter, mut neighbour) = self.neighbours.first(); + while let Some(node) = neighbour { + let skipped = skip.map_or(false, |skip| &node == skip); + if !skipped { + neighbours.push(node); + } + neighbour = self.neighbours.next(&mut iter); + } + return neighbours; + } + }; let mut neighbours = Vec::new(); - let (mut iter, mut neighbour) = self.neighbours.first(); + let mut iter = None; + let mut neighbour = self.known_peers.next(&mut iter); while let Some(node) = neighbour { - let skipped = if let Some(skip) = &skip { &node == *skip } else { false }; - if !skipped { + let skipped = skip.map_or(false, |skip| &node == skip); + // Skip CertifiedMembers: only send twostep to root members (validators). + let root = root_adnl_ids.map_or(true, |roots| roots.contains(&node)); + if !skipped && root { neighbours.push(node); } - neighbour = self.neighbours.next(&mut iter); + neighbour = self.known_peers.next(&mut iter); } neighbours } @@ -618,6 +640,24 @@ impl Overlay { Ok(buf) } + fn calc_broadcast_twostep_neighbours(&self) -> u32 { + let root_adnl_ids = match &self.overlay_type { + OverlayType::CertifiedMembers { root_adnl_ids, .. } => Some(root_adnl_ids), + OverlayType::Private { .. } => None, + _ => return self.neighbours.count(), + }; + let mut count = 0u32; + let mut iter = None; + let mut neighbour = self.known_peers.next(&mut iter); + while let Some(node) = neighbour { + if root_adnl_ids.map_or(true, |root_adnl_ids| root_adnl_ids.contains(&node)) { + count += 1; + } + neighbour = self.known_peers.next(&mut iter); + } + count + } + fn check_peer(&self, peer: &Arc, certificate: Option<&MemberCertificate>) -> Result<()> { match &self.overlay_type { OverlayType::Public => Ok(()), @@ -627,8 +667,8 @@ impl Overlay { } Ok(()) } - OverlayType::CertifiedMembers { root_members, .. } => { - if root_members.contains_key(peer) { + OverlayType::CertifiedMembers { root_adnl_ids, .. } => { + if root_adnl_ids.contains(peer) { return Ok(()); } // Bcasts are sent without a certificate, hoping the target already knows @@ -1035,14 +1075,13 @@ impl Overlay { fn validate_certificate(&self, peer: &Arc, cert: &MemberCertificate) -> Result<()> { let utime = UnixTime::now() as u32; - - let (max_slaves, root_members) = - if let OverlayType::CertifiedMembers { max_slaves, root_members, .. } = + let (max_slaves, root_public_keys) = + if let OverlayType::CertifiedMembers { max_slaves, root_public_keys, .. } = &self.overlay_type { - (*max_slaves, root_members) + (*max_slaves, root_public_keys) } else { - fail!("Overlay type is not certificated members") + fail!("Overlay type is not certified members") }; // 1) Expire check @@ -1058,7 +1097,7 @@ impl Overlay { // 3) Issuer let issuer: Arc = (&cert.issued_by).try_into()?; - let Some(slaves_info) = root_members.get(issuer.id()) else { + let Some(slaves_info) = root_public_keys.get(issuer.id()) else { fail!("Certificate is issued by unknown member: {}", cert.issued_by); }; @@ -1557,22 +1596,27 @@ impl OverlayNode { &self, params: OverlayParams, overlay_key: Option<&Arc>, - root_members: &[Arc], + root_adnl_ids: &[Arc], + root_public_keys: &[Arc], // Can be empty if overlay created by non-validator certificate: Option, max_slaves: usize, + use_quic: bool, ) -> Result { - let mut root_members_full = HashMap::with_capacity(root_members.len()); - for member in root_members { - root_members_full.insert(member.clone(), lockfree::map::Map::new()); + let root_adnl_set: HashSet> = root_adnl_ids.iter().cloned().collect(); + let mut root_public_keys_map = HashMap::with_capacity(root_public_keys.len()); + for pk in root_public_keys { + root_public_keys_map.insert(pk.clone(), lockfree::map::Map::new()); } let overlay_type = OverlayType::CertifiedMembers { - root_members: root_members_full, - max_slaves, bcast_prefix: OverlayUtils::calc_message_prefix(params.overlay_id)?, certificate, key: overlay_key.cloned(), + max_slaves, + root_adnl_ids: root_adnl_set, + root_public_keys: root_public_keys_map, + use_quic, }; - self.add_typed_private_overlay(overlay_type, params, root_members) + self.add_typed_private_overlay(overlay_type, params, root_adnl_ids) } /// Broadcast message @@ -1977,11 +2021,7 @@ impl OverlayNode { penalty: 1, to_block: Self::MAX_FAIL_COUNT, }; - let quic = if let OverlayType::Private { use_quic: true, .. } = &overlay_type { - self.quic.get().cloned() - } else { - None - }; + let quic = if overlay_type.quic_requested() { self.quic.get().cloned() } else { None }; let overlay = Overlay { adnl: self.adnl.clone(), rldp: self.rldp.get().cloned(), diff --git a/src/adnl/tests/test_overlay.rs b/src/adnl/tests/test_overlay.rs index 06185b45..a81ade0d 100644 --- a/src/adnl/tests/test_overlay.rs +++ b/src/adnl/tests/test_overlay.rs @@ -1373,7 +1373,17 @@ async fn test_overlay_semiprivate() -> Result<()> { adnl.start_over_udp(vec![pi.overlay.clone().unwrap()]).await.unwrap(); let params = OverlayParams::with_id_only(overlay_id); - assert!(overlay.add_semiprivate_overlay(params, Some(&pi.key), roots, None, 1)?); + // In this test the overlay key serves both as ADNL id and as cert signer, + // so root_adnl_ids == root_public_keys. + assert!(overlay.add_semiprivate_overlay( + params, + Some(&pi.key), + roots, + roots, + None, + 1, + false + )?); overlay .add_consumer(overlay_id, Arc::new(TestConsumer { received: pi.received.clone() }))?; @@ -1428,7 +1438,17 @@ async fn test_overlay_semiprivate() -> Result<()> { pi.certificate = Some(cert.clone()); let params = OverlayParams::with_id_only(overlay_id); - assert!(overlay.add_semiprivate_overlay(params, Some(&pi.key), roots, Some(cert), 1)?); + // In this test the overlay key serves both as ADNL id and as cert signer, + // so root_adnl_ids == root_public_keys. + assert!(overlay.add_semiprivate_overlay( + params, + Some(&pi.key), + roots, + roots, + Some(cert), + 1, + false + )?); overlay .add_consumer(overlay_id, Arc::new(TestConsumer { received: pi.received.clone() }))?; diff --git a/src/node/src/network/fast_sync_overlay_client.rs b/src/node/src/network/fast_sync_overlay_client.rs index 1975f5c6..b1d2543c 100644 --- a/src/node/src/network/fast_sync_overlay_client.rs +++ b/src/node/src/network/fast_sync_overlay_client.rs @@ -27,7 +27,7 @@ use ton_api::{ }, BoxedSerialize, }; -use ton_block::{fail, KeyId, KeyOption, Result, ShardIdent, ValidatorSet}; +use ton_block::{fail, KeyId, KeyOption, Result, ShardIdent, ValidatorDescr}; const MAX_FAST_SYNC_OVERLAY_CLIENTS: usize = 5; @@ -36,12 +36,13 @@ pub struct FastSyncOverlayClient { shard: ShardIdent, client: Arc, engine: Arc, + use_twostep: bool, } impl FastSyncOverlayClient { pub async fn new( shard: ShardIdent, - validators: &ValidatorSet, + validators: &[ValidatorDescr], key: Option<&Arc>, certificate: Option, cancellation_token: tokio_util::sync::CancellationToken, @@ -49,10 +50,13 @@ impl FastSyncOverlayClient { engine: Arc, policy: DhtSearchPolicy, default_rldp_roundtrip: Option, + use_quic: bool, ) -> Result> { - let mut root_members = Vec::new(); - for vd in validators.list() { - root_members.push(vd.adnl_addr().clone()); + let mut root_adnl_ids = Vec::with_capacity(validators.len()); + let mut root_public_keys = Vec::with_capacity(validators.len()); + for vd in validators { + root_adnl_ids.push(vd.adnl_addr().clone()); + root_public_keys.push(vd.public_key.pub_key().id().clone()); } let id = ton_api::ton::ton_node::fastsyncoverlayid::FastSyncOverlayId { zero_state_file_hash: engine.zerostate_id()?.file_hash().clone(), @@ -62,6 +66,11 @@ impl FastSyncOverlayClient { let overlay_key = OverlayKey { name: id_full.clone().into() }; let id_short = OverlayShortId::from_data(hash(overlay_key)?); + let use_twostep = use_quic && network_context.stack.quic.is_some() && key.is_some(); + if use_quic && !use_twostep { + log::warn!("Fast sync overlay {id_short}: use_quic=true but QUIC or key not available"); + } + log::info!( "Creating fast sync overlay client for shard {shard}, id: {id_short}, adnl id: {}", key.map(|k| k.id().to_string()).unwrap_or_default() @@ -70,7 +79,8 @@ impl FastSyncOverlayClient { let client = OverlayClient::new_semiprivate( id_short.clone(), id_full, - root_members, + root_adnl_ids, + root_public_keys, key, certificate, network_context, @@ -78,10 +88,11 @@ impl FastSyncOverlayClient { policy, default_rldp_roundtrip, MAX_FAST_SYNC_OVERLAY_CLIENTS, + use_twostep, ) .await?; - let result = Arc::new(Self { id: id_short, shard, client, engine }); + let result = Arc::new(Self { id: id_short, shard, client, engine, use_twostep }); result.clone().listen_broadcasts(cancellation_token.clone()); @@ -263,4 +274,38 @@ impl FastSyncOverlayClient { } } } + + pub async fn send_twostep_broadcast( + &self, + data: &TaggedByteSlice<'_>, + flags: u32, + ) -> Result<()> { + match self + .client + .broadcast_twostep(data, None, flags | OverlayNode::FLAG_BCAST_ANY_SENDER) + .await + { + Ok(info) => { + #[cfg(feature = "telemetry")] + log::debug!( + "sent twostep broadcast {:08x} in {} to {} nodes", + data.tag, + self.id, + info.send_to + ); + #[cfg(not(feature = "telemetry"))] + log::debug!("sent twostep broadcast in {} to {} nodes", self.id, info.send_to); + + Ok(()) + } + Err(e) => { + log::warn!("Error sending twostep broadcast: {}", e); + Err(e) + } + } + } + + pub fn use_twostep(&self) -> bool { + self.use_twostep + } } diff --git a/src/node/src/network/full_node_overlays.rs b/src/node/src/network/full_node_overlays.rs index 4e332ce8..dc611064 100644 --- a/src/node/src/network/full_node_overlays.rs +++ b/src/node/src/network/full_node_overlays.rs @@ -58,8 +58,8 @@ use ton_api::{ }; use ton_block::{ base64_encode, error, AccountIdPrefixFull, BlockIdExt, BlockSignaturesVariant, Cell, - ConfigParams, ImportedMsgQueueLimits, KeyOption, Result, ShardIdent, ValidatorSet, - BASE_WORKCHAIN_ID, + ConfigParams, ImportedMsgQueueLimits, KeyOption, Result, ShardIdent, ValidatorDescr, + ValidatorSet, BASE_WORKCHAIN_ID, }; /// The router encapsulates work with full node overlays at the logical level. It abstracts creation, @@ -386,12 +386,20 @@ impl FullNodeOverlaysRouter { self: &Arc, config: &ConfigParams, ) -> Result<()> { - let new_vset = config.validator_set()?; - let key = self.try_get_our_key(&new_vset)?; + let prev_vset = config.prev_validator_set()?; + let this_vset = config.validator_set()?; + let next_vset = config.next_validator_set()?; + let key = self.try_get_our_key(&this_vset)?; + let mc_use_quic = config.get_mc_simplex_config()?.map_or(false, |c| c.use_quic); + let shard_use_quic = config.get_shard_simplex_config()?.map_or(false, |c| c.use_quic); self.update_fast_sync_overlays( - &new_vset, + &prev_vset, + &this_vset, + &next_vset, config.base_workchain()?.monitor_min_split(), key.as_ref(), + mc_use_quic, + shard_use_quic, ) .await?; Ok(()) @@ -450,12 +458,16 @@ impl FullNodeOverlaysRouter { async fn update_fast_sync_overlays( self: &Arc, - new_validators: &ValidatorSet, + prev_validators: &ValidatorSet, + this_validators: &ValidatorSet, + next_validators: &ValidatorSet, new_monitor_min_split: u8, key: Option<&Arc>, + mc_use_quic: bool, + shard_use_quic: bool, ) -> Result<()> { let mut cur_validators = self.validators.lock().await; - let validators_changed = *cur_validators != *new_validators; + let validators_changed = *cur_validators != *this_validators; let old_monitor_min_split = self.monitor_min_split_for_fast_sync.load(Ordering::Relaxed); if (old_monitor_min_split == new_monitor_min_split) && !validators_changed { return Ok(()); @@ -464,13 +476,26 @@ impl FullNodeOverlaysRouter { log::info!( "Updating fast sync overlays: \ monitor min split {old_monitor_min_split} -> {new_monitor_min_split}, \ - validators changed {validators_changed}" + validators changed {validators_changed}, \ + mc_use_quic {mc_use_quic}, shard_use_quic {shard_use_quic}" ); - let create_overlay = |shard: &ShardIdent| { + // Root members = union of past + current + next validator sets. + // Duplicates (a validator present in multiple rounds) are collapsed by + // the HashSet/HashMap inside add_semiprivate_overlay + let mut validators: Vec = Vec::with_capacity( + prev_validators.list().len() + + this_validators.list().len() + + next_validators.list().len(), + ); + validators.extend(prev_validators.list().iter().cloned()); + validators.extend(this_validators.list().iter().cloned()); + validators.extend(next_validators.list().iter().cloned()); + + let create_overlay = |shard: &ShardIdent, use_quic: bool| { FastSyncOverlayClient::new( shard.clone(), - new_validators, + &validators, key, None, self.network.cancellation_token().child_token(), @@ -478,6 +503,7 @@ impl FullNodeOverlaysRouter { self.engine.clone(), self.policy.clone(), self.network.default_rldp_roundtrip(), + use_quic, ) }; @@ -494,7 +520,7 @@ impl FullNodeOverlaysRouter { if let Some(old) = self.fast_sync_overlays.remove(&shard) { old.val().stop(); } - let overlay = create_overlay(&shard).await?; + let overlay = create_overlay(&shard, shard_use_quic).await?; self.fast_sync_overlays.insert(shard, overlay) } else { self.fast_sync_overlays.remove(&shard) @@ -517,7 +543,7 @@ impl FullNodeOverlaysRouter { if key.is_none() { self.monitor_min_split_for_fast_sync.store(new_monitor_min_split, Ordering::Relaxed); log::info!("We are not a validator"); - *cur_validators = new_validators.clone(); + *cur_validators = this_validators.clone(); return Ok(()); } @@ -527,7 +553,7 @@ impl FullNodeOverlaysRouter { if let Some(old) = self.fast_sync_overlays.remove(&shard) { old.val().stop(); } - let overlay = create_overlay(&shard).await?; + let overlay = create_overlay(&shard, mc_use_quic).await?; self.fast_sync_overlays.insert(shard, overlay); } @@ -535,7 +561,7 @@ impl FullNodeOverlaysRouter { update_monitor_min_split(new_monitor_min_split, true).await?; self.monitor_min_split_for_fast_sync.store(new_monitor_min_split, Ordering::Relaxed); - *cur_validators = new_validators.clone(); + *cur_validators = this_validators.clone(); Ok(()) } @@ -612,10 +638,14 @@ impl FullNodeOverlaysRouter { #[cfg(feature = "telemetry")] tag: BlockBroadcastCompressed::constructor_const(), }; - if let Some(fast_sync_client) = fast_sync_client { - fast_sync_client - .send_broadcast(&broadcast, 0, AdnlSendMethod::Fast) - .await?; + if let Some(fast_sync_client) = &fast_sync_client { + if fast_sync_client.use_twostep() { + fast_sync_client.send_twostep_broadcast(&broadcast, 0).await?; + } else { + fast_sync_client + .send_broadcast(&broadcast, 0, AdnlSendMethod::Fast) + .await?; + } } for overlay in custom_overlays { overlay.send_broadcast(&broadcast, 0, AdnlSendMethod::Fast).await?; @@ -650,8 +680,14 @@ impl FullNodeOverlaysRouter { tag: BlockBroadcastCompressedV2::constructor_const(), }; - if let Some(fast_sync_client) = fast_sync_client { - fast_sync_client.send_broadcast(&broadcast, 0, AdnlSendMethod::Fast).await?; + if let Some(fast_sync_client) = &fast_sync_client { + if fast_sync_client.use_twostep() { + fast_sync_client.send_twostep_broadcast(&broadcast, 0).await?; + } else { + fast_sync_client + .send_broadcast(&broadcast, 0, AdnlSendMethod::Fast) + .await?; + } } for overlay in custom_overlays { overlay.send_broadcast(&broadcast, 0, AdnlSendMethod::Fast).await?; @@ -758,7 +794,11 @@ impl FullNodeOverlaysRouter { overlay.send_broadcast(&broadcast, 0, AdnlSendMethod::Fast).await?; } if let Some(client) = fast_sync_client { - client.send_broadcast(&broadcast, 0, AdnlSendMethod::Fast).await?; + if client.use_twostep() { + client.send_twostep_broadcast(&broadcast, 0).await?; + } else { + client.send_broadcast(&broadcast, 0, AdnlSendMethod::Fast).await?; + } } } Ok(()) diff --git a/src/node/src/network/overlay_client.rs b/src/node/src/network/overlay_client.rs index 9aa3b552..a2517057 100644 --- a/src/node/src/network/overlay_client.rs +++ b/src/node/src/network/overlay_client.rs @@ -130,7 +130,8 @@ impl OverlayClient { pub async fn new_semiprivate( id: Arc, id_full: OverlayId, - root_members: Vec>, + root_adnl_ids: Vec>, + root_public_keys: Vec>, key: Option<&Arc>, certificate: Option, network_context: Arc, @@ -138,15 +139,18 @@ impl OverlayClient { policy: DhtSearchPolicy, default_rldp_roundtrip: Option, max_clients: usize, + use_quic: bool, ) -> Result> { // Add a new overlay to the protocol let params = OverlayParams::with_id_only(&id); network_context.stack.overlay.add_semiprivate_overlay( params, key, - &root_members, + &root_adnl_ids, + &root_public_keys, certificate, max_clients, + use_quic, )?; OverlayClient::init( @@ -157,7 +161,7 @@ impl OverlayClient { policy, default_rldp_roundtrip, true, - root_members, + root_adnl_ids, None, ) .await @@ -298,6 +302,18 @@ impl OverlayClient { self.ctx.overlay_node().broadcast(&self.ctx.id, data, source, flags, method).await } + pub async fn broadcast_twostep( + &self, + data: &TaggedByteSlice<'_>, + source: Option<&Arc>, + flags: u32, + ) -> Result { + self.ctx + .overlay_node() + .broadcast_twostep(&self.ctx.id, data, source, flags, Vec::new()) + .await + } + pub async fn send_adnl_query_to_peer( &self, peer: &Arc, diff --git a/src/node/src/rpc_server/token.rs b/src/node/src/rpc_server/token.rs index dc91d73f..c3294e5d 100644 --- a/src/node/src/rpc_server/token.rs +++ b/src/node/src/rpc_server/token.rs @@ -155,6 +155,10 @@ fn parse_msg_address_from_stack_entry(entry: &StackEntry) -> Result { + if let Ok(root) = read_single_root_boc(&slice.bytes) { + return parse_msg_address_from_cell(&root); + } + const ADDR_BITS: usize = 267; if slice.bytes.len() * 8 < ADDR_BITS { diff --git a/src/vm/src/smart_contract_info.rs b/src/vm/src/smart_contract_info.rs index 83994ad0..6f196c72 100644 --- a/src/vm/src/smart_contract_info.rs +++ b/src/vm/src/smart_contract_info.rs @@ -405,7 +405,8 @@ pub fn convert_stack(items: &[StackItem]) -> Result> { StackEntryNumber { number }.into_boxed() } StackItem::Slice(slice) => { - let bytes = slice.get_bytestring(0); + let cell = slice.clone().into_cell()?; + let bytes = write_boc(&cell)?; let slice = ton_api::ton::tvm::slice::Slice { bytes }; StackEntrySlice { slice }.into_boxed() } @@ -484,8 +485,8 @@ pub fn convert_ton_stack(items: &[StackEntry]) -> Result> { StackItem::int(value) } StackEntry::Tvm_StackEntrySlice(slice) => { - let slice = &slice.slice; - let slice = SliceData::from_raw(slice.bytes.as_slice(), slice.bytes.len() * 8); + let cell = read_single_root_boc(&slice.slice.bytes)?; + let slice = SliceData::load_cell(cell)?; StackItem::slice(slice) } StackEntry::Tvm_StackEntryTuple(tuple) => { diff --git a/src/vm/src/tests/test_smart_contract_info.rs b/src/vm/src/tests/test_smart_contract_info.rs index 06f19b54..aa5a70ee 100644 --- a/src/vm/src/tests/test_smart_contract_info.rs +++ b/src/vm/src/tests/test_smart_contract_info.rs @@ -455,6 +455,50 @@ fn test_convert_stack() { assert_eq!(result[0], tuple); } +#[test] +fn test_convert_stack_slice_roundtrip_preserves_msg_addr_bit_len() { + let owner: MsgAddressInt = "EQAW-1_rm44ppdD6qzcSSyZDAZH-KwldLeXmb2uTH6-WSkG0".parse().unwrap(); + let owner_slice = owner.write_to_bitstring().unwrap(); + assert_eq!(owner_slice.remaining_bits(), 267); + + let items = convert_stack(&[StackItem::slice(owner_slice)]).unwrap(); + let roundtrip = convert_ton_stack(&items).unwrap(); + + assert_eq!(roundtrip.len(), 1); + + let roundtrip_slice = match &roundtrip[0] { + StackItem::Slice(s) => s.clone(), + other => panic!("expected slice, got {:?}", other), + }; + + assert_eq!(roundtrip_slice.remaining_bits(), 267); + + let mut slice = roundtrip_slice; + let parsed = MsgAddressInt::construct_from(&mut slice).unwrap(); + assert_eq!(parsed, owner); + assert_eq!(slice.remaining_bits(), 0); +} + +#[test] +fn test_convert_stack_slice_roundtrip_preserves_slice_window() { + let mut source = SliceData::from_raw(vec![0b1010_1100, 0b1111_0000], 16); + source.move_by(3).unwrap(); + let slice = source.get_slice(7).unwrap(); + + let items = convert_stack(&[StackItem::slice(slice.clone())]).unwrap(); + let roundtrip = convert_ton_stack(&items).unwrap(); + + let roundtrip_slice = match &roundtrip[0] { + StackItem::Slice(s) => s.clone(), + other => panic!("expected slice, got {:?}", other), + }; + + assert_eq!(roundtrip_slice.remaining_bits(), slice.remaining_bits()); + for bit in 0..slice.remaining_bits() { + assert_eq!(roundtrip_slice.get_bit(bit).unwrap(), slice.get_bit(bit).unwrap()); + } +} + #[test] fn test_convert_stack_none_is_empty_list() { let items = convert_stack(&[StackItem::None]).unwrap();