Skip to content

Commit f980cce

Browse files
authored
Merge pull request #1937 from mintlayer/mempool_oders_v1_fix
Allow multiple FillOrder v1 txs to co-exist in the mempool
2 parents 36ed048 + 90cf547 commit f980cce

20 files changed

Lines changed: 1365 additions & 270 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
// Copyright (c) 2021-2025 RBB S.r.l
2+
// opensource@mintlayer.org
3+
// SPDX-License-Identifier: MIT
4+
// Licensed under the MIT License;
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
use chainstate::BlockSource;
17+
use chainstate_storage::{BlockchainStorageRead, Transactional};
18+
use common::{
19+
chain::{
20+
make_token_id,
21+
output_value::OutputValue,
22+
signature::inputsig::InputWitness,
23+
tokens::{IsTokenFreezable, TokenId, TokenIssuance, TokenIssuanceV1, TokenTotalSupply},
24+
AccountCommand, AccountNonce, AccountType, Block, Destination, GenBlock, OrderId,
25+
OrdersVersion, Transaction, TxInput, TxOutput, UtxoOutPoint,
26+
},
27+
primitives::{Amount, BlockHeight, Id, Idable},
28+
};
29+
use orders_accounting::OrdersAccountingDB;
30+
use randomness::{CryptoRng, Rng, SliceRandom as _};
31+
use test_utils::random_ascii_alphanumeric_string;
32+
33+
use crate::{get_output_value, TestFramework, TransactionBuilder};
34+
35+
// Note: this function will create 2 blocks
36+
pub fn issue_and_mint_random_token_from_best_block(
37+
rng: &mut (impl Rng + CryptoRng),
38+
tf: &mut TestFramework,
39+
utxo_to_pay_fee: UtxoOutPoint,
40+
amount_to_mint: Amount,
41+
total_supply: TokenTotalSupply,
42+
is_freezable: IsTokenFreezable,
43+
) -> (
44+
TokenId,
45+
/*tokens*/ UtxoOutPoint,
46+
/*coins change*/ UtxoOutPoint,
47+
) {
48+
let best_block_id = tf.best_block_id();
49+
let issuance = {
50+
let max_ticker_len = tf.chain_config().token_max_ticker_len();
51+
let max_dec_count = tf.chain_config().token_max_dec_count();
52+
let max_uri_len = tf.chain_config().token_max_uri_len();
53+
54+
let issuance = TokenIssuanceV1 {
55+
token_ticker: random_ascii_alphanumeric_string(rng, 1..max_ticker_len)
56+
.as_bytes()
57+
.to_vec(),
58+
number_of_decimals: rng.gen_range(1..max_dec_count),
59+
metadata_uri: random_ascii_alphanumeric_string(rng, 1..max_uri_len).as_bytes().to_vec(),
60+
total_supply,
61+
is_freezable,
62+
authority: Destination::AnyoneCanSpend,
63+
};
64+
TokenIssuance::V1(issuance)
65+
};
66+
67+
let (token_id, _, utxo_with_change) =
68+
issue_token_from_block(rng, tf, best_block_id, utxo_to_pay_fee, issuance);
69+
70+
let best_block_id = tf.best_block_id();
71+
let (_, mint_tx_id) = mint_tokens_in_block(
72+
rng,
73+
tf,
74+
best_block_id,
75+
utxo_with_change,
76+
token_id,
77+
amount_to_mint,
78+
true,
79+
);
80+
81+
(
82+
token_id,
83+
UtxoOutPoint::new(mint_tx_id.into(), 0),
84+
UtxoOutPoint::new(mint_tx_id.into(), 1),
85+
)
86+
}
87+
88+
pub fn issue_token_from_block(
89+
rng: &mut (impl Rng + CryptoRng),
90+
tf: &mut TestFramework,
91+
parent_block_id: Id<GenBlock>,
92+
utxo_to_pay_fee: UtxoOutPoint,
93+
issuance: TokenIssuance,
94+
) -> (TokenId, Id<Block>, UtxoOutPoint) {
95+
let token_issuance_fee = tf.chainstate.get_chain_config().fungible_token_issuance_fee();
96+
97+
let fee_utxo_coins =
98+
get_output_value(tf.chainstate.utxo(&utxo_to_pay_fee).unwrap().unwrap().output())
99+
.unwrap()
100+
.coin_amount()
101+
.unwrap();
102+
103+
let tx = TransactionBuilder::new()
104+
.add_input(utxo_to_pay_fee.into(), InputWitness::NoSignature(None))
105+
.add_output(TxOutput::Transfer(
106+
OutputValue::Coin((fee_utxo_coins - token_issuance_fee).unwrap()),
107+
Destination::AnyoneCanSpend,
108+
))
109+
.add_output(TxOutput::IssueFungibleToken(Box::new(issuance.clone())))
110+
.build();
111+
let parent_block_height = tf.gen_block_index(&parent_block_id).block_height();
112+
let token_id = make_token_id(
113+
tf.chain_config(),
114+
parent_block_height.next_height(),
115+
tx.transaction().inputs(),
116+
)
117+
.unwrap();
118+
let tx_id = tx.transaction().get_id();
119+
let block = tf
120+
.make_block_builder()
121+
.add_transaction(tx)
122+
.with_parent(parent_block_id)
123+
.build(rng);
124+
let block_id = block.get_id();
125+
tf.process_block(block, BlockSource::Local).unwrap();
126+
127+
(token_id, block_id, UtxoOutPoint::new(tx_id.into(), 0))
128+
}
129+
130+
pub fn mint_tokens_in_block(
131+
rng: &mut (impl Rng + CryptoRng),
132+
tf: &mut TestFramework,
133+
parent_block_id: Id<GenBlock>,
134+
utxo_to_pay_fee: UtxoOutPoint,
135+
token_id: TokenId,
136+
amount_to_mint: Amount,
137+
produce_change: bool,
138+
) -> (Id<Block>, Id<Transaction>) {
139+
let token_supply_change_fee =
140+
tf.chainstate.get_chain_config().token_supply_change_fee(BlockHeight::zero());
141+
142+
let nonce = BlockchainStorageRead::get_account_nonce_count(
143+
&tf.storage.transaction_ro().unwrap(),
144+
AccountType::Token(token_id),
145+
)
146+
.unwrap()
147+
.map_or(AccountNonce::new(0), |n| n.increment().unwrap());
148+
149+
let tx_builder = TransactionBuilder::new()
150+
.add_input(
151+
TxInput::from_command(nonce, AccountCommand::MintTokens(token_id, amount_to_mint)),
152+
InputWitness::NoSignature(None),
153+
)
154+
.add_input(
155+
utxo_to_pay_fee.clone().into(),
156+
InputWitness::NoSignature(None),
157+
)
158+
.add_output(TxOutput::Transfer(
159+
OutputValue::TokenV1(token_id, amount_to_mint),
160+
Destination::AnyoneCanSpend,
161+
));
162+
163+
let tx_builder = if produce_change {
164+
let fee_utxo_coins = tf.coin_amount_from_utxo(&utxo_to_pay_fee);
165+
166+
tx_builder.add_output(TxOutput::Transfer(
167+
OutputValue::Coin((fee_utxo_coins - token_supply_change_fee).unwrap()),
168+
Destination::AnyoneCanSpend,
169+
))
170+
} else {
171+
tx_builder
172+
};
173+
174+
let tx = tx_builder.build();
175+
let tx_id = tx.transaction().get_id();
176+
177+
let block = tf
178+
.make_block_builder()
179+
.add_transaction(tx)
180+
.with_parent(parent_block_id)
181+
.build(rng);
182+
let block_id = block.get_id();
183+
tf.process_block(block, BlockSource::Local).unwrap();
184+
185+
(block_id, tx_id)
186+
}
187+
188+
/// Given the fill amount in the "ask" currency, return the filled amount in the "give" currency.
189+
pub fn calculate_fill_order(
190+
tf: &TestFramework,
191+
order_id: &OrderId,
192+
fill_amount_in_ask_currency: Amount,
193+
orders_version: OrdersVersion,
194+
) -> Amount {
195+
let db_tx = tf.storage.transaction_ro().unwrap();
196+
let orders_db = OrdersAccountingDB::new(&db_tx);
197+
orders_accounting::calculate_fill_order(
198+
&orders_db,
199+
*order_id,
200+
fill_amount_in_ask_currency,
201+
orders_version,
202+
)
203+
.unwrap()
204+
}
205+
206+
/// Split an u128 value into the specified number of "randomish" parts (the min part size is half
207+
/// the average part size).
208+
pub fn split_u128(rng: &mut (impl Rng + CryptoRng), amount: u128, parts_count: usize) -> Vec<u128> {
209+
assert!(parts_count > 0);
210+
let mut result = Vec::with_capacity(parts_count);
211+
let parts_count = parts_count as u128;
212+
let min_part_amount = amount / parts_count / 2;
213+
let mut remaining_amount_above_min = amount - min_part_amount * parts_count;
214+
215+
for i in 0..parts_count {
216+
let amount_part_above_min = if i == parts_count - 1 {
217+
remaining_amount_above_min
218+
} else {
219+
rng.gen_range(0..remaining_amount_above_min / 2)
220+
};
221+
222+
result.push(min_part_amount + amount_part_above_min);
223+
remaining_amount_above_min -= amount_part_above_min;
224+
}
225+
226+
assert_eq!(result.iter().sum::<u128>(), amount);
227+
228+
result.shuffle(rng);
229+
result
230+
}
231+
232+
/// Start building a tx that will "split" the specified outpoint into the specified number of outpoints.
233+
///
234+
/// The "fee" parameter only makes sense if the outpoint's currency is coins.
235+
pub fn make_tx_builder_to_split_utxo(
236+
rng: &mut (impl Rng + CryptoRng),
237+
tf: &mut TestFramework,
238+
outpoint: UtxoOutPoint,
239+
parts_count: usize,
240+
fee: Amount,
241+
) -> TransactionBuilder {
242+
let utxo_output_value = get_output_value(tf.utxo(&outpoint).output()).unwrap();
243+
let utxo_amount = utxo_output_value.amount();
244+
245+
let output_amounts = split_u128(rng, (utxo_amount - fee).unwrap().into_atoms(), parts_count);
246+
247+
let mut tx_builder =
248+
TransactionBuilder::new().add_input(outpoint.into(), InputWitness::NoSignature(None));
249+
for output_amount in output_amounts {
250+
tx_builder = tx_builder.add_output(TxOutput::Transfer(
251+
output_value_with_amount(&utxo_output_value, Amount::from_atoms(output_amount)),
252+
Destination::AnyoneCanSpend,
253+
))
254+
}
255+
256+
tx_builder
257+
}
258+
259+
pub fn split_utxo(
260+
rng: &mut (impl Rng + CryptoRng),
261+
tf: &mut TestFramework,
262+
outpoint: UtxoOutPoint,
263+
parts_count: usize,
264+
) -> Id<Transaction> {
265+
let tx = make_tx_builder_to_split_utxo(rng, tf, outpoint, parts_count, Amount::ZERO).build();
266+
let tx_id = tx.transaction().get_id();
267+
268+
tf.make_block_builder().add_transaction(tx).build_and_process(rng).unwrap();
269+
tx_id
270+
}
271+
272+
pub fn output_value_with_amount(output_value: &OutputValue, new_amount: Amount) -> OutputValue {
273+
match output_value {
274+
OutputValue::Coin(_) => OutputValue::Coin(new_amount),
275+
OutputValue::TokenV0(_) => {
276+
panic!("Unexpected token v0");
277+
}
278+
OutputValue::TokenV1(id, _) => OutputValue::TokenV1(*id, new_amount),
279+
}
280+
}

chainstate/test-framework/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
mod block_builder;
1919
mod framework;
2020
mod framework_builder;
21+
pub mod helpers;
2122
mod key_manager;
2223
mod pos_block_builder;
2324
mod random_tx_maker;

chainstate/test-suite/src/tests/fungible_tokens_v1.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ use chainstate::{
2222
ConnectTransactionError, IOPolicyError, TokensError,
2323
};
2424
use chainstate_storage::{BlockchainStorageRead, Transactional};
25-
use chainstate_test_framework::{TestFramework, TransactionBuilder};
25+
use chainstate_test_framework::{
26+
helpers::{issue_token_from_block, mint_tokens_in_block},
27+
TestFramework, TransactionBuilder,
28+
};
2629
use common::{
2730
chain::{
2831
make_token_id,
@@ -57,8 +60,6 @@ use tx_verifier::{
5760
CheckTransactionError,
5861
};
5962

60-
use crate::tests::helpers::{issue_token_from_block, mint_tokens_in_block};
61-
6263
fn make_issuance(
6364
rng: &mut impl Rng,
6465
supply: TokenTotalSupply,

0 commit comments

Comments
 (0)