Skip to content

Commit f1940eb

Browse files
authored
Merge branch 'master' into fix_infinite_expand
2 parents 3f8b6ac + 4207d94 commit f1940eb

File tree

19 files changed

+281
-131
lines changed

19 files changed

+281
-131
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1010
- [2551](https://github.com/FuelLabs/fuel-core/pull/2551): Enhanced the DA compressed block header to include block id.
1111
- [2579](https://github.com/FuelLabs/fuel-core/pull/2579): Clear expiration txs cache in transaction pool based on inserted transactions
1212

13+
### Fixed
14+
- [2609](https://github.com/FuelLabs/fuel-core/pull/2609): Check response before trying to deserialize, return error instead
15+
- [2599](https://github.com/FuelLabs/fuel-core/pull/2599): Use the proper `url` apis to construct full url path in `BlockCommitterHttpApi` client
16+
- [2593](https://github.com/FuelLabs/fuel-core/pull/2593): Fixed utxo id decompression
17+
1318
## [Version 0.41.0]
1419

1520
### Added

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.

crates/chain-config/src/config/coin.rs

Lines changed: 58 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ use fuel_core_types::{
2020
BlockHeight,
2121
Bytes32,
2222
},
23-
fuel_vm::SecretKey,
2423
};
2524
use serde::{
2625
Deserialize,
@@ -75,41 +74,6 @@ impl From<CoinConfig> for TableEntry<Coins> {
7574
}
7675
}
7776

78-
/// Generates a new coin config with a unique utxo id for testing
79-
#[derive(Default, Debug)]
80-
pub struct CoinConfigGenerator {
81-
count: usize,
82-
}
83-
84-
impl CoinConfigGenerator {
85-
pub fn new() -> Self {
86-
Self { count: 0 }
87-
}
88-
89-
pub fn generate(&mut self) -> CoinConfig {
90-
let mut bytes = [0u8; 32];
91-
bytes[..std::mem::size_of::<usize>()].copy_from_slice(&self.count.to_be_bytes());
92-
93-
let config = CoinConfig {
94-
tx_id: Bytes32::from(bytes),
95-
..Default::default()
96-
};
97-
self.count = self.count.checked_add(1).expect("Max coin count reached");
98-
99-
config
100-
}
101-
102-
pub fn generate_with(&mut self, secret: SecretKey, amount: u64) -> CoinConfig {
103-
let owner = Address::from(*secret.public_key().hash());
104-
105-
CoinConfig {
106-
amount,
107-
owner,
108-
..self.generate()
109-
}
110-
}
111-
}
112-
11377
impl CoinConfig {
11478
pub fn utxo_id(&self) -> UtxoId {
11579
UtxoId::new(self.tx_id, self.output_index)
@@ -157,6 +121,62 @@ impl GenesisCommitment for Coin {
157121
}
158122
}
159123

124+
#[cfg(feature = "test-helpers")]
125+
pub mod coin_config_helpers {
126+
use crate::CoinConfig;
127+
use fuel_core_types::{
128+
fuel_types::{
129+
Address,
130+
Bytes32,
131+
},
132+
fuel_vm::SecretKey,
133+
};
134+
135+
type CoinCount = u16;
136+
137+
/// Generates a new coin config with a unique utxo id for testing
138+
#[derive(Default, Debug)]
139+
pub struct CoinConfigGenerator {
140+
count: CoinCount,
141+
}
142+
143+
pub fn tx_id(count: CoinCount) -> Bytes32 {
144+
let mut bytes = [0u8; 32];
145+
bytes[..size_of::<CoinCount>()].copy_from_slice(&count.to_be_bytes());
146+
bytes.into()
147+
}
148+
149+
impl CoinConfigGenerator {
150+
pub fn new() -> Self {
151+
Self { count: 0 }
152+
}
153+
154+
pub fn generate(&mut self) -> CoinConfig {
155+
let tx_id = tx_id(self.count);
156+
157+
let config = CoinConfig {
158+
tx_id,
159+
output_index: self.count,
160+
..Default::default()
161+
};
162+
163+
self.count = self.count.checked_add(1).expect("Max coin count reached");
164+
165+
config
166+
}
167+
168+
pub fn generate_with(&mut self, secret: SecretKey, amount: u64) -> CoinConfig {
169+
let owner = Address::from(*secret.public_key().hash());
170+
171+
CoinConfig {
172+
amount,
173+
owner,
174+
..self.generate()
175+
}
176+
}
177+
}
178+
}
179+
160180
#[cfg(test)]
161181
mod tests {
162182
use super::*;
@@ -167,7 +187,7 @@ mod tests {
167187

168188
#[test]
169189
fn test_generate_unique_utxo_id() {
170-
let mut generator = CoinConfigGenerator::new();
190+
let mut generator = coin_config_helpers::CoinConfigGenerator::new();
171191
let config1 = generator.generate();
172192
let config2 = generator.generate();
173193

@@ -180,7 +200,7 @@ mod tests {
180200
let secret = SecretKey::random(&mut rng);
181201
let amount = 1000;
182202

183-
let mut generator = CoinConfigGenerator::new();
203+
let mut generator = coin_config_helpers::CoinConfigGenerator::new();
184204
let config = generator.generate_with(secret, amount);
185205

186206
assert_eq!(config.owner, Address::from(*secret.public_key().hash()));

crates/chain-config/src/config/state.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ use serde::{
4646
use crate::SnapshotMetadata;
4747

4848
#[cfg(feature = "test-helpers")]
49-
use crate::CoinConfigGenerator;
49+
use crate::coin_config_helpers::CoinConfigGenerator;
5050
#[cfg(feature = "test-helpers")]
5151
use bech32::{
5252
ToBase32,

crates/compression/src/decompress.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use fuel_core_types::{
1818
RegistryKey,
1919
},
2020
fuel_tx::{
21+
field::TxPointer,
2122
input::{
2223
coin::{
2324
Coin,
@@ -35,6 +36,7 @@ use fuel_core_types::{
3536
Mint,
3637
ScriptCode,
3738
Transaction,
39+
TxPointer as FuelTxPointer,
3840
UtxoId,
3941
},
4042
fuel_types::{
@@ -68,12 +70,29 @@ where
6870
db,
6971
};
7072

71-
let transactions = <Vec<Transaction> as DecompressibleBy<_>>::decompress_with(
73+
let mut transactions = <Vec<Transaction> as DecompressibleBy<_>>::decompress_with(
7274
block.transactions(),
7375
&ctx,
7476
)
7577
.await?;
7678

79+
let transaction_count = transactions.len();
80+
81+
// patch mint transaction
82+
let mint_tx = transactions
83+
.last_mut()
84+
.ok_or_else(|| anyhow::anyhow!("No transactions"))?;
85+
if let Transaction::Mint(mint) = mint_tx {
86+
let tx_pointer = mint.tx_pointer_mut();
87+
*tx_pointer = FuelTxPointer::new(
88+
block.consensus_header().height,
89+
#[allow(clippy::arithmetic_side_effects)]
90+
u16::try_from(transaction_count - 1)?,
91+
);
92+
} else {
93+
anyhow::bail!("Last transaction is not a mint");
94+
}
95+
7796
Ok(PartialFuelBlock {
7897
header: block.partial_block_header(),
7998
transactions,
@@ -211,7 +230,7 @@ where
211230
ctx: &DecompressCtx<D>,
212231
) -> anyhow::Result<Self> {
213232
Ok(Transaction::mint(
214-
Default::default(), // TODO: what should this we do with this?
233+
Default::default(), // TODO: what should we do with this?
215234
c.input_contract.decompress(ctx).await?,
216235
c.output_contract.decompress(ctx).await?,
217236
c.mint_amount.decompress(ctx).await?,

crates/fuel-core/src/coins_query.rs

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,20 @@ pub async fn select_coins_to_spend(
285285
// See also "let upper_target = target.saturating_mul(2);" in "fn random_improve()".
286286
const TOTAL_AMOUNT_ADJUSTMENT_FACTOR: u64 = 2;
287287

288+
// After selecting large coins that cover at least twice the required amount,
289+
// we include a limited number of small (dust) coins. The maximum number of dust coins
290+
// is determined by the multiplier defined below. Specifically, the number of dust coins
291+
// will never exceed FACTOR times the number of large coins selected.
292+
//
293+
// This limit prevents excessive dust coins from being included in cases where
294+
// the query lacks a specified maximum limit (defaulting to 255).
295+
//
296+
// Example:
297+
// - If 3 large coins are selected (and FACTOR is 5), up to 15 dust coins may be included (0..=15).
298+
// - Still, if the selected dust can cover the amount of some big coins, the
299+
// latter will be removed from the set
300+
const DUST_TO_BIG_COINS_FACTOR: u16 = 5;
301+
288302
if total == 0 || max == 0 {
289303
return Err(CoinsQueryError::IncorrectQueryParameters {
290304
provided_total: total,
@@ -327,7 +341,8 @@ pub async fn select_coins_to_spend(
327341
}
328342
})?;
329343

330-
let max_dust_count = max_dust_count(max, number_of_big_coins);
344+
let max_dust_count =
345+
max_dust_count(max, number_of_big_coins, DUST_TO_BIG_COINS_FACTOR);
331346
let (dust_coins_total, selected_dust_coins) = dust_coins(
332347
dust_coins_stream,
333348
last_selected_big_coin,
@@ -408,9 +423,14 @@ fn is_excluded(key: &CoinsToSpendIndexKey, excluded_ids: &ExcludedCoinIds) -> bo
408423
}
409424
}
410425

411-
fn max_dust_count(max: u16, big_coins_len: u16) -> u16 {
426+
fn max_dust_count(max: u16, big_coins_len: u16, dust_to_big_coins_factor: u16) -> u16 {
412427
let mut rng = rand::thread_rng();
413-
rng.gen_range(0..=max.saturating_sub(big_coins_len))
428+
429+
let max_from_factor = big_coins_len.saturating_mul(dust_to_big_coins_factor);
430+
let max_adjusted = max.saturating_sub(big_coins_len);
431+
let upper_bound = max_from_factor.min(max_adjusted);
432+
433+
rng.gen_range(0..=upper_bound)
414434
}
415435

416436
fn skip_big_coins_up_to_amount(
@@ -442,6 +462,7 @@ mod tests {
442462
use crate::{
443463
coins_query::{
444464
largest_first,
465+
max_dust_count,
445466
random_improve,
446467
CoinsQueryError,
447468
SpendQuery,
@@ -491,6 +512,10 @@ mod tests {
491512
};
492513
use futures::TryStreamExt;
493514
use itertools::Itertools;
515+
use proptest::{
516+
prelude::*,
517+
proptest,
518+
};
494519
use rand::{
495520
rngs::StdRng,
496521
Rng,
@@ -1537,6 +1562,27 @@ mod tests {
15371562
)
15381563
}
15391564

1565+
proptest! {
1566+
#[test]
1567+
fn max_dust_count_respects_limits(
1568+
max in 1u16..255,
1569+
number_of_big_coins in 1u16..255,
1570+
factor in 1u16..10,
1571+
) {
1572+
// We're at the stage of the algorithm where we have already selected the big coins and
1573+
// we're trying to select the dust coins.
1574+
// So we're sure that the following assumptions hold:
1575+
// 1. number_of_big_coins <= max - big coin selection algo is capped at 'max'.
1576+
// 2. there must be at least one big coin selected, otherwise we'll break
1577+
// with the `InsufficientCoinsForTheMax` error earlier.
1578+
prop_assume!(number_of_big_coins <= max && number_of_big_coins >= 1);
1579+
1580+
let max_dust_count = max_dust_count(max, number_of_big_coins, factor);
1581+
prop_assert!(number_of_big_coins + max_dust_count <= max);
1582+
prop_assert!(max_dust_count <= number_of_big_coins.saturating_mul(factor));
1583+
}
1584+
}
1585+
15401586
#[test_case::test_case(
15411587
TestCase {
15421588
db_amount: vec![u64::MAX, u64::MAX],

crates/fuel-core/src/graphql_api/da_compression.rs

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -273,10 +273,7 @@ impl_temporal_registry!(ContractId);
273273
impl_temporal_registry!(ScriptCode);
274274
impl_temporal_registry!(PredicateCode);
275275

276-
impl<'a, Tx> UtxoIdToPointer for CompressDbTx<'a, Tx>
277-
where
278-
Tx: OffChainDatabaseTransaction,
279-
{
276+
impl<'a, Tx> UtxoIdToPointer for CompressDbTx<'a, Tx> {
280277
fn lookup(
281278
&self,
282279
utxo_id: fuel_core_types::fuel_tx::UtxoId,
@@ -301,7 +298,6 @@ where
301298

302299
impl<'a, Tx, Onchain> HistoryLookup for DecompressDbTx<'a, Tx, Onchain>
303300
where
304-
Tx: OffChainDatabaseTransaction,
305301
Onchain: StorageInspect<Coins, Error = fuel_core_storage::Error>
306302
+ StorageInspect<Messages, Error = fuel_core_storage::Error>
307303
+ StorageInspect<FuelBlocks, Error = fuel_core_storage::Error>,
@@ -310,17 +306,16 @@ where
310306
&self,
311307
c: fuel_core_types::fuel_tx::CompressedUtxoId,
312308
) -> anyhow::Result<fuel_core_types::fuel_tx::UtxoId> {
309+
#[cfg(feature = "test-helpers")]
313310
if c.tx_pointer.block_height() == 0u32.into() {
314311
// This is a genesis coin, which is handled differently.
315312
// See CoinConfigGenerator::generate which generates the genesis coins.
316-
let mut bytes = [0u8; 32];
317-
let tx_index = c.tx_pointer.tx_index();
318-
bytes[..std::mem::size_of_val(&tx_index)]
319-
.copy_from_slice(&tx_index.to_be_bytes());
320-
return Ok(fuel_core_types::fuel_tx::UtxoId::new(
321-
fuel_core_types::fuel_tx::TxId::from(bytes),
322-
0,
323-
));
313+
let tx_id =
314+
fuel_core_chain_config::coin_config_helpers::tx_id(c.output_index);
315+
316+
let utxo_id = fuel_core_types::fuel_tx::UtxoId::new(tx_id, c.output_index);
317+
318+
return Ok(utxo_id);
324319
}
325320

326321
let block_info = self

crates/fuel-core/src/p2p_test_helpers.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
33
use crate::{
44
chain_config::{
5+
coin_config_helpers::CoinConfigGenerator,
56
CoinConfig,
6-
CoinConfigGenerator,
77
},
88
combined_database::CombinedDatabase,
99
database::{
@@ -246,7 +246,7 @@ pub async fn make_nodes(
246246
let initial_coin = CoinConfig {
247247
// set idx to prevent overlapping utxo_ids when
248248
// merging with existing coins from config
249-
output_index: 2,
249+
output_index: 10,
250250
..coin_generator.generate_with(secret, 10000)
251251
};
252252
let tx = TransactionBuilder::script(

crates/services/gas_price_service/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ num_enum = { workspace = true }
2222
parking_lot = { workspace = true }
2323
reqwest = { workspace = true, features = ["json"] }
2424
serde = { workspace = true }
25-
serde_json = { workspace = true, optional = true }
25+
serde_json = { workspace = true }
2626
strum = { workspace = true, features = ["derive"] }
2727
strum_macros = { workspace = true }
2828
thiserror = { workspace = true }
@@ -39,4 +39,4 @@ mockito = { version = "1.6.1" }
3939
serde_json = { workspace = true }
4040

4141
[features]
42-
test-helpers = ["dep:mockito", "dep:serde_json"]
42+
test-helpers = ["dep:mockito"]

0 commit comments

Comments
 (0)