From dcf6f8c81b735d36700d525262ef331a5e27bcce Mon Sep 17 00:00:00 2001 From: Lapo4kaKek Date: Sun, 5 Apr 2026 21:51:42 +0300 Subject: [PATCH 1/3] feat: impl run_smc_method in ls --- src/node/src/network/liteserver.rs | 401 +++++++++++++++++++++++++- src/node/src/tests/test_liteserver.rs | 280 +++++++++++++++++- 2 files changed, 665 insertions(+), 16 deletions(-) diff --git a/src/node/src/network/liteserver.rs b/src/node/src/network/liteserver.rs index 212af10..4517254 100644 --- a/src/node/src/network/liteserver.rs +++ b/src/node/src/network/liteserver.rs @@ -49,12 +49,12 @@ use ton_api::{ libraryresultwithproof::LibraryResultWithProof, lookupblockresult::LookupBlockResult, masterchaininfo::MasterchainInfo, masterchaininfoext::MasterchainInfoExt, outmsgqueuesize::OutMsgQueueSize, outmsgqueuesizes::OutMsgQueueSizes, - partialblockproof::PartialBlockProof, sendmsgstatus::SendMsgStatus, - shardblocklink::ShardBlockLink, shardblockproof::ShardBlockProof, shardinfo::ShardInfo, - transactionid::TransactionId, transactionid3::TransactionId3, - transactioninfo::TransactionInfo, transactionlist::TransactionList, - transactionmetadata::TransactionMetadata, validatorstats::ValidatorStats, - version::Version, BlockTransactions as BlockTxEnum, + partialblockproof::PartialBlockProof, runmethodresult::RunMethodResult, + sendmsgstatus::SendMsgStatus, shardblocklink::ShardBlockLink, + shardblockproof::ShardBlockProof, shardinfo::ShardInfo, transactionid::TransactionId, + transactionid3::TransactionId3, transactioninfo::TransactionInfo, + transactionlist::TransactionList, transactionmetadata::TransactionMetadata, + validatorstats::ValidatorStats, version::Version, BlockTransactions as BlockTxEnum, BlockTransactionsExt as BlockTxExtEnum, Error as ErrorEnum, }, rpc::lite_server::{ @@ -64,7 +64,7 @@ use ton_api::{ GetMasterchainInfo, GetMasterchainInfoExt, GetOneTransaction, GetOutMsgQueueSizes, GetShardBlockProof, GetShardInfo, GetState, GetTime, GetTransactions, GetValidatorStats, GetVersion, ListBlockTransactions, ListBlockTransactionsExt, - LookupBlock, LookupBlockWithProof, Query, QueryPrefix, SendMessage, + LookupBlock, LookupBlockWithProof, Query, QueryPrefix, RunSmcMethod, SendMessage, WaitMasterchainSeqno, }, ton_node::{blockid::BlockId, zerostateidext::ZeroStateIdExt}, @@ -74,9 +74,13 @@ use ton_api::{ use ton_block::{ error, fail, read_single_root_boc, write_boc, write_boc_multi, Account, AccountId, AccountIdPrefixFull, Block, BlockError, BlockIdExt, BocWriter, BuilderData, Cell, - Deserializable, HashmapAugType, HashmapType, MerkleProof, MsgAddrStd, MsgAddressInt, Result, - Serializable, ShardIdent, ShardStateUnsplit, SliceData, Transaction, UInt256, UsageTree, - INVALID_WORKCHAIN_ID, MASTERCHAIN_ID, + Deserializable, HashmapAugType, HashmapType, IBitstring, MerkleProof, MsgAddrStd, + MsgAddressInt, Result, Serializable, ShardIdent, ShardStateUnsplit, SliceData, Transaction, + UInt256, UsageTree, INVALID_WORKCHAIN_ID, MASTERCHAIN_ID, +}; +use ton_vm::{ + smart_contract_info::{convert_stack, convert_ton_stack}, + stack::StackItem, }; pub type LookupMode = i32; @@ -709,6 +713,146 @@ impl LiteServerQuerySubscriber { .await? } + async fn run_smc_method( + engine: &Arc, + mode: i32, + block_id: BlockIdExt, + account_id: AccountIdTl, + method_id: i64, + params: Vec, + ) -> Result { + if block_id.shard().workchain_id() == INVALID_WORKCHAIN_ID { + fail!("Reference block id for runSmcMethod is invalid"); + } + if params.len() >= 65536 { + fail!("more than 64k parameter bytes passed"); + } + if mode & !0x3f != 0 { + fail!("unsupported mode in runSmcMethod"); + } + + let account_address = MsgAddressInt::AddrStd(MsgAddrStd { + anycast: None, + workchain_id: account_id.workchain as i8, + address: account_id.id.clone().into(), + }); + + let id; + let shardblk; + let shard_state; + let mc_state_root; + let mut shard_proof_bytes = vec![]; + + if !block_id.shard_id.is_masterchain() { + id = block_id.clone(); + shardblk = block_id.clone(); + shard_state = engine.load_and_pin_state(&block_id).await?; + let master_ref = shard_state + .state() + .state()? + .master_ref() + .ok_or_else(|| error!("shard state has no master_ref"))?; + let mc_block_id = BlockIdExt::from_ext_blk(master_ref.master.clone()); + let mc_state = engine.load_and_pin_state(&mc_block_id).await?; + mc_state_root = mc_state.state().root_cell().clone(); + } else { + id = if block_id.seq_no != !0 { + block_id.clone() + } else { + (*get_last_liteserver_state_block(engine)?).clone() + }; + let mc_state = engine.load_and_pin_state(&id).await?; + mc_state_root = mc_state.state().root_cell().clone(); + + if account_id.workchain == MASTERCHAIN_ID { + shard_state = mc_state; + shardblk = id.clone(); + } else { + let mut sb_id = None; + for top_id in mc_state.state().top_blocks(account_address.workchain_id())? { + if top_id.shard().contains_address(&account_address)? { + sb_id = Some(top_id); + break; + } + } + shardblk = + sb_id.ok_or_else(|| error!("No shard found for address {account_address}"))?; + shard_state = engine.load_and_pin_state(&shardblk).await?; + + if mode & 1 != 0 { + let mc_state_proof_cell = state_header_proof(engine, &id).await?; + let shard_id = shardblk.shard_id.clone(); + let mc_root_for_proof = mc_state_root.clone(); + shard_proof_bytes = tokio::task::spawn_blocking(move || { + let shard_header = create_shard_proof(&mc_root_for_proof, &shard_id)?; + BocWriter::with_roots([mc_state_proof_cell, shard_header.serialize()?])? + .write_to_vec() + }) + .await??; + } + } + } + + let state_proof_cell = + if mode & 1 != 0 { Some(state_header_proof(engine, &shardblk).await?) } else { None }; + + let shard_root = shard_state.state().root_cell().clone(); + let addr = account_address.clone(); + let (exit_code, result_bytes, proof_bytes) = tokio::task::spawn_blocking( + move || -> Result<(i32, Option>, Option>)> { + let usage_tree = UsageTree::with_params(shard_root.clone(), true); + let uss = ShardStateUnsplit::construct_from_cell(usage_tree.root_cell())?; + let shard_account = + uss.read_accounts()?.account(&addr.address())?.unwrap_or_default(); + + let proof = if let Some(spc) = state_proof_cell { + let acc_proof = MerkleProof::create_by_usage_tree(&shard_root, &usage_tree)?; + Some(BocWriter::with_roots([spc, acc_proof.serialize()?])?.write_to_vec()?) + } else { + None + }; + + let account = shard_account.read_account()?; + if account.get_code().is_none() { + return Ok((-256, None, proof)); + } + + let input_stack = deserialize_vm_stack_boc(¶ms)?; + let input_entries = convert_stack(&input_stack)?; + + let run_result = ton_vm::run_smc_method( + &shard_account, + mc_state_root, + method_id as u32, + input_entries, + )?; + + let result_bytes = if mode & 4 != 0 { + let result_items = convert_ton_stack(&run_result.stack)?; + Some(serialize_vm_stack_boc(&result_items)?) + } else { + None + }; + + Ok((run_result.exit_code, result_bytes, proof)) + }, + ) + .await??; + + Ok(RunMethodResult { + mode, + id: id.into(), + shardblk: shardblk.into(), + shard_proof: if mode & 1 != 0 { Some(shard_proof_bytes) } else { None }, + proof: proof_bytes, + state_proof: None, + init_c7: None, + lib_extras: None, + exit_code, + result: result_bytes, + }) + } + async fn get_account_state_coalesced( context: &SubscriberContext, block_id: BlockIdExt, @@ -2521,6 +2665,8 @@ impl LiteServerQuerySubscriber { q.lt, q.utime ), + RunSmcMethod => + |q| Self::run_smc_method(engine, q.mode, q.id, q.account, q.method_id, q.params), SendMessage => |q| Self::send_message(engine, q.body), ); @@ -2781,7 +2927,8 @@ impl Subscriber for LiteServerQuerySubscriber { || query.is::() || query.is::() || query.is::() - || query.is::(); + || query.is::() + || query.is::(); // Wait-prefixed queries go through the shared wait registry if let Some((want_seqno, timeout_ms)) = maybe_wait { @@ -2818,6 +2965,238 @@ impl Subscriber for LiteServerQuerySubscriber { } } +// VmStack BOC serialization helpers (C++ compatible format) +// Format: vm_stack#_ depth:(## 24) stack:(VmStackList depth) = VmStack; +// vm_stk_nil#_ = VmStackList 0; +// vm_stk_cons#_ {n:#} rest:^(VmStackList n) tos:VmStackValue = VmStackList (n + 1); + +fn deserialize_vm_stack_boc(boc: &[u8]) -> Result> { + if boc.is_empty() { + return Ok(Vec::new()); + } + let root = read_single_root_boc(boc)?; + let mut cs = SliceData::load_cell(root)?; + let depth = cs.get_next_int(24)? as usize; + if depth == 0 { + return Ok(Vec::new()); + } + if depth > 1024 { + fail!("VmStack depth {} exceeds limit 1024", depth); + } + let rest = cs.checked_drain_reference()?; + let top = read_vm_stack_value(&mut cs)?; + let mut items = vec![top]; + + let mut rest_cell = rest; + for _ in 0..depth - 1 { + let mut rest_cs = SliceData::load_cell(rest_cell)?; + let next_rest = rest_cs.checked_drain_reference()?; + let item = read_vm_stack_value(&mut rest_cs)?; + items.push(item); + rest_cell = next_rest; + } + items.reverse(); + Ok(items) +} + +fn serialize_vm_stack_boc(items: &[StackItem]) -> Result> { + let n = items.len(); + let mut cb = BuilderData::new(); + cb.append_bits(n, 24)?; + if n == 0 { + return write_boc(&cb.into_cell()?); + } + // vm_stk_nil = empty cell + let mut rest = BuilderData::new().into_cell()?; + for item in items.iter().take(n - 1) { + let mut cons = BuilderData::new(); + cons.checked_append_reference(rest)?; + write_vm_stack_value(item, &mut cons)?; + rest = cons.into_cell()?; + } + cb.checked_append_reference(rest)?; + write_vm_stack_value(&items[n - 1], &mut cb)?; + write_boc(&cb.into_cell()?) +} + +fn read_vm_stack_value(cs: &mut SliceData) -> Result { + let tag = cs.get_next_byte()?; + match tag { + 0x00 => Ok(StackItem::None), + 0x01 => { + let val = cs.get_next_i64()?; + Ok(StackItem::int(val)) + } + 0x02 => { + let next = cs.get_next_byte()?; + match next { + 0xFF => Ok(StackItem::integer(ton_vm::stack::integer::IntegerData::nan())), + 0x00 => { + let bytes = cs.get_next_u256()?; + Ok(StackItem::integer(ton_vm::stack::integer::IntegerData::from_bytes( + bytes, 256, false, true, + )?)) + } + 0x01 => { + let bytes_256 = cs.get_next_u256()?; + let mut bytes = vec![0xff]; + bytes.extend_from_slice(&bytes_256); + Ok(StackItem::integer(ton_vm::stack::integer::IntegerData::from_bytes( + &bytes, 264, true, true, + )?)) + } + _ => fail!("VmStackValue: invalid big int subtag 0x{:02x}", next), + } + } + 0x03 => { + let cell = cs.checked_drain_reference()?; + Ok(StackItem::cell(cell)) + } + 0x04 => { + let cell = cs.checked_drain_reference()?; + let st_bits = cs.get_next_int(10)? as usize; + let end_bits = cs.get_next_int(10)? as usize; + let st_ref = cs.get_next_int(3)? as usize; + let end_ref = cs.get_next_int(3)? as usize; + if st_bits > end_bits || st_ref > end_ref { + fail!("VmStackValue: invalid slice window"); + } + let slice = SliceData::load_cell_with_window(cell, st_bits..end_bits, st_ref..end_ref)?; + Ok(StackItem::slice(slice)) + } + 0x05 => { + let cell = cs.checked_drain_reference()?; + let cell_slice = SliceData::load_cell(cell)?; + let mut builder = BuilderData::new(); + builder.checked_append_references_and_data(&cell_slice)?; + Ok(StackItem::builder(builder)) + } + 0x07 => { + let n = cs.get_next_u16()? as usize; + read_vm_tuple(cs, n) + } + _ => fail!("VmStackValue: unknown tag 0x{:02x}", tag), + } +} + +fn read_vm_tuple(cs: &mut SliceData, n: usize) -> Result { + if n == 0 { + return Ok(StackItem::tuple(Vec::new())); + } + if n == 1 { + let cell = cs.checked_drain_reference()?; + let mut item_cs = SliceData::load_cell(cell)?; + let item = read_vm_stack_value(&mut item_cs)?; + return Ok(StackItem::tuple(vec![item])); + } + let mut head = cs.checked_drain_reference()?; + let tail = cs.checked_drain_reference()?; + let mut tail_cs = SliceData::load_cell(tail)?; + let last = read_vm_stack_value(&mut tail_cs)?; + + let mut remaining = n - 1; + let mut items = Vec::with_capacity(n); + while remaining > 1 { + let mut head_cs = SliceData::load_cell(head)?; + let next_head = head_cs.checked_drain_reference()?; + let item_cell = head_cs.checked_drain_reference()?; + let mut item_cs = SliceData::load_cell(item_cell)?; + items.push(read_vm_stack_value(&mut item_cs)?); + head = next_head; + remaining -= 1; + } + let mut head_cs = SliceData::load_cell(head)?; + let first = read_vm_stack_value(&mut head_cs)?; + items.push(first); + items.reverse(); + items.push(last); + Ok(StackItem::tuple(items)) +} + +fn write_vm_stack_value(item: &StackItem, cb: &mut BuilderData) -> Result<()> { + match item { + StackItem::None => { + cb.append_bits(0x00, 8)?; + } + StackItem::Integer(value) => { + if value.is_nan() { + cb.append_bits(0x02FF, 16)?; + } else if value.fits_in(64)? { + cb.append_bits(0x01, 8)?; + let v: i64 = value.as_integer_value(i64::MIN..=i64::MAX)?; + cb.append_i64(v)?; + } else { + cb.append_bits(0x0100, 15)?; + let int_builder = value.as_builder(257, true, true)?; + cb.append_builder(&int_builder)?; + } + } + StackItem::Cell(cell) => { + cb.append_bits(0x03, 8)?; + cb.checked_append_reference(cell.clone())?; + } + StackItem::Slice(slice) => { + cb.append_bits(0x04, 8)?; + cb.checked_append_reference(slice.cell()?)?; + let refs = slice.get_references(); + cb.append_bits(slice.pos(), 10)?; + cb.append_bits(slice.pos() + slice.remaining_bits(), 10)?; + cb.append_bits(refs.start, 3)?; + cb.append_bits(refs.end, 3)?; + } + StackItem::Builder(bd) => { + cb.append_bits(0x05, 8)?; + cb.checked_append_reference(bd.as_ref().clone().into_cell()?)?; + } + StackItem::Tuple(items) => { + write_vm_tuple(items, cb)?; + } + StackItem::Continuation(_) => { + fail!("Cannot serialize continuation in liteserver response"); + } + } + Ok(()) +} + +fn write_vm_tuple(items: &[StackItem], cb: &mut BuilderData) -> Result<()> { + let n = items.len(); + cb.append_bits(0x07, 8)?; + cb.append_u16(n as u16)?; + if n == 0 { + return Ok(()); + } + if n == 1 { + let cell = serialize_single_vm_value(&items[0])?; + cb.checked_append_reference(cell)?; + return Ok(()); + } + let mut head: Option = None; + let mut tail: Option = None; + for (i, item) in items.iter().enumerate() { + std::mem::swap(&mut head, &mut tail); + if i > 1 { + let mut chain = BuilderData::new(); + chain.checked_append_reference(tail.take().unwrap())?; + chain.checked_append_reference(head.take().unwrap())?; + head = Some(chain.into_cell()?); + } + tail = Some(serialize_single_vm_value(item)?); + } + if let Some(h) = head { + cb.checked_append_reference(h)?; + } + if let Some(t) = tail { + cb.checked_append_reference(t)?; + } + Ok(()) +} + +fn serialize_single_vm_value(item: &StackItem) -> Result { + let mut cb = BuilderData::new(); + write_vm_stack_value(item, &mut cb)?; + cb.into_cell() +} + #[cfg(test)] #[path = "../tests/test_liteserver.rs"] mod tests; diff --git a/src/node/src/tests/test_liteserver.rs b/src/node/src/tests/test_liteserver.rs index 2252a18..aa5d05b 100644 --- a/src/node/src/tests/test_liteserver.rs +++ b/src/node/src/tests/test_liteserver.rs @@ -33,11 +33,12 @@ use ton_api::{ ton::lite_server::{accountid::AccountId as AccountIdTl, BlockHeader, LookupBlockResult}, }; use ton_block::{ - fail, read_single_root_boc, write_boc, AccountDispatchQueue, BlkPrevInfo, Block, BlockExtra, - BlockIdExt, BlockInfo, ChildCell, CurrencyCollection, DispatchQueue, EnqueuedMsg, ExtBlkRef, - GetRepresentationHash, IntermediateAddress, InternalMessageHeader, KeyId, MerkleUpdate, - Message, MsgAddressInt, MsgEnvelope, OutMsgQueue, OutMsgQueueExtra, OutMsgQueueInfo, - OutMsgQueueKey, ShardIdent, UInt256, ValueFlow, + fail, read_single_root_boc, write_boc, AccountBlock, AccountDispatchQueue, AccountStatus, + BlkPrevInfo, Block, BlockExtra, BlockIdExt, BlockInfo, ChildCell, CurrencyCollection, + DispatchQueue, EnqueuedMsg, ExtBlkRef, GetRepresentationHash, HashUpdate, IntermediateAddress, + InternalMessageHeader, KeyId, MerkleProof, MerkleUpdate, Message, MsgAddressInt, MsgEnvelope, + OutMsgQueue, OutMsgQueueExtra, OutMsgQueueInfo, OutMsgQueueKey, ShardAccountBlocks, ShardIdent, + Transaction, Transactions, UInt256, ValueFlow, }; //static DB_COUNTER: AtomicUsize = AtomicUsize::new(0); @@ -2511,3 +2512,272 @@ async fn test_account_state_lru_eviction() -> Result<()> { Ok(()) } + +// list_block_transactions tests +fn build_block_with_txs( + shard: ShardIdent, + seq_no: u32, + txs: &[(UInt256, u64)], +) -> Result<(Vec, BlockIdExt)> { + let mut sab = ShardAccountBlocks::default(); + for (account_id, lt) in txs { + let mut tx = Transaction::with_address_and_status( + account_id.clone().into(), + AccountStatus::AccStateActive, + ); + tx.set_logical_time(*lt); + let acc_block = AccountBlock::with_transaction(account_id.clone().into(), &tx)?; + sab.insert(&acc_block)?; + } + let mut extra = BlockExtra::default(); + extra.write_account_blocks(&sab)?; + + let mut info = BlockInfo::default(); + info.set_shard(shard.clone()); + info.set_seq_no(seq_no)?; + info.set_prev_stuff( + false, + &BlkPrevInfo::Block { + prev: ExtBlkRef { + end_lt: 1, + seq_no: seq_no.saturating_sub(1), + root_hash: UInt256::from([0xAB; 32]), + file_hash: UInt256::from([0xCD; 32]), + }, + }, + )?; + let block = Block::with_params(0, info, ValueFlow::default(), MerkleUpdate::default(), extra)?; + finalize_block_to_boc( + BlockIdExt { + shard_id: shard, + seq_no, + root_hash: UInt256::default(), + file_hash: UInt256::default(), + }, + &block, + ) +} + +fn build_block_with_multi_tx_account( + shard: ShardIdent, + seq_no: u32, + account_id: &UInt256, + lts: &[u64], +) -> Result<(Vec, BlockIdExt)> { + let account_addr: AccountId = account_id.clone().into(); + let mut transactions = Transactions::default(); + for lt in lts { + let mut tx = Transaction::with_address_and_status( + account_addr.clone(), + AccountStatus::AccStateActive, + ); + tx.set_logical_time(*lt); + transactions.insert(&tx)?; + } + let hash_update = HashUpdate::with_hashes(UInt256::default(), UInt256::default()); + let acc_block = AccountBlock::with_params(&account_addr, &transactions, &hash_update)?; + let mut sab = ShardAccountBlocks::default(); + sab.insert(&acc_block)?; + + let mut extra = BlockExtra::default(); + extra.write_account_blocks(&sab)?; + + let mut info = BlockInfo::default(); + info.set_shard(shard.clone()); + info.set_seq_no(seq_no)?; + info.set_prev_stuff( + false, + &BlkPrevInfo::Block { + prev: ExtBlkRef { + end_lt: 1, + seq_no: seq_no.saturating_sub(1), + root_hash: UInt256::from([0xAB; 32]), + file_hash: UInt256::from([0xCD; 32]), + }, + }, + )?; + let block = Block::with_params(0, info, ValueFlow::default(), MerkleUpdate::default(), extra)?; + finalize_block_to_boc( + BlockIdExt { + shard_id: shard, + seq_no, + root_hash: UInt256::default(), + file_hash: UInt256::default(), + }, + &block, + ) +} + +#[tokio::test] +async fn test_list_block_transactions_empty_block() -> Result<()> { + let mut engine = LiteServerTestEngine::new().await; + let shard = ShardIdent::masterchain(); + let (data, block_id) = build_block_with_txs(shard, 200, &[])?; + engine.add_mock_block_with_handle(block_id.clone(), data).await?; + let engine = Arc::new(engine); + + let result = LiteServerQuerySubscriber::list_block_transactions( + &(engine.clone() as Arc), + block_id.clone(), + 0, + 100, + None, + ) + .await?; + + assert_eq!(result.ids.len(), 0); + assert_eq!(result.incomplete, false.into()); + assert_eq!(result.req_count, 100); + assert_eq!(result.id, block_id); + Ok(()) +} + +#[tokio::test] +async fn test_list_block_transactions_single_tx() -> Result<()> { + let mut engine = LiteServerTestEngine::new().await; + let shard = ShardIdent::masterchain(); + let acc = UInt256::from([0x11; 32]); + let (data, block_id) = build_block_with_txs(shard, 201, &[(acc.clone(), 100)])?; + engine.add_mock_block_with_handle(block_id.clone(), data).await?; + let engine = Arc::new(engine); + + let result = LiteServerQuerySubscriber::list_block_transactions( + &(engine.clone() as Arc), + block_id.clone(), + 0, + 100, + None, + ) + .await?; + + assert_eq!(result.ids.len(), 1); + assert_eq!(result.incomplete, false.into()); + let tid = &result.ids[0]; + assert_eq!(tid.account.as_ref().unwrap(), &acc); + assert_eq!(tid.lt.unwrap(), 100); + assert!(tid.hash.is_some()); + Ok(()) +} + +#[tokio::test] +async fn test_list_block_transactions_with_proof() -> Result<()> { + let mut engine = LiteServerTestEngine::new().await; + let shard = ShardIdent::masterchain(); + let txs = vec![(UInt256::from([0x11; 32]), 100)]; + let (data, block_id) = build_block_with_txs(shard, 206, &txs)?; + engine.add_mock_block_with_handle(block_id.clone(), data).await?; + let engine = Arc::new(engine); + + let result_no_proof = LiteServerQuerySubscriber::list_block_transactions( + &(engine.clone() as Arc), + block_id.clone(), + 0, + 100, + None, + ) + .await?; + assert!(result_no_proof.proof.is_empty()); + + let result_with_proof = LiteServerQuerySubscriber::list_block_transactions( + &(engine.clone() as Arc), + block_id.clone(), + 0x20, // WANT_PROOF + 100, + None, + ) + .await?; + assert!(!result_with_proof.proof.is_empty()); + let proof_cell = read_single_root_boc(&result_with_proof.proof)?; + let _proof = MerkleProof::construct_from_cell(proof_cell)?; + Ok(()) +} + +#[tokio::test] +async fn test_list_block_transactions_ext_returns_tx_cells() -> Result<()> { + let mut engine = LiteServerTestEngine::new().await; + let shard = ShardIdent::masterchain(); + let txs = vec![(UInt256::from([0x11; 32]), 100), (UInt256::from([0x22; 32]), 200)]; + let (data, block_id) = build_block_with_txs(shard, 207, &txs)?; + engine.add_mock_block_with_handle(block_id.clone(), data).await?; + let engine = Arc::new(engine); + + let result = LiteServerQuerySubscriber::list_block_transactions_ext( + &(engine.clone() as Arc), + block_id.clone(), + 0, + 100, + None, + ) + .await?; + + assert_eq!(result.id, block_id); + assert_eq!(result.incomplete, false.into()); + assert!(!result.transactions.is_empty()); + Ok(()) +} + +#[tokio::test] +async fn test_list_block_transactions_multiple_txs_per_account() -> Result<()> { + let mut engine = LiteServerTestEngine::new().await; + let shard = ShardIdent::masterchain(); + let acc = UInt256::from([0x42; 32]); + let (data, block_id) = build_block_with_multi_tx_account(shard, 210, &acc, &[10, 20, 30])?; + engine.add_mock_block_with_handle(block_id.clone(), data).await?; + let engine = Arc::new(engine); + + let result = LiteServerQuerySubscriber::list_block_transactions( + &(engine.clone() as Arc), + block_id.clone(), + 0, + 100, + None, + ) + .await?; + + assert_eq!(result.ids.len(), 3); + assert_eq!(result.ids[0].lt.unwrap(), 10); + assert_eq!(result.ids[1].lt.unwrap(), 20); + assert_eq!(result.ids[2].lt.unwrap(), 30); + for tid in &result.ids { + assert_eq!(tid.account.as_ref().unwrap(), &acc); + } + Ok(()) +} + +// VM stack BOC serialization roundtrip tests +#[test] +fn test_vm_stack_boc_roundtrip_mixed_types() { + let empty_cell = ton_block::BuilderData::new().into_cell().unwrap(); + let cases: Vec> = vec![ + vec![], + vec![StackItem::int(0)], + vec![StackItem::int(42), StackItem::int(-1)], + vec![StackItem::int(1), StackItem::int(2), StackItem::int(3)], + vec![StackItem::cell(empty_cell), StackItem::None], + ]; + for items in &cases { + let boc = serialize_vm_stack_boc(items).unwrap(); + let decoded = deserialize_vm_stack_boc(&boc).unwrap(); + assert_eq!(decoded.len(), items.len(), "length mismatch for {:?}", items); + } +} + +#[test] +fn test_vm_stack_boc_roundtrip_extreme_ints() { + let items = vec![StackItem::int(i64::MAX), StackItem::int(i64::MIN)]; + let boc = serialize_vm_stack_boc(&items).unwrap(); + let decoded = deserialize_vm_stack_boc(&boc).unwrap(); + assert_eq!(decoded.len(), 2); + match &decoded[0] { + StackItem::Integer(v) => { + assert_eq!(v.as_integer_value(i64::MIN..=i64::MAX).unwrap(), i64::MAX) + } + other => panic!("expected int(MAX), got {:?}", other), + } + match &decoded[1] { + StackItem::Integer(v) => { + assert_eq!(v.as_integer_value(i64::MIN..=i64::MAX).unwrap(), i64::MIN) + } + other => panic!("expected int(MIN), got {:?}", other), + } +} From 52816820bfb15d0aab25ec59b7e423aa040407c0 Mon Sep 17 00:00:00 2001 From: Lapo4kaKek Date: Sun, 5 Apr 2026 23:07:00 +0300 Subject: [PATCH 2/3] fix: return exit_code -256 for non-existent accounts in runSmcMethod LS --- src/node/src/network/liteserver.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/node/src/network/liteserver.rs b/src/node/src/network/liteserver.rs index 4517254..e5022f7 100644 --- a/src/node/src/network/liteserver.rs +++ b/src/node/src/network/liteserver.rs @@ -802,8 +802,7 @@ impl LiteServerQuerySubscriber { move || -> Result<(i32, Option>, Option>)> { let usage_tree = UsageTree::with_params(shard_root.clone(), true); let uss = ShardStateUnsplit::construct_from_cell(usage_tree.root_cell())?; - let shard_account = - uss.read_accounts()?.account(&addr.address())?.unwrap_or_default(); + let shard_account_opt = uss.read_accounts()?.account(&addr.address())?; let proof = if let Some(spc) = state_proof_cell { let acc_proof = MerkleProof::create_by_usage_tree(&shard_root, &usage_tree)?; @@ -812,10 +811,10 @@ impl LiteServerQuerySubscriber { None }; - let account = shard_account.read_account()?; - if account.get_code().is_none() { - return Ok((-256, None, proof)); - } + let shard_account = match shard_account_opt { + Some(sa) if sa.read_account()?.get_code().is_some() => sa, + _ => return Ok((-256, None, proof)), + }; let input_stack = deserialize_vm_stack_boc(¶ms)?; let input_entries = convert_stack(&input_stack)?; From 65667ec9237bc4d1f3cb2637ee744da086da5822 Mon Sep 17 00:00:00 2001 From: Slava Date: Thu, 9 Apr 2026 21:19:41 +0300 Subject: [PATCH 3/3] Support changes for Simplex in LiteServer --- src/node/src/block.rs | 41 +- src/node/src/network/liteserver.rs | 507 +++++++++++-------- src/node/src/rpc_server/handlers.rs | 16 +- src/node/src/tests/test_liteserver.rs | 59 ++- src/node/src/validator/collator.rs | 13 + src/tl/ton_api/tl/lite_api.tl | 4 +- src/vm/src/lib.rs | 2 +- src/vm/src/smart_contract_info.rs | 55 +- src/vm/src/tests/test_smart_contract_info.rs | 54 +- src/vm/tests/common/test_framework.rs | 4 +- 10 files changed, 481 insertions(+), 274 deletions(-) diff --git a/src/node/src/block.rs b/src/node/src/block.rs index f859e27..2ac6986 100644 --- a/src/node/src/block.rs +++ b/src/node/src/block.rs @@ -21,16 +21,19 @@ use ton_api::ton::{ shardblocklink::ShardBlockLink, shardblockproof::ShardBlockProof, signature::Signature, - signatureset, BlockLink, SignatureSet, + signature_set::signatureset::{ + Ordinary as TlSignatureSetOrdinary, Simplex as TlSignatureSetSimplex, + }, + BlockLink, SignatureSet, }, Bool, }; use ton_block::{ error, fail, read_single_root_boc, write_boc, AccountBlock, AccountId, AccountIdPrefixFull, - BlkPrevInfo, Block, BlockIdExt, BocReader, Cell, ConfigParams, CryptoSignaturePair, - Deserializable, ExtBlkRef, HashmapAugType, HashmapType, McStateExtra, MerkleProof, - OldMcBlocksInfo, Result, Serializable, ShardDescr, ShardIdent, ShardStateUnsplit, SliceData, - UInt256, UsageTree, + BlkPrevInfo, Block, BlockIdExt, BlockSignaturesVariant, BocReader, Cell, ConfigParams, + CryptoSignaturePair, Deserializable, ExtBlkRef, HashmapAugType, HashmapType, McStateExtra, + MerkleProof, OldMcBlocksInfo, Result, Serializable, ShardDescr, ShardIdent, ShardStateUnsplit, + SliceData, UInt256, UsageTree, }; pub type ProofMode = i32; @@ -503,12 +506,28 @@ fn sigset_from_proof_boc(id: &BlockIdExt, boc: &[u8]) -> Result { Ok(true) }, )?; - let result = SignatureSet::LiteServer_SignatureSet(signatureset::SignatureSet { - validator_set_hash: block_sigs.validator_info().validator_list_hash_short as i32, - catchain_seqno: block_sigs.validator_info().catchain_seqno as i32, - signatures: out_sigs, - }); - Ok(result) + let validator_set_hash = block_sigs.validator_info().validator_list_hash_short as i32; + let cc_seqno = block_sigs.validator_info().catchain_seqno as i32; + + Ok(match block_sigs { + BlockSignaturesVariant::Ordinary(_) => { + SignatureSet::LiteServer_SignatureSet_Ordinary(TlSignatureSetOrdinary { + validator_set_hash, + catchain_seqno: cc_seqno, + signatures: out_sigs, + }) + } + BlockSignaturesVariant::Simplex(simplex) => { + SignatureSet::LiteServer_SignatureSet_Simplex(TlSignatureSetSimplex { + cc_seqno, + validator_set_hash, + signatures: out_sigs, + session_id: simplex.session_id.clone(), + slot: simplex.slot as i32, + candidate: simplex.candidate_data_bytes()?, + }) + } + }) } async fn build_zs_config_proof( diff --git a/src/node/src/network/liteserver.rs b/src/node/src/network/liteserver.rs index b2a06d1..f63706d 100644 --- a/src/node/src/network/liteserver.rs +++ b/src/node/src/network/liteserver.rs @@ -15,6 +15,7 @@ use crate::{ }, engine_traits::{EngineOperations, Stoppable}, error::NodeError, + shard_states_keeper::PinnedShardStateGuard, types::awaiters_pool::AwaitersPool, validating_utils::UNREGISTERED_CHAIN_MAX_LEN, }; @@ -78,10 +79,18 @@ use ton_block::{ MsgAddressInt, Result, Serializable, ShardIdent, ShardStateUnsplit, SliceData, Transaction, UInt256, UsageTree, INVALID_WORKCHAIN_ID, MASTERCHAIN_ID, }; -use ton_vm::{ - smart_contract_info::{convert_stack, convert_ton_stack}, - stack::StackItem, -}; +use ton_vm::{smart_contract_info::convert_stack, stack::StackItem}; + +const SEQNO_ANY: u32 = u32::MAX; + +const RUN_SMC_METHOD_PROOFS: i32 = 0x1; +const RUN_SMC_METHOD_STATE_PROOF: i32 = 0x2; +const RUN_SMC_METHOD_RESULT: i32 = 0x4; +const RUN_SMC_METHOD_INIT_C7: i32 = 0x8; +const RUN_SMC_METHOD_LIB_EXTRAS: i32 = 0x10; +const RUN_SMC_METHOD_FULL_C7: i32 = 0x20; +const RUN_SMC_METHOD_SUPPORTED: i32 = 0x3f; +const RUN_SMC_METHOD_ERROR_CODE: i32 = -0x100; pub type LookupMode = i32; @@ -232,8 +241,10 @@ fn make_shard_descr_proof( if got.seq_no != expected_descr.seq_no || got.root_hash != expected_descr.root_hash { log::warn!( "make_shard_descr_proof: descr mismatch; expected (seqno={}, rh={:x}) got (seqno={}, rh={:x})", - expected_descr.seq_no, expected_descr.root_hash, - got.seq_no, got.root_hash, + expected_descr.seq_no, + expected_descr.root_hash, + got.seq_no, + got.root_hash, ); } @@ -271,6 +282,126 @@ async fn state_header_proof(engine: &Arc, id: &BlockIdExt) Ok(proof.serialize()?) } +struct ResolvedAccount { + id: BlockIdExt, + shardblk: BlockIdExt, + mc_state: PinnedShardStateGuard, + _shard_state: PinnedShardStateGuard, + shard_proof: Option>, + proof: Option>, + account_cell: Option, + gen_utime: u32, + gen_lt: u64, +} + +async fn resolve_account_state( + engine: &Arc, + block_id: BlockIdExt, + account_address: &MsgAddressInt, + workchain: i32, + need_proofs: bool, +) -> Result { + let id; + let shardblk; + let shard_state_guard: PinnedShardStateGuard; + let mc_state_guard: PinnedShardStateGuard; + let mut shard_proof = None; + + if !block_id.shard_id.is_masterchain() { + // Reference block is not from masterchain — it must be fully specified + // and exactly this block must contain the account state + shardblk = block_id.clone(); + shard_state_guard = engine.load_and_pin_state(&block_id).await?; + let master_ref = shard_state_guard + .state() + .state()? + .master_ref() + .ok_or_else(|| error!("shard state has no master_ref"))?; + let mc_block_id = BlockIdExt::from_ext_blk(master_ref.master.clone()); + id = mc_block_id.clone(); + mc_state_guard = engine.load_and_pin_state(&mc_block_id).await?; + } else { + id = if block_id.seq_no != SEQNO_ANY { + // Reference block is specified + block_id.clone() + } else { + // Reference block is not specified, use last known mc state + (*get_last_liteserver_state_block(engine)?).clone() + }; + mc_state_guard = engine.load_and_pin_state(&id).await?; + + if workchain == MASTERCHAIN_ID { + // Account is in masterchain — read directly from the reference block state + shard_state_guard = mc_state_guard.clone(); + shardblk = id.clone(); + } else { + // Find the shard containing the account + let mut sb_id = None; + for top_id in mc_state_guard.state().top_blocks(account_address.workchain_id())? { + if top_id.shard().contains_address(account_address)? { + sb_id = Some(top_id); + break; + } + } + shardblk = + sb_id.ok_or_else(|| error!("No shard found for address {account_address}"))?; + shard_state_guard = engine.load_and_pin_state(&shardblk).await?; + + // Build shard proofs (mc block→state + mc state→shard) + if need_proofs { + let mc_state_proof_cell = state_header_proof(engine, &id).await?; + let shard_id = shardblk.shard_id.clone(); + let mc_state_root = mc_state_guard.state().root_cell().clone(); + shard_proof = Some( + tokio::task::spawn_blocking(move || { + let shard_header = create_shard_proof(&mc_state_root, &shard_id)?; + BocWriter::with_roots([mc_state_proof_cell, shard_header.serialize()?])? + .write_to_vec() + }) + .await??, + ); + } + } + } + + let state_proof_cell = + if need_proofs { Some(state_header_proof(engine, &shardblk).await?) } else { None }; + + // Lookup account in the shard state and build its proof + let shard_root = shard_state_guard.state().root_cell().clone(); + let addr = account_address.clone(); + tokio::task::spawn_blocking(move || { + let usage_tree = UsageTree::with_params(shard_root.clone(), true); + let state = ShardStateUnsplit::construct_from_cell(usage_tree.root_cell())?; + let shard_account = state.read_accounts()?.account(&addr.address())?; + let account_cell = shard_account.map(|acc| acc.account_cell()); + + // proof: BOC with 2 roots (shard block→state proof + state→account proof) + let proof = state_proof_cell + .map(|spc| { + let acc_proof = MerkleProof::create_by_usage_tree(&shard_root, &usage_tree)?; + BocWriter::with_roots([spc, acc_proof.serialize()?])?.write_to_vec() + }) + .transpose()?; + + let gen_utime = state.gen_time(); + let gen_lt = state.gen_lt(); + + Ok(ResolvedAccount { + id, + shardblk, + mc_state: mc_state_guard, + _shard_state: shard_state_guard, + shard_proof, + proof, + account_cell, + gen_utime, + gen_lt, + }) + }) + .await? +} + fn create_state_proof(block_root: &Cell) -> Result { let usage_tree = UsageTree::with_params(block_root.clone(), true); let block = Block::construct_from_cell(usage_tree.root_cell())?; @@ -583,116 +714,36 @@ impl LiteServerQuerySubscriber { fail!("Requested account id is not contained in the shard of the reference block"); } - // Choose block and state - - // if block_id is specified - it is - // if not - last mc block - let id; - - // if block_id is specified - it is - // if not - shard (or mc block) account readed from - let shardblk; - - // if block_id is specified - empty - // if account is in mc - empty - // else - boc with 2 roots: mc state proof and shard descr proof - let mut shard_proof = vec![]; - - // State to read account from and corresponding block - let shard_state; - let state_proof_cell; - - if !block_id.shard_id.is_masterchain() { - // Reference block is not from masterchain - it means the block must be fully specifed - // and exactly this block must contain the account state - id = block_id.clone(); - shardblk = block_id.clone(); - shard_state = engine.load_and_pin_state(&block_id).await?; - state_proof_cell = state_header_proof(engine, &block_id).await?; - } else { - id = if block_id.seq_no != !0 { - // Reference block is specified - block_id.clone() - } else { - // Reference block is not specified, use last known shard state - (*get_last_liteserver_state_block(engine)?).clone() - }; - let mc_state = engine.load_and_pin_state(&id).await?; - - if account_id.workchain == MASTERCHAIN_ID { - // If account is in masterchain - read it directly from reference block's state - shard_state = mc_state; - shardblk = id.clone(); - state_proof_cell = state_header_proof(engine, &id).await?; - } else { - // Find for needed shard - let mut sb_id = None; - for top_id in mc_state.state().top_blocks(account_address.workchain_id())? { - if top_id.shard().contains_address(&account_address)? { - sb_id = Some(top_id); - break; + let resolved = + resolve_account_state(engine, block_id, &account_address, account_id.workchain, true) + .await?; + let ResolvedAccount { id, shardblk, shard_proof, proof, account_cell, .. } = resolved; + let proof = proof.expect("always built with need_proofs=true"); + let shard_proof = shard_proof.unwrap_or_default(); + + if !is_slow.load(Ordering::Relaxed) { + if let Some(ref ac) = account_cell { + let acc = Account::construct_from_cell(ac.clone())?; + if let Some(si) = acc.storage_info() { + if si.used().bits() > 30 * 1024 * 8 || si.used().cells() > 300 { + log::info!( + "Account {} is big: storage stat: {} bits, {} cells, processing is postponed", + account_id.id, + si.used().bits(), + si.used().cells(), + ); + is_slow.store(true, Ordering::Relaxed); + return Ok(AccountState::default()); } } - shardblk = - sb_id.ok_or_else(|| error!("No shard found for address {account_address}"))?; - shard_state = engine.load_and_pin_state(&shardblk).await?; - state_proof_cell = state_header_proof(engine, &shardblk).await?; - - // Build shard proofs - let mc_state_proof_cell = state_header_proof(engine, &id).await?; - let shard_id = shardblk.shard_id.clone(); - shard_proof = tokio::task::spawn_blocking(move || { - let shard_header_proof = - create_shard_proof(mc_state.state().root_cell(), &shard_id)?; - BocWriter::with_roots([mc_state_proof_cell, shard_header_proof.serialize()?])? - .write_to_vec() - }) - .await??; } } - tokio::task::spawn_blocking(move || { - // Lookup account and build its proof - let usage_tree = UsageTree::with_params(shard_state.state().root_cell().clone(), true); - let uss = ShardStateUnsplit::construct_from_cell(usage_tree.root_cell())?; - let shard_account = uss - .read_accounts()? - .account(&account_address.address())?; - let account_cell = shard_account.as_ref().map(|sa| sa.account_cell()); - let acc_proof = - MerkleProof::create_by_usage_tree(shard_state.state().root_cell(), &usage_tree)?; - // boc with 2 roots: state proof and account proof - // (In account proof - dict value + first ref) - let proof = BocWriter::with_roots([state_proof_cell, acc_proof.serialize()?])? - .write_to_vec()?; - - if !is_slow.load(Ordering::Relaxed) { - if let Some(sa) = shard_account { - if let Some(si) = sa.read_account()?.storage_info() { - if si.used().bits() > 30 * 1024 * 8 || si.used().cells() > 300 { - log::info!( - "Account {} is big: storage stat: {} bits, {} cells, processing is postponed", - account_id.id, - si.used().bits(), - si.used().cells(), - ); - is_slow.store(true, Ordering::Relaxed); - return Ok(AccountState::default()); - } - } - } - } - - let mut state = Vec::new(); - if let Some(ac) = account_cell { - let account_cell = usage_tree - .original_cell(&ac.repr_hash()) - .ok_or_else(|| error!("account root not found in usage tree"))?; + let state = if let Some(account_cell) = account_cell { + tokio::task::spawn_blocking(move || { if !prune { - // include full account - state = write_boc(&account_cell)?; + write_boc(&account_cell) } else { - // With balance, but without code, data and libs let usage_tree = UsageTree::with_root(account_cell.clone()); let acc = Account::construct_from_cell(usage_tree.root_cell())?; let balance_root = acc @@ -700,18 +751,20 @@ impl LiteServerQuerySubscriber { .map(|cc| cc.other.root().map(|c| c.repr_hash())) .flatten() .unwrap_or_default(); - state = MerkleProof::create_with_subtrees( + MerkleProof::create_with_subtrees( &account_cell, |hash| usage_tree.contains(hash), |hash| hash == &balance_root, )? - .write_to_bytes()?; + .write_to_bytes() } - } + }) + .await?? + } else { + Vec::new() + }; - Ok(AccountState { id, shardblk, shard_proof, proof, state }) - }) - .await? + Ok(AccountState { id, shardblk, shard_proof, proof, state }) } async fn run_smc_method( @@ -728,7 +781,7 @@ impl LiteServerQuerySubscriber { if params.len() >= 65536 { fail!("more than 64k parameter bytes passed"); } - if mode & !0x3f != 0 { + if mode & !RUN_SMC_METHOD_SUPPORTED != 0 { fail!("unsupported mode in runSmcMethod"); } @@ -738,119 +791,120 @@ impl LiteServerQuerySubscriber { address: account_id.id.clone().into(), }); - let id; - let shardblk; - let shard_state; - let mc_state_root; - let mut shard_proof_bytes = vec![]; - - if !block_id.shard_id.is_masterchain() { - id = block_id.clone(); - shardblk = block_id.clone(); - shard_state = engine.load_and_pin_state(&block_id).await?; - let master_ref = shard_state - .state() - .state()? - .master_ref() - .ok_or_else(|| error!("shard state has no master_ref"))?; - let mc_block_id = BlockIdExt::from_ext_blk(master_ref.master.clone()); - let mc_state = engine.load_and_pin_state(&mc_block_id).await?; - mc_state_root = mc_state.state().root_cell().clone(); - } else { - id = if block_id.seq_no != !0 { - block_id.clone() + let resolved = resolve_account_state( + engine, + block_id, + &account_address, + account_id.workchain, + mode & RUN_SMC_METHOD_PROOFS != 0, + ) + .await?; + let mc_state_root = resolved.mc_state.state().root_cell().clone(); + + let lib_extras = + if mode & RUN_SMC_METHOD_LIB_EXTRAS != 0 { Some(Vec::new()) } else { None }; + + let make_result = move |exit_code, result, state_proof, init_c7| RunMethodResult { + mode, + id: resolved.id.into(), + shardblk: resolved.shardblk.into(), + shard_proof: resolved.shard_proof, + proof: resolved.proof, + state_proof, + init_c7, + lib_extras, + exit_code, + result, + }; + + let empty_result = if mode & RUN_SMC_METHOD_RESULT != 0 { Some(Vec::new()) } else { None }; + let empty_init_c7 = + if mode & RUN_SMC_METHOD_INIT_C7 != 0 { Some(Vec::new()) } else { None }; + + let account_cell = match resolved.account_cell { + Some(cell) => cell, + None => { + let empty_state_proof = + if mode & RUN_SMC_METHOD_STATE_PROOF != 0 { Some(Vec::new()) } else { None }; + return Ok(make_result( + RUN_SMC_METHOD_ERROR_CODE, + empty_result, + empty_state_proof, + empty_init_c7, + )); + } + }; + + tokio::task::spawn_blocking(move || -> Result { + let account_usage = if mode & RUN_SMC_METHOD_STATE_PROOF != 0 { + Some(UsageTree::with_params(account_cell.clone(), true)) } else { - (*get_last_liteserver_state_block(engine)?).clone() + None }; - let mc_state = engine.load_and_pin_state(&id).await?; - mc_state_root = mc_state.state().root_cell().clone(); - - if account_id.workchain == MASTERCHAIN_ID { - shard_state = mc_state; - shardblk = id.clone(); + let account = if let Some(ref usage) = account_usage { + Account::construct_from_cell(usage.root_cell())? } else { - let mut sb_id = None; - for top_id in mc_state.state().top_blocks(account_address.workchain_id())? { - if top_id.shard().contains_address(&account_address)? { - sb_id = Some(top_id); - break; - } - } - shardblk = - sb_id.ok_or_else(|| error!("No shard found for address {account_address}"))?; - shard_state = engine.load_and_pin_state(&shardblk).await?; - - if mode & 1 != 0 { - let mc_state_proof_cell = state_header_proof(engine, &id).await?; - let shard_id = shardblk.shard_id.clone(); - let mc_root_for_proof = mc_state_root.clone(); - shard_proof_bytes = tokio::task::spawn_blocking(move || { - let shard_header = create_shard_proof(&mc_root_for_proof, &shard_id)?; - BocWriter::with_roots([mc_state_proof_cell, shard_header.serialize()?])? - .write_to_vec() + Account::construct_from_cell(account_cell.clone())? + }; + + if account.get_code().is_none() { + let state_proof = account_usage + .map(|usage| { + MerkleProof::create_by_usage_tree(&account_cell, &usage)?.write_to_bytes() }) - .await??; - } + .transpose()?; + return Ok(make_result( + RUN_SMC_METHOD_ERROR_CODE, + empty_result, + state_proof, + empty_init_c7, + )); } - } - let state_proof_cell = - if mode & 1 != 0 { Some(state_header_proof(engine, &shardblk).await?) } else { None }; + let input_stack = deserialize_vm_stack_boc(¶ms)?; + let input_entries = convert_stack(&input_stack)?; - let shard_root = shard_state.state().root_cell().clone(); - let addr = account_address.clone(); - let (exit_code, result_bytes, proof_bytes) = tokio::task::spawn_blocking( - move || -> Result<(i32, Option>, Option>)> { - let usage_tree = UsageTree::with_params(shard_root.clone(), true); - let uss = ShardStateUnsplit::construct_from_cell(usage_tree.root_cell())?; - let shard_account_opt = uss.read_accounts()?.account(&addr.address())?; + let run = ton_vm::run_smc_method( + &account, + mc_state_root.clone(), + method_id as u32, + input_entries, + resolved.gen_utime, + resolved.gen_lt, + )?; - let proof = if let Some(spc) = state_proof_cell { - let acc_proof = MerkleProof::create_by_usage_tree(&shard_root, &usage_tree)?; - Some(BocWriter::with_roots([spc, acc_proof.serialize()?])?.write_to_vec()?) + // Always serialize stack when state_proof is requested — serialization + // visits data cells referenced from the result, capturing them in the usage tree + let result = if mode & (RUN_SMC_METHOD_RESULT | RUN_SMC_METHOD_STATE_PROOF) != 0 { + let cell = serialize_vm_stack(&run.stack)?; + if mode & RUN_SMC_METHOD_RESULT != 0 { + Some(write_boc(&cell)?) } else { None - }; - - let shard_account = match shard_account_opt { - Some(sa) if sa.read_account()?.get_code().is_some() => sa, - _ => return Ok((-256, None, proof)), - }; - - let input_stack = deserialize_vm_stack_boc(¶ms)?; - let input_entries = convert_stack(&input_stack)?; - - let run_result = ton_vm::run_smc_method( - &shard_account, - mc_state_root, - method_id as u32, - input_entries, - )?; + } + } else { + None + }; - let result_bytes = if mode & 4 != 0 { - let result_items = convert_ton_stack(&run_result.stack)?; - Some(serialize_vm_stack_boc(&result_items)?) - } else { - None - }; + let state_proof = account_usage + .map(|usage| { + MerkleProof::create_by_usage_tree(&account_cell, &usage)?.write_to_bytes() + }) + .transpose()?; - Ok((run_result.exit_code, result_bytes, proof)) - }, - ) - .await??; + let init_c7 = if mode & RUN_SMC_METHOD_INIT_C7 != 0 { + let mut smc_info = run.smc_info; + if mode & RUN_SMC_METHOD_FULL_C7 == 0 { + smc_info.config_params = Default::default(); + } + Some(serialize_vm_stack_value_boc(&smc_info.as_temp_data_item())?) + } else { + None + }; - Ok(RunMethodResult { - mode, - id: id.into(), - shardblk: shardblk.into(), - shard_proof: if mode & 1 != 0 { Some(shard_proof_bytes) } else { None }, - proof: proof_bytes, - state_proof: None, - init_c7: None, - lib_extras: None, - exit_code, - result: result_bytes, + Ok(make_result(run.exit_code, result, state_proof, init_c7)) }) + .await? } async fn get_account_state_coalesced( @@ -865,7 +919,7 @@ impl LiteServerQuerySubscriber { // 2. Parallel duplicate requests share a single computation via coalescing. // Resolve "latest" block_id to actual id for a stable cache key. - let resolved_root_hash = if block_id.seq_no == u32::MAX { + let resolved_root_hash = if block_id.seq_no == SEQNO_ANY { get_last_liteserver_state_block(&context.engine)?.root_hash.clone() } else { block_id.root_hash.clone() @@ -1153,6 +1207,8 @@ impl LiteServerQuerySubscriber { cfg.root().ok_or_else(|| error!("No config root in state {id}"))?.repr_hash(), ); } else { + param_list.sort_unstable(); + param_list.dedup(); for &pid in ¶m_list { let key_bits: SliceData = pid.write_to_bitstring()?; if let Some(leaf) = cfg.config_params.get(key_bits)? { @@ -1546,10 +1602,10 @@ impl LiteServerQuerySubscriber { let handle = engine .load_block_handle(&block_id)? .ok_or_else(|| error!("no handle for {}", block_id))?; - let bp = engine.load_block_proof(&handle, !block_id.is_masterchain()).await?; + let raw = engine.load_block_raw(&handle).await?; tokio::task::spawn_blocking(move || { - let (_blk_cell, virt_root) = bp.virtualize_block()?; - let usage = UsageTree::with_params(virt_root.clone(), true); + let root = read_single_root_boc(&raw)?; + let usage = UsageTree::with_params(root.clone(), true); let blk = Block::construct_from_cell(usage.root_cell())?; let info = blk.read_info()?; @@ -1576,8 +1632,7 @@ impl LiteServerQuerySubscriber { let tx_cell = slice.reference(0)?; let tx_boc = write_boc(&tx_cell)?; - let proof_bytes = - MerkleProof::create_by_usage_tree(&virt_root, &usage)?.write_to_bytes()?; + let proof_bytes = MerkleProof::create_by_usage_tree(&root, &usage)?.write_to_bytes()?; Ok(TransactionInfo { id: block_id, proof: proof_bytes, transaction: tx_boc }) }) @@ -2972,12 +3027,12 @@ fn deserialize_vm_stack_boc(boc: &[u8]) -> Result> { Ok(items) } -fn serialize_vm_stack_boc(items: &[StackItem]) -> Result> { +fn serialize_vm_stack(items: &[StackItem]) -> Result { let n = items.len(); let mut cb = BuilderData::new(); cb.append_bits(n, 24)?; if n == 0 { - return write_boc(&cb.into_cell()?); + return cb.into_cell(); } // vm_stk_nil = empty cell let mut rest = BuilderData::new().into_cell()?; @@ -2989,6 +3044,12 @@ fn serialize_vm_stack_boc(items: &[StackItem]) -> Result> { } cb.checked_append_reference(rest)?; write_vm_stack_value(&items[n - 1], &mut cb)?; + cb.into_cell() +} + +fn serialize_vm_stack_value_boc(item: &StackItem) -> Result> { + let mut cb = BuilderData::new(); + write_vm_stack_value(item, &mut cb)?; write_boc(&cb.into_cell()?) } diff --git a/src/node/src/rpc_server/handlers.rs b/src/node/src/rpc_server/handlers.rs index ceadc22..bb1697d 100644 --- a/src/node/src/rpc_server/handlers.rs +++ b/src/node/src/rpc_server/handlers.rs @@ -454,8 +454,13 @@ async fn run_get_method(p: RunGetMethodParams, ctx: Ctx) -> JsonResult { UIntOrStr::Int(i) => i, }; let mc_state_cell = acc_ctx.mc_state_root_cell(); + let gen_utime = acc_ctx.acc_state.state().state()?.gen_time(); + let gen_lt = acc_ctx.acc_state.state().state()?.gen_lt(); + let account = acc_ctx.shard_account.read_account()?; let stack = p.stack.into_iter().map(|e| e.into()).collect(); - let result = ton_vm::run_smc_method(&acc_ctx.shard_account, mc_state_cell, method_id, stack)?; + let result = + ton_vm::run_smc_method(&account, mc_state_cell, method_id, stack, gen_utime, gen_lt)? + .into_run_result()?; let stack = serialize_stack(result.stack)?; Ok(serde_json::json!({ @@ -1431,6 +1436,8 @@ struct GetTokenDataParams { async fn get_token_data(p: GetTokenDataParams, ctx: Ctx) -> JsonResult { let acc_ctx = AccountContext::with_address(&ctx, &p.address, None).await?; let mc_state_cell = acc_ctx.mc_state_root_cell(); + let gen_utime = acc_ctx.acc_state.state().state()?.gen_time(); + let gen_lt = acc_ctx.acc_state.state().state()?.gen_lt(); let is_testnet = ctx.is_testnet().await; const TYPES_METHODS: [(&str, &str); 4] = [ @@ -1440,6 +1447,7 @@ async fn get_token_data(p: GetTokenDataParams, ctx: Ctx) -> JsonResult { ("nft_item", "get_nft_data"), ]; + let account = acc_ctx.shard_account.read_account()?; let mut contract_type: Option<&str> = None; let mut stack: Option> = None; @@ -1447,13 +1455,15 @@ async fn get_token_data(p: GetTokenDataParams, ctx: Ctx) -> JsonResult { let method_id = ton_method_id(&method_name); let res = ton_vm::run_smc_method( - &acc_ctx.shard_account, + &account, mc_state_cell.clone(), method_id, Vec::::new(), + gen_utime, + gen_lt, ); - let Ok(res) = res else { + let Ok(res) = res.and_then(|r| r.into_run_result()) else { continue; }; diff --git a/src/node/src/tests/test_liteserver.rs b/src/node/src/tests/test_liteserver.rs index 389f51c..93af87c 100644 --- a/src/node/src/tests/test_liteserver.rs +++ b/src/node/src/tests/test_liteserver.rs @@ -33,12 +33,13 @@ use ton_api::{ ton::lite_server::{accountid::AccountId as AccountIdTl, BlockHeader, LookupBlockResult}, }; use ton_block::{ - fail, read_single_root_boc, write_boc, AccountDispatchQueue, BlkPrevInfo, Block, BlockExtra, - BlockIdExt, BlockInfo, ChildCell, ConfigParam0, ConfigParamEnum, ConfigParams, - CurrencyCollection, DispatchQueue, EnqueuedMsg, ExtBlkRef, GetRepresentationHash, - IntermediateAddress, InternalMessageHeader, KeyExtBlkRef, KeyId, McBlockExtra, MerkleUpdate, - Message, MsgAddressInt, MsgEnvelope, OldMcBlocksInfo, OutMsgQueue, OutMsgQueueExtra, - OutMsgQueueInfo, OutMsgQueueKey, ShardIdent, UInt256, ValueFlow, + fail, read_single_root_boc, write_boc, AccountBlock, AccountDispatchQueue, AccountStatus, + BlkPrevInfo, Block, BlockExtra, BlockIdExt, BlockInfo, ChildCell, ConfigParam0, + ConfigParamEnum, ConfigParams, CurrencyCollection, DispatchQueue, EnqueuedMsg, ExtBlkRef, + GetRepresentationHash, HashUpdate, IntermediateAddress, InternalMessageHeader, KeyExtBlkRef, + KeyId, McBlockExtra, MerkleProof, MerkleUpdate, Message, MsgAddressInt, MsgEnvelope, + OldMcBlocksInfo, OutMsgQueue, OutMsgQueueExtra, OutMsgQueueInfo, OutMsgQueueKey, + ShardAccountBlocks, ShardIdent, Transaction, Transactions, UInt256, ValueFlow, }; //static DB_COUNTER: AtomicUsize = AtomicUsize::new(0); @@ -2908,7 +2909,7 @@ fn test_vm_stack_boc_roundtrip_mixed_types() { vec![StackItem::cell(empty_cell), StackItem::None], ]; for items in &cases { - let boc = serialize_vm_stack_boc(items).unwrap(); + let boc = write_boc(&serialize_vm_stack(items).unwrap()).unwrap(); let decoded = deserialize_vm_stack_boc(&boc).unwrap(); assert_eq!(decoded.len(), items.len(), "length mismatch for {:?}", items); } @@ -2917,7 +2918,7 @@ fn test_vm_stack_boc_roundtrip_mixed_types() { #[test] fn test_vm_stack_boc_roundtrip_extreme_ints() { let items = vec![StackItem::int(i64::MAX), StackItem::int(i64::MIN)]; - let boc = serialize_vm_stack_boc(&items).unwrap(); + let boc = write_boc(&serialize_vm_stack(&items).unwrap()).unwrap(); let decoded = deserialize_vm_stack_boc(&boc).unwrap(); assert_eq!(decoded.len(), 2); match &decoded[0] { @@ -2933,3 +2934,45 @@ fn test_vm_stack_boc_roundtrip_extreme_ints() { other => panic!("expected int(MIN), got {:?}", other), } } + +#[test] +fn test_vm_stack_boc_roundtrip_bigints() { + use ton_vm::stack::integer::IntegerData; + + let cases: Vec = vec![ + // positive 256-bit: 2^200 + StackItem::integer( + IntegerData::from_str_radix( + "1606938044258990275541962092341162602522202993782792835301376", + 10, + ) + .unwrap(), + ), + // max unsigned 256-bit: 2^256 - 1 + StackItem::integer(IntegerData::from_unsigned_bytes_be([0xFF; 32])), + // negative big: -(2^200) + StackItem::integer( + IntegerData::from_str_radix( + "-1606938044258990275541962092341162602522202993782792835301376", + 10, + ) + .unwrap(), + ), + // just outside i64 range: i64::MAX + 1 + StackItem::integer(IntegerData::from_str_radix("9223372036854775808", 10).unwrap()), + // just outside i64 range: i64::MIN - 1 + StackItem::integer(IntegerData::from_str_radix("-9223372036854775809", 10).unwrap()), + ]; + + let boc = write_boc(&serialize_vm_stack(&cases).unwrap()).unwrap(); + let decoded = deserialize_vm_stack_boc(&boc).unwrap(); + assert_eq!(decoded.len(), cases.len()); + for (i, (orig, dec)) in cases.iter().zip(decoded.iter()).enumerate() { + let orig_int = orig.as_integer().expect("original is integer"); + let dec_int = dec.as_integer().expect("decoded is integer"); + assert_eq!( + orig_int, dec_int, + "bigint mismatch at index {i}: orig={orig_int:?}, decoded={dec_int:?}" + ); + } +} diff --git a/src/node/src/validator/collator.rs b/src/node/src/validator/collator.rs index 933c6f5..a85e1c9 100644 --- a/src/node/src/validator/collator.rs +++ b/src/node/src/validator/collator.rs @@ -966,6 +966,18 @@ impl ExecutionManager { }) .await?; + if cancel_ext.load(Ordering::Relaxed) { + log::debug!( + "{}: account {:x} ext message cancelled by cutoff timeout", + collated_block_descr, + shard_acc.account_id() + ); + let transaction_res = + Err(error!("cancelled by cutoff timeout after execution")); + wait_tr.respond(Some((new_msg, msg_metadata, transaction_res))); + continue; + } + if let Ok(transaction) = transaction_res.as_mut() { let res = shard_acc.add_transaction(transaction, account); if let Err(err) = res { @@ -3788,6 +3800,7 @@ impl Collator { self.collated_block_descr ); exec_manager.cancel_ext.store(true, Ordering::Relaxed); + break; } exec_manager.wait_transaction(collator_data).await?; } diff --git a/src/tl/ton_api/tl/lite_api.tl b/src/tl/ton_api/tl/lite_api.tl index b00951d..26bda48 100644 --- a/src/tl/ton_api/tl/lite_api.tl +++ b/src/tl/ton_api/tl/lite_api.tl @@ -47,7 +47,9 @@ liteServer.transactionId3 account:int256 lt:long = liteServer.TransactionId3; liteServer.blockTransactions id:tonNode.blockIdExt req_count:# incomplete:Bool ids:(vector liteServer.transactionId) proof:bytes = liteServer.BlockTransactions; liteServer.blockTransactionsExt id:tonNode.blockIdExt req_count:# incomplete:Bool transactions:bytes proof:bytes = liteServer.BlockTransactionsExt; liteServer.signature node_id_short:int256 signature:bytes = liteServer.Signature; -liteServer.signatureSet validator_set_hash:int catchain_seqno:int signatures:(vector liteServer.signature) = liteServer.SignatureSet; +liteServer.signatureSet.ordinary#f644a6e6 validator_set_hash:int catchain_seqno:int signatures:(vector liteServer.signature) = liteServer.SignatureSet; +liteServer.signatureSet.simplex cc_seqno:int validator_set_hash:int signatures:(vector liteServer.signature) + session_id:int256 slot:int candidate:bytes = liteServer.SignatureSet; liteServer.blockLinkBack to_key_block:Bool from:tonNode.blockIdExt to:tonNode.blockIdExt dest_proof:bytes proof:bytes state_proof:bytes = liteServer.BlockLink; liteServer.blockLinkForward to_key_block:Bool from:tonNode.blockIdExt to:tonNode.blockIdExt dest_proof:bytes config_proof:bytes signatures:liteServer.SignatureSet = liteServer.BlockLink; liteServer.partialBlockProof complete:Bool from:tonNode.blockIdExt to:tonNode.blockIdExt steps:(vector liteServer.BlockLink) = liteServer.PartialBlockProof; diff --git a/src/vm/src/lib.rs b/src/vm/src/lib.rs index 80ba4ac..2da94d3 100644 --- a/src/vm/src/lib.rs +++ b/src/vm/src/lib.rs @@ -18,6 +18,6 @@ pub mod error; pub mod smart_contract_info; pub mod utils; -pub use self::smart_contract_info::{run_smc_method, SmartContractInfo}; +pub use self::smart_contract_info::{run_smc_method, SmartContractInfo, SmcMethodResult}; include!("../../common/src/info.rs"); diff --git a/src/vm/src/smart_contract_info.rs b/src/vm/src/smart_contract_info.rs index d9f2c3d..83994ad 100644 --- a/src/vm/src/smart_contract_info.rs +++ b/src/vm/src/smart_contract_info.rs @@ -14,21 +14,18 @@ use crate::{ }; use std::{borrow::Cow, mem}; use ton_api::{ - ton::{ - smc::runresult::RunResult, - tvm::{ - stackentry::{ - StackEntryCell, StackEntryList, StackEntryNumber, StackEntrySlice, StackEntryTuple, - }, - StackEntry, + ton::tvm::{ + stackentry::{ + StackEntryCell, StackEntryList, StackEntryNumber, StackEntrySlice, StackEntryTuple, }, + StackEntry, }, IntoBoxed, }; use ton_block::{ - error, fail, read_single_root_boc, write_boc, Cell, ConfigParams, CurrencyCollection, + error, fail, read_single_root_boc, write_boc, Account, Cell, ConfigParams, CurrencyCollection, Deserializable, ExtBlkRef, HashmapAugType, KeyExtBlkRef, Message, OldMcBlocksInfo, Result, - Serializable, Sha256, ShardAccount, ShardStateUnsplit, SliceData, UInt256, UnixTime, + Serializable, Sha256, ShardStateUnsplit, SliceData, UInt256, UnixTime, }; /* @@ -74,9 +71,26 @@ pub struct SmartContractInfo { pub precompiled_gas_usage: u64, } +pub struct SmcMethodResult { + pub exit_code: i32, + pub gas_used: i64, + pub stack: Vec, + pub smc_info: SmartContractInfo, +} + +impl SmcMethodResult { + pub fn into_run_result(self) -> Result { + Ok(ton_api::ton::smc::runresult::RunResult { + gas_used: self.gas_used, + stack: convert_stack(&self.stack)?, + exit_code: self.exit_code, + }) + } +} + impl SmartContractInfo { pub fn with_params( - shard_account: Option<&ShardAccount>, + account: Option<&Account>, message_root: Option, mc_state_root: Option, // state root could be virtualized ) -> Result { @@ -101,14 +115,14 @@ impl SmartContractInfo { smci.unix_time = mc_state.gen_time() + 1; smci.config_params = extra.config; } - if let Some(shard_account) = shard_account { - let account = shard_account.read_account()?; + if let Some(account) = account { if let Some(addr) = account.get_addr() { smci.myself = addr.write_to_bitstring()?; } if let Some(balance) = account.balance() { smci.balance = balance.clone(); } + smci.mycode = account.get_code().unwrap_or_default(); smci.due_payment = account.due_payment().map_or(0, |g| g.as_u128()); } if let Some(message_root) = message_root { @@ -488,16 +502,20 @@ pub fn convert_ton_stack(items: &[StackEntry]) -> Result> { } pub fn run_smc_method( - shard_account: &ShardAccount, + account: &Account, mc_state_cell: Cell, method_id: u32, stack: Vec, -) -> Result { - let account = shard_account.read_account()?; + gen_utime: u32, + gen_lt: u64, +) -> Result { let code = account.get_code().ok_or_else(|| error!("Account has no code"))?; let data = account.get_data().unwrap_or_default(); - let smc_info = - SmartContractInfo::with_params(Some(shard_account), None, Some(mc_state_cell.clone()))?; + let mut smc_info = + SmartContractInfo::with_params(Some(account), None, Some(mc_state_cell.clone()))?; + smc_info.unix_time = gen_utime; + smc_info.block_lt = gen_lt; + smc_info.trans_lt = gen_lt; let mut storage = convert_ton_stack(&stack)?; storage.push(StackItem::int(method_id)); @@ -534,8 +552,7 @@ pub fn run_smc_method( log::debug!("run_smc_method: result_stack_depth={}\n", stack.len()); log::debug!("run_smc_method: result_stack dump: [ {} ]\n", render_stack(&stack)); - let stack = convert_stack(&stack)?; - Ok(RunResult { gas_used: vm.gas_used(), stack, exit_code }) + Ok(SmcMethodResult { exit_code, gas_used: vm.gas_used(), stack, smc_info }) } #[cfg(test)] diff --git a/src/vm/src/tests/test_smart_contract_info.rs b/src/vm/src/tests/test_smart_contract_info.rs index 80cf5e8..06f19b5 100644 --- a/src/vm/src/tests/test_smart_contract_info.rs +++ b/src/vm/src/tests/test_smart_contract_info.rs @@ -11,7 +11,7 @@ use super::*; use ton_block::{ base64_decode, read_single_root_boc, ton_method_id, Coins, CurrencyCollection, - InternalMessageHeader, Message, MsgAddressInt, + InternalMessageHeader, Message, MsgAddressInt, ShardAccount, }; #[test] @@ -73,8 +73,17 @@ fn test_run_get_method_seqno_with_config() { let mc_state = ShardStateUnsplit::construct_from_cell(mc_state_cell.clone()).unwrap(); let shard_account = mc_state.read_accounts().unwrap().get(&[0x55; 32].into()).unwrap().unwrap(); - let result = - run_smc_method(&shard_account, mc_state_cell.clone(), method_id, Vec::new()).unwrap(); + let result = run_smc_method( + &shard_account.read_account().unwrap(), + mc_state_cell.clone(), + method_id, + Vec::new(), + mc_state.gen_time(), + mc_state.gen_lt(), + ) + .unwrap() + .into_run_result() + .unwrap(); assert_eq!(result.exit_code, 0); assert_eq!(result.gas_used, 869); assert_eq!(result.stack.len(), 1); @@ -94,8 +103,17 @@ fn test_run_get_method_seqno_with_elector() { // let account = base64_decode(account).unwrap(); // let account = ton_block::Account::construct_from_bytes(&account).unwrap(); // let shard_account = ShardAccount::with_params(&account, Default::default(), 0).unwrap(); - let result = - run_smc_method(&shard_account, mc_state_cell.clone(), method_id, Vec::new()).unwrap(); + let result = run_smc_method( + &shard_account.read_account().unwrap(), + mc_state_cell.clone(), + method_id, + Vec::new(), + mc_state.gen_time(), + mc_state.gen_lt(), + ) + .unwrap() + .into_run_result() + .unwrap(); assert_eq!(result.exit_code, 11); assert_eq!(result.gas_used, 770); assert_eq!(result.stack.len(), 1); @@ -207,8 +225,19 @@ fn run_elector_method( ) -> ton_api::ton::smc::runresult::RunResult { let shard_account = load_elector_shard_account(); let mc_state_cell = load_mc_state_cell(); + let mc_state = ShardStateUnsplit::construct_from_cell(mc_state_cell.clone()).unwrap(); let method_id = ton_method_id(method); - run_smc_method(&shard_account, mc_state_cell, method_id, stack).unwrap() + run_smc_method( + &shard_account.read_account().unwrap(), + mc_state_cell, + method_id, + stack, + mc_state.gen_time(), + mc_state.gen_lt(), + ) + .unwrap() + .into_run_result() + .unwrap() } fn stack_number(value: i64) -> ton_api::ton::tvm::StackEntry { @@ -239,8 +268,19 @@ fn run_external_elector_method( ) -> ton_api::ton::smc::runresult::RunResult { let shard_account = load_external_elector_shard_account(); let mc_state_cell = load_mc_state_cell(); + let mc_state = ShardStateUnsplit::construct_from_cell(mc_state_cell.clone()).unwrap(); let method_id = ton_method_id(method); - run_smc_method(&shard_account, mc_state_cell, method_id, stack).unwrap() + run_smc_method( + &shard_account.read_account().unwrap(), + mc_state_cell, + method_id, + stack, + mc_state.gen_time(), + mc_state.gen_lt(), + ) + .unwrap() + .into_run_result() + .unwrap() } fn first_external_participant_pubkey_and_wallet() -> (String, String) { diff --git a/src/vm/tests/common/test_framework.rs b/src/vm/tests/common/test_framework.rs index 5a66f5f..ebcbddb 100644 --- a/src/vm/tests/common/test_framework.rs +++ b/src/vm/tests/common/test_framework.rs @@ -519,8 +519,10 @@ impl TestCase { .push(StackItem::Slice(message.body().cloned().unwrap_or_default())) .push(StackItem::boolean(!message.is_internal())); } + let account = + args.shard_account.as_ref().map(|sa| sa.read_account()).transpose().unwrap(); let mut smci = SmartContractInfo::with_params( - args.shard_account.as_ref(), + account.as_ref(), args.message.clone(), mc_state_root, )