From c9b4fb348576acc404ea9a37d89f0ec5adfd447f Mon Sep 17 00:00:00 2001 From: Mykhailo Kremniov Date: Mon, 22 Sep 2025 17:57:35 +0300 Subject: [PATCH 1/2] Fix spurious test failures in the api server --- api-server/stack-test-suite/tests/v2/helpers.rs | 17 ++++++++++------- api-server/stack-test-suite/tests/v2/htlc.rs | 13 ++++--------- api-server/stack-test-suite/tests/v2/orders.rs | 10 ++++++---- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/api-server/stack-test-suite/tests/v2/helpers.rs b/api-server/stack-test-suite/tests/v2/helpers.rs index 5a9a58ef5b..4390c2128c 100644 --- a/api-server/stack-test-suite/tests/v2/helpers.rs +++ b/api-server/stack-test-suite/tests/v2/helpers.rs @@ -13,13 +13,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use chainstate_test_framework::empty_witness; -use chainstate_test_framework::{TestFramework, TransactionBuilder}; -use common::chain::make_delegation_id; +use chainstate_test_framework::{empty_witness, TestFramework, TransactionBuilder}; use common::{ address::pubkeyhash::PublicKeyHash, chain::{ - make_token_id, + make_delegation_id, make_token_id, output_value::OutputValue, stakelock::StakePoolData, tokens::{TokenId, TokenIssuance, TokenTotalSupply}, @@ -154,20 +152,25 @@ pub struct IssueAndMintTokensResult { } pub fn issue_and_mint_tokens_from_genesis( + min_mint_amount: Amount, rng: &mut (impl Rng + CryptoRng), tf: &mut TestFramework, ) -> IssueAndMintTokensResult { let token_issuance_fee = tf.chainstate.get_chain_config().fungible_token_issuance_fee(); - let issuance = test_utils::token_utils::random_token_issuance_v1( + let min_mint_amount_atoms = min_mint_amount.into_atoms(); + let issuance = test_utils::token_utils::random_token_issuance_v1_with_min_supply( tf.chain_config(), Destination::AnyoneCanSpend, + min_mint_amount_atoms, rng, ); let amount_to_mint = match issuance.total_supply { - TokenTotalSupply::Fixed(limit) => Amount::from_atoms(rng.gen_range(1..=limit.into_atoms())), + TokenTotalSupply::Fixed(limit) => { + Amount::from_atoms(rng.gen_range(min_mint_amount_atoms..=limit.into_atoms())) + } TokenTotalSupply::Lockable | TokenTotalSupply::Unlimited => { - Amount::from_atoms(rng.gen_range(100..1000)) + Amount::from_atoms(rng.gen_range(min_mint_amount_atoms..min_mint_amount_atoms * 10)) } }; diff --git a/api-server/stack-test-suite/tests/v2/htlc.rs b/api-server/stack-test-suite/tests/v2/htlc.rs index 781328c03d..6a98262c0b 100644 --- a/api-server/stack-test-suite/tests/v2/htlc.rs +++ b/api-server/stack-test-suite/tests/v2/htlc.rs @@ -88,8 +88,9 @@ async fn spend(#[case] seed: Seed) { .build(); // Issue and mint some tokens to lock in htlc + let tokens_amount = Amount::from_atoms(100); let issue_and_mint_result = - helpers::issue_and_mint_tokens_from_genesis(&mut rng, &mut tf); + helpers::issue_and_mint_tokens_from_genesis(tokens_amount, &mut rng, &mut tf); // Create htlc let (htlc, _) = create_htlc(&chain_config, &alice_pk, &bob_pk, secret.hash()); @@ -99,10 +100,7 @@ async fn spend(#[case] seed: Seed) { InputWitness::NoSignature(None), ) .add_output(TxOutput::Htlc( - OutputValue::TokenV1( - issue_and_mint_result.token_id, - Amount::from_atoms(100), - ), + OutputValue::TokenV1(issue_and_mint_result.token_id, tokens_amount), Box::new(htlc), )) .build(); @@ -118,10 +116,7 @@ async fn spend(#[case] seed: Seed) { InputWitness::NoSignature(None), ) .add_output(TxOutput::Transfer( - OutputValue::TokenV1( - issue_and_mint_result.token_id, - Amount::from_atoms(100), - ), + OutputValue::TokenV1(issue_and_mint_result.token_id, tokens_amount), Destination::AnyoneCanSpend, )) .build() diff --git a/api-server/stack-test-suite/tests/v2/orders.rs b/api-server/stack-test-suite/tests/v2/orders.rs index dabb09d78b..05e1df2a67 100644 --- a/api-server/stack-test-suite/tests/v2/orders.rs +++ b/api-server/stack-test-suite/tests/v2/orders.rs @@ -54,14 +54,15 @@ async fn create_fill_conclude_order(#[case] seed: Seed, #[case] version: OrdersV .build(); // Issue and mint some tokens to create an order with different currencies + let tokens_amount = Amount::from_atoms(10); let issue_and_mint_result = - helpers::issue_and_mint_tokens_from_genesis(&mut rng, &mut tf); + helpers::issue_and_mint_tokens_from_genesis(tokens_amount, &mut rng, &mut tf); // Create order let order_data = OrderData::new( Destination::AnyoneCanSpend, OutputValue::Coin(Amount::from_atoms(10)), - OutputValue::TokenV1(issue_and_mint_result.token_id, Amount::from_atoms(10)), + OutputValue::TokenV1(issue_and_mint_result.token_id, tokens_amount), ); let tx_1 = TransactionBuilder::new() .add_input( @@ -223,14 +224,15 @@ async fn order_pairs(#[case] seed: Seed) { .build(); // Issue and mint some tokens to create an order with different currencies + let tokens_amount = Amount::from_atoms(10); let issue_and_mint_result = - helpers::issue_and_mint_tokens_from_genesis(&mut rng, &mut tf); + helpers::issue_and_mint_tokens_from_genesis(tokens_amount, &mut rng, &mut tf); // Create order let order_data = OrderData::new( Destination::AnyoneCanSpend, OutputValue::Coin(Amount::from_atoms(10)), - OutputValue::TokenV1(issue_and_mint_result.token_id, Amount::from_atoms(10)), + OutputValue::TokenV1(issue_and_mint_result.token_id, tokens_amount), ); let tx_1 = TransactionBuilder::new() .add_input( From 023b68d4c0ba76d3cc63949e98b98d6ca8bbc989 Mon Sep 17 00:00:00 2001 From: Mykhailo Kremniov Date: Mon, 22 Sep 2025 20:01:20 +0300 Subject: [PATCH 2/2] Fix spurious failures of order tests in chainstate-test-suite --- chainstate/test-framework/src/helpers.rs | 26 ++++++++- .../test-suite/src/tests/orders_tests.rs | 57 +++++++++++++------ 2 files changed, 64 insertions(+), 19 deletions(-) diff --git a/chainstate/test-framework/src/helpers.rs b/chainstate/test-framework/src/helpers.rs index a7ccebdbab..8062f4c3fa 100644 --- a/chainstate/test-framework/src/helpers.rs +++ b/chainstate/test-framework/src/helpers.rs @@ -26,7 +26,7 @@ use common::{ }, primitives::{Amount, BlockHeight, Id, Idable}, }; -use orders_accounting::OrdersAccountingDB; +use orders_accounting::{OrdersAccountingDB, OrdersAccountingView as _}; use randomness::{CryptoRng, Rng, SliceRandom as _}; use test_utils::random_ascii_alphanumeric_string; @@ -203,6 +203,30 @@ pub fn calculate_fill_order( .unwrap() } +pub fn order_min_non_zero_fill_amount( + tf: &TestFramework, + order_id: &OrderId, + orders_version: OrdersVersion, +) -> Amount { + match orders_version { + // Note: in orders v0 even direct zero fills are allowed. + // However, this function is supposed to only return non-zero amounts. + OrdersVersion::V0 => Amount::from_atoms(1), + + // In orders v1, the fill amount must be big enough so that the filled amount is non-zero. + OrdersVersion::V1 => { + let db_tx = tf.storage.transaction_ro().unwrap(); + let orders_db = OrdersAccountingDB::new(&db_tx); + + let order_data = orders_db.get_order_data(order_id).unwrap().unwrap(); + let original_ask = order_data.ask().amount().into_atoms(); + let original_give = order_data.give().amount().into_atoms(); + + Amount::from_atoms(original_ask.div_ceil(original_give)) + } + } +} + /// Split an u128 value into the specified number of "randomish" parts (the min part size is half /// the average part size). pub fn split_u128(rng: &mut (impl Rng + CryptoRng), amount: u128, parts_count: usize) -> Vec { diff --git a/chainstate/test-suite/src/tests/orders_tests.rs b/chainstate/test-suite/src/tests/orders_tests.rs index 6795b12560..0bfd6663db 100644 --- a/chainstate/test-suite/src/tests/orders_tests.rs +++ b/chainstate/test-suite/src/tests/orders_tests.rs @@ -22,7 +22,10 @@ use chainstate::{ ConnectTransactionError, }; use chainstate_test_framework::{ - helpers::{calculate_fill_order, issue_and_mint_random_token_from_best_block}, + helpers::{ + calculate_fill_order, issue_and_mint_random_token_from_best_block, + order_min_non_zero_fill_amount, + }, output_value_amount, TestFramework, TransactionBuilder, }; use common::{ @@ -669,7 +672,10 @@ fn fill_order_check_storage(#[case] seed: Seed, #[case] version: OrdersVersion) tf.make_block_builder().add_transaction(tx).build_and_process(&mut rng).unwrap(); // Fill the order partially - let fill_amount = Amount::from_atoms(rng.gen_range(1..ask_amount.into_atoms())); + let min_fill_amount = order_min_non_zero_fill_amount(&tf, &order_id, version); + let fill_amount = Amount::from_atoms( + rng.gen_range(min_fill_amount.into_atoms()..=ask_amount.into_atoms()), + ); let filled_amount = calculate_fill_order(&tf, &order_id, fill_amount, version); let left_to_fill = (ask_amount - fill_amount).unwrap(); @@ -799,7 +805,10 @@ fn fill_partially_then_conclude(#[case] seed: Seed, #[case] version: OrdersVersi tf.make_block_builder().add_transaction(tx).build_and_process(&mut rng).unwrap(); // Fill the order partially - let fill_amount = Amount::from_atoms(rng.gen_range(1..=ask_amount.into_atoms())); + let min_fill_amount = order_min_non_zero_fill_amount(&tf, &order_id, version); + let fill_amount = Amount::from_atoms( + rng.gen_range(min_fill_amount.into_atoms()..=ask_amount.into_atoms()), + ); let filled_amount = calculate_fill_order(&tf, &order_id, fill_amount, version); let fill_input = match version { @@ -1475,7 +1484,10 @@ fn reorg_before_create(#[case] seed: Seed, #[case] version: OrdersVersion) { .unwrap(); // Fill the order partially - let fill_amount = Amount::from_atoms(rng.gen_range(1..ask_amount.into_atoms())); + let min_fill_amount = order_min_non_zero_fill_amount(&tf, &order_id, version); + let fill_amount = Amount::from_atoms( + rng.gen_range(min_fill_amount.into_atoms()..ask_amount.into_atoms()), + ); let filled_amount = calculate_fill_order(&tf, &order_id, fill_amount, version); let left_to_fill = (ask_amount - fill_amount).unwrap(); @@ -1594,7 +1606,10 @@ fn reorg_after_create(#[case] seed: Seed, #[case] version: OrdersVersion) { let reorg_common_ancestor = tf.best_block_id(); // Fill the order partially - let fill_amount = Amount::from_atoms(rng.gen_range(1..ask_amount.into_atoms())); + let min_fill_amount = order_min_non_zero_fill_amount(&tf, &order_id, version); + let fill_amount = Amount::from_atoms( + rng.gen_range(min_fill_amount.into_atoms()..ask_amount.into_atoms()), + ); let filled_amount = calculate_fill_order(&tf, &order_id, fill_amount, version); let left_to_fill = (ask_amount - fill_amount).unwrap(); @@ -2626,7 +2641,7 @@ fn orders_v1_activation(#[case] seed: Seed) { .build_and_process(&mut rng) .unwrap(); - // Try to fill order before activation, check an error + // Try to fill order before activation, expect an error { let tx = TransactionBuilder::new() .add_input( @@ -2654,7 +2669,7 @@ fn orders_v1_activation(#[case] seed: Seed) { ); } - // Try to conclude order before activation, check an error + // Try to conclude order before activation, expect an error { let tx = TransactionBuilder::new() .add_input( @@ -2682,7 +2697,7 @@ fn orders_v1_activation(#[case] seed: Seed) { // produce an empty block and activate fork tf.make_block_builder().build_and_process(&mut rng).unwrap(); - // Try to fill order with deprecated command, check an error + // Try to fill order with deprecated command, expect an error { let tx = TransactionBuilder::new() .add_input( @@ -2714,7 +2729,7 @@ fn orders_v1_activation(#[case] seed: Seed) { ); } - // Try to conclude order before activation, check an error + // Try to conclude order with deprecated command, expect an error { let tx = TransactionBuilder::new() .add_input( @@ -3175,7 +3190,10 @@ fn fill_freeze_conclude_order(#[case] seed: Seed) { tf.make_block_builder().add_transaction(tx).build_and_process(&mut rng).unwrap(); // Fill order partially - let fill_amount = Amount::from_atoms(rng.gen_range(1..=ask_amount.into_atoms())); + let min_fill_amount = order_min_non_zero_fill_amount(&tf, &order_id, OrdersVersion::V1); + let fill_amount = Amount::from_atoms( + rng.gen_range(min_fill_amount.into_atoms()..=ask_amount.into_atoms()), + ); let filled_amount = calculate_fill_order(&tf, &order_id, fill_amount, OrdersVersion::V1); let left_to_fill = (ask_amount - fill_amount).unwrap(); let fill_tx = TransactionBuilder::new() @@ -3260,7 +3278,7 @@ fn fill_freeze_conclude_order(#[case] seed: Seed) { } } - //Try freezing the order once more + // Try freezing the order once more { let result = tf .make_block_builder() @@ -3644,10 +3662,9 @@ fn fill_order_v1_must_not_be_signed(#[case] seed: Seed) { let (sk, pk) = PrivateKey::new_from_rng(&mut rng, KeyKind::Secp256k1Schnorr); let output_destination = Destination::PublicKey(pk); - let min_fill_amount = - initial_ask_amount.into_atoms().div_ceil(initial_give_amount.into_atoms()); + let min_fill_amount = order_min_non_zero_fill_amount(&tf, &order_id, OrdersVersion::V1); let fill_amount = Amount::from_atoms( - rng.gen_range(min_fill_amount..initial_ask_amount.into_atoms() / 10), + rng.gen_range(min_fill_amount.into_atoms()..initial_ask_amount.into_atoms() / 10), ); let filled_amount = calculate_fill_order(&tf, &order_id, fill_amount, OrdersVersion::V1); let fill_order_input = @@ -3778,6 +3795,7 @@ fn fill_order_v1_must_not_be_signed(#[case] seed: Seed) { #[rstest] #[trace] #[case(Seed::from_entropy(), OrdersVersion::V0)] +#[trace] #[case(Seed::from_entropy(), OrdersVersion::V1)] fn fill_order_twice_in_same_block( #[case] seed: Seed, @@ -3810,15 +3828,18 @@ fn fill_order_twice_in_same_block( let (_, pk) = PrivateKey::new_from_rng(&mut rng, KeyKind::Secp256k1Schnorr); let output_destination = Destination::PublicKey(pk); - let fill_amount1 = - Amount::from_atoms(rng.gen_range(1..initial_ask_amount.into_atoms() / 3)); + let min_fill_amount = order_min_non_zero_fill_amount(&tf, &order_id, version); + let fill_amount1 = Amount::from_atoms( + rng.gen_range(min_fill_amount.into_atoms()..initial_ask_amount.into_atoms() / 3), + ); let fill_amount2 = if use_same_amount { fill_amount1 } else { (|| { for _ in 0..1000 { - let new_amount = - Amount::from_atoms(rng.gen_range(1..initial_ask_amount.into_atoms() / 3)); + let new_amount = Amount::from_atoms(rng.gen_range( + min_fill_amount.into_atoms()..initial_ask_amount.into_atoms() / 3, + )); if new_amount != fill_amount1 { return new_amount; }