Skip to content

Commit 9f240e8

Browse files
authored
Merge pull request #78 from RSquad/executor
Transaction execution fixes
2 parents 51e89b5 + 6111731 commit 9f240e8

11 files changed

Lines changed: 144 additions & 36 deletions
188 Bytes
Binary file not shown.
187 Bytes
Binary file not shown.
730 KB
Binary file not shown.
738 Bytes
Binary file not shown.

src/executor/src/ordinary_transaction.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ impl TransactionExecutor for OrdinaryTransactionExecutor {
116116
log::debug!(target: "executor", "Account is frozen, hash = {:x}", hash);
117117
}
118118
let mut acc_balance = account.balance().cloned().unwrap_or_default();
119+
let mut original_acc_balance = acc_balance.clone();
119120
let is_special = self.config.is_special_account(is_masterchain, &account_id)?;
120121
let account_address = MsgAddressInt::with_params(wc_id, account_id.clone())?;
121122

@@ -228,8 +229,10 @@ impl TransactionExecutor for OrdinaryTransactionExecutor {
228229

229230
log::debug!(target: "executor",
230231
"storage_phase: {}", if description.storage_ph.is_some() {"present"} else {"none"});
231-
let mut original_acc_balance = account.balance().cloned().unwrap_or_default();
232-
original_acc_balance.sub(tr.total_fees())?;
232+
if !original_acc_balance.sub(tr.total_fees())? {
233+
original_acc_balance.coins = Default::default();
234+
debug_assert!(tr.total_fees().other.is_empty());
235+
}
233236

234237
if !description.credit_first && !is_ext_msg {
235238
description.credit_ph = match self.credit_phase(&msg_balance, &mut acc_balance) {

src/executor/src/tests/common/cross_check.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ pub(crate) fn cross_check(
8787
// assert!(extra.config.global_version() >= ton_block::SUPPORTED_VERSION, "global_version {} must be >= {}",
8888
// config.global_version(), ton_block::SUPPORTED_VERSION);
8989
#[cfg(windows)]
90-
let lib_name = "../../ton/build/crypto/Release/vm_run_shared.dll";
90+
let lib_name = "../../../ton/build/crypto/vm_run_shared.dll";
9191
#[cfg(target_os = "linux")]
9292
let lib_name = "../../ton-node-cpp/build/crypto/libvm_run_shared.so";
9393
#[cfg(target_os = "macos")]

src/executor/src/tests/common/mod.rs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ pub static RECEIVER_ACCOUNT: AccountId = AccountId::with_uint256([0x22; 128]);
5353
pub static THIRD_ACCOUNT: AccountId = AccountId::with_uint256([0x33; 128]);
5454
pub static BLOCKCHAIN_CONFIG: LazyLock<BlockchainConfig> = LazyLock::new(default_config);
5555
pub static SIMPLE_MC_STATE: LazyLock<Cell> =
56-
LazyLock::new(|| mc_state_proof_cell_with_config(BLOCKCHAIN_CONFIG.raw_config().clone()));
56+
LazyLock::new(|| mc_state_proof_cell_with_config(BLOCKCHAIN_CONFIG.raw_config().clone(), None));
5757

5858
pub fn mc_state_cell_with_config(config: ConfigParams) -> ShardStateUnsplit {
5959
let mc_seqno = 1234567;
@@ -78,8 +78,11 @@ pub fn make_proof_cell(p: &impl Serializable) -> Cell {
7878
proof.serialize().unwrap()
7979
}
8080

81-
pub fn mc_state_proof_cell_with_config(config: ConfigParams) -> Cell {
82-
let mc_state = mc_state_cell_with_config(config);
81+
pub fn mc_state_proof_cell_with_config(config: ConfigParams, libs: Option<Cell>) -> Cell {
82+
let mut mc_state = mc_state_cell_with_config(config);
83+
if let Some(libs) = libs {
84+
*mc_state.libraries_mut() = ton_block::Libraries::with_hashmap(Some(libs));
85+
}
8386
make_proof_cell(&mc_state)
8487
}
8588

@@ -142,7 +145,7 @@ pub fn execute_params(last_tr_lt: u64) -> ExecuteParams {
142145
Emulator,
143146
}
144147
let debug = DebugType::None;
145-
let _ = cross_check::DisableCrossCheck::new();
148+
// let _ = cross_check::DisableCrossCheck::new();
146149
let (verbosity, pattern, trace_callback) = match debug {
147150
DebugType::None => (4, None, None),
148151
DebugType::Simple => (2048 + 4, Some("{m}"), None),
@@ -978,13 +981,27 @@ pub fn read_config(cfg: &str) -> Result<ConfigParams> {
978981
}
979982

980983
pub fn replay_transaction_by_files(acc: &str, acc_after: &str, tr: &str, cfg: &str) {
981-
replay_transaction_with_prevs(acc, acc_after, tr, cfg, "")
984+
replay_transaction_full(acc, acc_after, tr, cfg, "", "")
982985
}
983986

984-
pub fn replay_transaction_with_prevs(acc: &str, acc_after: &str, tr: &str, cfg: &str, prev: &str) {
987+
pub fn replay_transaction_full(
988+
acc: &str,
989+
acc_after: &str,
990+
tr: &str,
991+
cfg: &str,
992+
prev: &str,
993+
libs: &str,
994+
) {
985995
let config = read_config(cfg).unwrap();
986996
assert!(config.valid_config_data(false, None).unwrap());
987-
let mc_state_proof = mc_state_proof_cell_with_config(config);
997+
let libs = if libs.is_empty() {
998+
None
999+
} else if let Ok(data) = std::fs::read(libs) {
1000+
Some(read_single_root_boc(data).unwrap())
1001+
} else {
1002+
None
1003+
};
1004+
let mc_state_proof = mc_state_proof_cell_with_config(config, libs);
9881005
replay_transaction(None, acc, acc_after, tr, prev, mc_state_proof)
9891006
}
9901007

@@ -1015,7 +1032,7 @@ pub fn try_replay_transaction(
10151032
config: BlockchainConfig,
10161033
params: &ExecuteParams,
10171034
) -> Result<Transaction> {
1018-
let mc_state_proof = mc_state_proof_cell_with_config(config.raw_config().clone());
1035+
let mc_state_proof = mc_state_proof_cell_with_config(config.raw_config().clone(), None);
10191036
let msg_cell = message.map(|msg| msg.serialize().unwrap());
10201037
execute_with_params(mc_state_proof, msg_cell, account, params)
10211038
}

src/executor/src/tests/test_transaction_executor_with_real_data.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,18 @@ fn test_fwd_fee_payment_in_smc() {
559559
)
560560
}
561561

562+
#[test]
563+
fn test_raw_reserve_with_flag4() {
564+
replay_transaction_full(
565+
"real_boc/raw_reserve_with_flag4_account_old.boc",
566+
"real_boc/raw_reserve_with_flag4_account_new.boc",
567+
"real_boc/raw_reserve_with_flag4_transaction.boc",
568+
"real_boc/config12.boc",
569+
"",
570+
"real_boc/raw_reserve_with_flag4_libs.boc",
571+
)
572+
}
573+
562574
#[ignore = "test for replay transaction by files"]
563575
#[test]
564576
fn test_replay_transaction_by_files() {
@@ -632,6 +644,8 @@ fn test_bad_single() {
632644
fn test_bad_trans() {
633645
let json = "../../emulator/emulator_test.json";
634646
let prefix = "real_boc/bad_".to_string();
647+
let libs = std::path::PathBuf::from(json).parent().unwrap().join("libs.boc");
648+
let libs = libs.to_string_lossy();
635649
let json = std::fs::read_to_string(json).unwrap();
636650
let json: serde_json::Map<String, serde_json::Value> = serde_json::from_str(&json).unwrap();
637651
let acc = json["shard_account_boc"].as_str().unwrap();
@@ -642,11 +656,12 @@ fn test_bad_trans() {
642656
shard_acc.account_cell().write_to_file(prefix.clone() + "account_new.boc");
643657
std::fs::write(prefix.clone() + "transaction.boc", base64_decode(tr).unwrap()).unwrap();
644658

645-
replay_transaction_with_prevs(
659+
replay_transaction_full(
646660
acc,
647661
&(prefix + "account_new.boc"),
648662
tr,
649663
"real_boc/config12.boc",
650664
prev,
665+
&libs,
651666
);
652667
}

src/node/src/ext_messages.rs

Lines changed: 74 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,17 @@ use ton_block::{
2222
#[path = "tests/test_ext_messages.rs"]
2323
mod tests;
2424

25-
const MESSAGE_LIFETIME: u32 = 600; // seconds
26-
const MESSAGE_MAX_GENERATIONS: u8 = 3;
27-
25+
const LIMIT_MEMPOOL_PER_ADDRESS: u32 = 256;
2826
const MAX_EXTERNAL_MESSAGE_DEPTH: u16 = 512;
2927
pub const MAX_EXTERNAL_MESSAGE_SIZE: usize = 65535;
28+
const MESSAGE_LIFETIME: u32 = 600; // seconds
29+
const MESSAGE_MAX_GENERATIONS: u8 = 3;
3030

3131
pub const EXT_MESSAGES_TRACE_TARGET: &str = "ext_messages";
3232

3333
#[derive(Clone)]
3434
struct MessageKeeper {
35+
addr_key: (i32, UInt256),
3536
message: Arc<Message>,
3637

3738
// active: bool, 0x1_00_00000000
@@ -41,11 +42,11 @@ struct MessageKeeper {
4142
}
4243

4344
impl MessageKeeper {
44-
fn new(message: Arc<Message>) -> Self {
45+
fn new(message: Arc<Message>, addr_key: (i32, UInt256)) -> Self {
4546
let mut atomic_storage = 0;
4647
Self::set_active(&mut atomic_storage, true);
4748

48-
Self { message, atomic_storage: Arc::new(AtomicU64::new(atomic_storage)) }
49+
Self { addr_key, message, atomic_storage: Arc::new(AtomicU64::new(atomic_storage)) }
4950
}
5051

5152
fn message(&self) -> &Arc<Message> {
@@ -139,6 +140,8 @@ impl OrderMap {
139140
}
140141

141142
pub struct MessagesPool {
143+
// per-address message count for rate limiting (key = (workchain_id, account_id))
144+
per_address: Map<(i32, UInt256), AtomicU32>,
142145
// map by hash of message
143146
messages: Map<UInt256, MessageKeeper>,
144147
// map by timestamp, inside map by seqno for hash of message, workchain_id and prefix of dst address
@@ -160,6 +163,7 @@ impl MessagesPool {
160163
metrics::gauge!("ton_node_ext_messages_queue_size").set(0f64);
161164

162165
Self {
166+
per_address: Map::new(),
163167
messages: Map::with_hasher(Default::default()),
164168
order: Map::with_hasher(Default::default()),
165169
min_timestamp: AtomicU32::new(now),
@@ -202,11 +206,35 @@ impl MessagesPool {
202206
}
203207
}
204208

205-
log::debug!(target: EXT_MESSAGES_TRACE_TARGET, "adding external message {:x}", id);
206209
let workchain_id = message.dst_workchain_id().unwrap_or_default();
210+
let account_id = message
211+
.int_dst_account_id()
212+
.map_or(UInt256::default(), |s| UInt256::from_slice(&s.get_bytestring(0)));
213+
let addr_key = (workchain_id, account_id);
214+
215+
// Per-address rate limiting
216+
if let Some(guard) = self.per_address.get(&addr_key) {
217+
if guard.val().load(Ordering::Relaxed) >= LIMIT_MEMPOOL_PER_ADDRESS {
218+
fail!(
219+
"per-address limit ({}) reached for {}:{}",
220+
LIMIT_MEMPOOL_PER_ADDRESS,
221+
workchain_id,
222+
addr_key.1.to_hex_string()
223+
)
224+
}
225+
}
226+
227+
log::debug!(target: EXT_MESSAGES_TRACE_TARGET, "adding external message {:x}", id);
207228
let prefix =
208229
message.int_dst_account_id().map_or(0, |slice| slice.get_int(64).unwrap_or_default());
209-
self.messages.insert(id.clone(), MessageKeeper::new(message));
230+
self.messages.insert(id.clone(), MessageKeeper::new(message, addr_key.clone()));
231+
232+
// Increment per-address counter
233+
if let Some(guard) = self.per_address.get(&addr_key) {
234+
guard.val().fetch_add(1, Ordering::Relaxed);
235+
} else {
236+
self.per_address.insert(addr_key, AtomicU32::new(1));
237+
}
210238
self.total_messages.fetch_add(1, Ordering::Relaxed);
211239
#[cfg(test)]
212240
self.total_in_order.fetch_add(1, Ordering::Relaxed);
@@ -253,12 +281,13 @@ impl MessagesPool {
253281
true
254282
}
255283
});
256-
if result.is_some() {
284+
if let Some(guard) = result {
257285
log::debug!(
258286
target: EXT_MESSAGES_TRACE_TARGET,
259287
"complete_messages: removing external message {:x} with reason {} because can't postpone",
260288
id, reason,
261289
);
290+
self.decrement_per_address(&guard.val().addr_key);
262291
metrics::gauge!("ton_node_ext_messages_queue_size").decrement(1f64);
263292
self.total_messages.fetch_sub(1, Ordering::Relaxed);
264293
}
@@ -279,6 +308,15 @@ impl MessagesPool {
279308
);
280309
}
281310

311+
fn decrement_per_address(&self, addr_key: &(i32, UInt256)) {
312+
if let Some(guard) = self.per_address.get(addr_key) {
313+
let prev = guard.val().fetch_sub(1, Ordering::Relaxed);
314+
if prev <= 1 {
315+
self.per_address.remove(addr_key);
316+
}
317+
}
318+
}
319+
282320
fn clear_expired_messages(&self, timestamp: u32, finish_time_ms: u64) -> bool {
283321
let order = match self.order.get(&timestamp) {
284322
Some(guard) => guard.val().clone(),
@@ -301,6 +339,7 @@ impl MessagesPool {
301339
target: EXT_MESSAGES_TRACE_TARGET,
302340
"removing external message {:x} because it is expired", guard.key()
303341
);
342+
self.decrement_per_address(&guard.val().addr_key);
304343
self.total_messages.fetch_sub(1, Ordering::Relaxed);
305344
}
306345
#[cfg(test)]
@@ -342,8 +381,9 @@ pub struct MessagePoolIter {
342381

343382
impl MessagePoolIter {
344383
fn new(pool: Arc<MessagesPool>, shard: ShardIdent, now: u32, finish_time_ms: u64) -> Self {
345-
let timestamp = pool.min_timestamp.load(Ordering::Relaxed);
346-
Self { pool, shard, now, timestamp, seqno: 0, finish_time_ms }
384+
// Start from newest messages (now) and iterate backwards to oldest
385+
// (matching C++ behavior where newest messages get priority)
386+
Self { pool, shard, now, timestamp: now, seqno: 0, finish_time_ms }
347387
}
348388

349389
fn find_in_map(
@@ -371,20 +411,33 @@ impl Iterator for MessagePoolIter {
371411
type Item = (Arc<Message>, UInt256);
372412

373413
fn next(&mut self) -> Option<Self::Item> {
374-
// iterate timestamp
375414
let now = UnixTime::now_ms();
376-
while self.timestamp <= self.now {
415+
let min_timestamp = self.pool.min_timestamp.load(Ordering::Relaxed);
416+
// Iterate from newest to oldest (reverse chronological order)
417+
loop {
377418
if self.finish_time_ms < now {
378419
return None;
379420
}
380-
// check if this order map is expired
421+
if self.timestamp < min_timestamp {
422+
return None;
423+
}
424+
// Check if this timestamp is expired
381425
if self.timestamp + MESSAGE_LIFETIME < self.now {
382-
if !self.pool.clear_expired_messages(self.timestamp, self.finish_time_ms) {
426+
if self.timestamp == min_timestamp {
427+
if !self.pool.clear_expired_messages(self.timestamp, self.finish_time_ms) {
428+
return None;
429+
}
430+
self.pool.increment_min_timestamp(self.timestamp);
431+
}
432+
// Skip expired timestamps
433+
if self.timestamp == 0 {
383434
return None;
384435
}
385-
// level was removed or not present try to move bottom margin
386-
self.pool.increment_min_timestamp(self.timestamp);
387-
} else if let Some(order) =
436+
self.timestamp -= 1;
437+
self.seqno = 0;
438+
continue;
439+
}
440+
if let Some(order) =
388441
self.pool.order.get(&self.timestamp).map(|guard| guard.val().clone())
389442
{
390443
while self.seqno < order.seqno.load(Ordering::Relaxed) {
@@ -398,10 +451,12 @@ impl Iterator for MessagePoolIter {
398451
}
399452
}
400453
}
401-
self.timestamp += 1;
454+
if self.timestamp == 0 {
455+
return None;
456+
}
457+
self.timestamp -= 1;
402458
self.seqno = 0;
403459
}
404-
None
405460
}
406461
}
407462

src/node/src/tests/test_ext_messages.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ fn test_create_ext_message() {
103103
#[test]
104104
fn test_message_keeper() {
105105
let m = Message::with_ext_in_header(ExternalInboundMessageHeader::default());
106-
let mk = MessageKeeper::new(Arc::new(m));
106+
let mk = MessageKeeper::new(Arc::new(m), Default::default());
107107

108108
assert!(mk.check_active(10000));
109109

@@ -133,7 +133,7 @@ fn test_message_keeper() {
133133
#[test]
134134
fn test_message_keeper_multithread() {
135135
let m = Message::with_ext_in_header(ExternalInboundMessageHeader::default());
136-
let mk = Arc::new(MessageKeeper::new(Arc::new(m)));
136+
let mk = Arc::new(MessageKeeper::new(Arc::new(m), Default::default()));
137137

138138
let mut hs = vec![];
139139
for _ in 0..50 {
@@ -173,11 +173,15 @@ fn test_message_keeper_multithread() {
173173
}
174174

175175
fn create_external_message(dst_shard: u8, salt: Vec<u8>) -> Arc<Message> {
176+
create_external_message_to([dst_shard; 32], salt)
177+
}
178+
179+
fn create_external_message_to(dst_account: [u8; 32], salt: Vec<u8>) -> Arc<Message> {
176180
let mut hdr = ExternalInboundMessageHeader::default();
177181
let length_in_bits = salt.len() * 8;
178182
let address = SliceData::from_raw(salt, length_in_bits);
179183
hdr.src = MsgAddressExt::with_extern(address).unwrap();
180-
hdr.dst = MsgAddressInt::with_standart(None, 0, [dst_shard; 32].into()).unwrap();
184+
hdr.dst = MsgAddressInt::with_standart(None, 0, dst_account.into()).unwrap();
181185
hdr.import_fee = 10u64.into();
182186
Arc::new(Message::with_ext_in_header(hdr))
183187
}
@@ -352,7 +356,10 @@ fn test_external_messages_big_load() {
352356
let queue_seconds = min(MESSAGE_LIFETIME, 100);
353357
for i in 0..queue_seconds {
354358
for j in 0..rate_per_second {
355-
let m = create_external_message(0, (i * rate_per_second + j).to_be_bytes().to_vec());
359+
let idx = i * rate_per_second + j;
360+
let mut dst = [0u8; 32];
361+
dst[..4].copy_from_slice(&idx.to_be_bytes());
362+
let m = create_external_message_to(dst, idx.to_be_bytes().to_vec());
356363
let id = m.hash().unwrap();
357364
mp.new_message(&id, m, now + i).unwrap();
358365
}
@@ -369,6 +376,9 @@ fn test_external_messages_big_load() {
369376
(count, n)
370377
};
371378
println!("count = {}, time = {:?}", count, n);
372-
assert_eq!(0, count);
379+
// With newest-first iteration, non-expired messages are found quickly.
380+
// The pool contains messages from ~502 to ~601 seconds ago;
381+
// those within MESSAGE_LIFETIME (600s) are valid and returned.
382+
assert!(count <= 100);
373383
assert!((n as u64) < limit * 3);
374384
}

0 commit comments

Comments
 (0)