Skip to content

Commit 526f6c1

Browse files
authored
Merge pull request #1971 from mintlayer/chainstate_test_fw_and_api_srv_cleanup
Chainstate test framework and api server cleanup
2 parents b118b3b + 49f7b04 commit 526f6c1

File tree

19 files changed

+505
-229
lines changed

19 files changed

+505
-229
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api-server/api-server-common/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ async-trait.workspace = true
2121
bb8-postgres = "0.8"
2222
clap = { workspace = true, features = ["derive"] }
2323
futures = { workspace = true, default-features = false }
24+
itertools.workspace = true
2425
parity-scale-codec.workspace = true
2526
thiserror.workspace = true
2627
tokio = { workspace = true, features = ["full"] }

api-server/api-server-common/src/storage/impls/in_memory/mod.rs

Lines changed: 41 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use common::{
3131
},
3232
primitives::{id::WithId, Amount, BlockHeight, CoinOrTokenId, Id, Idable},
3333
};
34+
use itertools::Itertools as _;
3435
use std::{
3536
cmp::Reverse,
3637
collections::{BTreeMap, BTreeSet},
@@ -61,15 +62,14 @@ struct ApiServerInMemoryStorage {
6162
statistics:
6263
BTreeMap<CoinOrTokenStatistic, BTreeMap<CoinOrTokenId, BTreeMap<BlockHeight, Amount>>>,
6364
orders_table: BTreeMap<OrderId, BTreeMap<BlockHeight, Order>>,
64-
best_block: BlockAuxData,
6565
genesis_block: Arc<WithId<Genesis>>,
6666
number_of_coin_decimals: u8,
6767
storage_version: u32,
6868
}
6969

7070
impl ApiServerInMemoryStorage {
7171
pub fn new(chain_config: &ChainConfig) -> Self {
72-
let mut result = Self {
72+
Self {
7373
block_table: BTreeMap::new(),
7474
block_aux_data_table: BTreeMap::new(),
7575
address_balance_table: BTreeMap::new(),
@@ -89,18 +89,9 @@ impl ApiServerInMemoryStorage {
8989
statistics: BTreeMap::new(),
9090
orders_table: BTreeMap::new(),
9191
genesis_block: chain_config.genesis_block().clone(),
92-
best_block: BlockAuxData::new(
93-
chain_config.genesis_block_id(),
94-
0.into(),
95-
chain_config.genesis_block().timestamp(),
96-
),
9792
number_of_coin_decimals: chain_config.coin_decimals(),
98-
storage_version: super::CURRENT_STORAGE_VERSION,
99-
};
100-
result
101-
.initialize_storage(chain_config)
102-
.expect("In-memory initialization must succeed");
103-
result
93+
storage_version: CURRENT_STORAGE_VERSION,
94+
}
10495
}
10596

10697
fn is_initialized(&self) -> Result<bool, ApiServerStorageError> {
@@ -295,7 +286,18 @@ impl ApiServerInMemoryStorage {
295286
}
296287

297288
fn get_best_block(&self) -> Result<BlockAuxData, ApiServerStorageError> {
298-
Ok(self.best_block)
289+
let result = self.main_chain_blocks_table.last_key_value().map_or_else(
290+
|| {
291+
BlockAuxData::new(
292+
self.genesis_block.get_id().into(),
293+
0.into(),
294+
self.genesis_block.timestamp(),
295+
)
296+
},
297+
|(_, id)| *self.block_aux_data_table.get(id).expect("must exist"),
298+
);
299+
300+
Ok(result)
299301
}
300302

301303
fn get_latest_blocktimestamps(&self) -> Result<Vec<BlockTimestamp>, ApiServerStorageError> {
@@ -331,28 +333,18 @@ impl ApiServerInMemoryStorage {
331333
&self,
332334
time_range: (BlockTimestamp, BlockTimestamp),
333335
) -> Result<(BlockHeight, BlockHeight), ApiServerStorageError> {
334-
let from_height = self
335-
.main_chain_blocks_table
336-
.iter()
337-
.find_map(|(k, v)| {
338-
(self.block_aux_data_table.get(v).expect("must exist").block_timestamp()
339-
>= time_range.0)
340-
.then_some(*k)
341-
})
342-
.unwrap_or(BlockHeight::new(0));
343-
344-
let to_height = self
336+
let result = self
345337
.main_chain_blocks_table
346338
.iter()
347-
.rev()
348-
.find_map(|(k, v)| {
349-
(self.block_aux_data_table.get(v).expect("must exist").block_timestamp()
350-
<= time_range.1)
351-
.then_some(*k)
339+
.filter_map(|(h, id)| {
340+
let ts = self.block_aux_data_table.get(id).expect("must exist").block_timestamp();
341+
(ts >= time_range.0 && ts <= time_range.1).then_some(*h)
352342
})
353-
.unwrap_or(BlockHeight::new(0));
343+
.minmax()
344+
.into_option()
345+
.unwrap_or((BlockHeight::new(0), BlockHeight::new(0)));
354346

355-
Ok((from_height, to_height))
347+
Ok(result)
356348
}
357349

358350
fn get_delegation(
@@ -798,35 +790,13 @@ impl ApiServerInMemoryStorage {
798790
}
799791

800792
impl ApiServerInMemoryStorage {
801-
fn initialize_storage(
802-
&mut self,
803-
_chain_config: &ChainConfig,
804-
) -> Result<(), ApiServerStorageError> {
805-
self.storage_version = CURRENT_STORAGE_VERSION;
806-
807-
Ok(())
808-
}
809-
810793
fn reinitialize_storage(
811794
&mut self,
812795
chain_config: &ChainConfig,
813796
) -> Result<(), ApiServerStorageError> {
814-
self.block_table.clear();
815-
self.block_aux_data_table.clear();
816-
self.address_balance_table.clear();
817-
self.address_locked_balance_table.clear();
818-
self.address_transactions_table.clear();
819-
self.delegation_table.clear();
820-
self.main_chain_blocks_table.clear();
821-
self.pool_data_table.clear();
822-
self.transaction_table.clear();
823-
self.utxo_table.clear();
824-
self.address_utxos.clear();
825-
self.fungible_token_data.clear();
826-
self.nft_token_issuances.clear();
827-
self.orders_table.clear();
828-
829-
self.initialize_storage(chain_config)
797+
let mut new_storage = Self::new(chain_config);
798+
std::mem::swap(self, &mut new_storage);
799+
Ok(())
830800
}
831801

832802
fn del_address_balance_above_height(
@@ -971,13 +941,22 @@ impl ApiServerInMemoryStorage {
971941
block_height: BlockHeight,
972942
block: &BlockWithExtraData,
973943
) -> Result<(), ApiServerStorageError> {
944+
let previously_stored_height =
945+
self.block_aux_data_table.get(&block_id).map(|data| data.block_height());
946+
947+
let aux_data = BlockAuxData::new(block_id.into(), block_height, block.block.timestamp());
974948
self.block_table.insert(block_id, block.clone());
975-
self.block_aux_data_table.insert(
976-
block_id,
977-
BlockAuxData::new(block_id.into(), block_height, block.block.timestamp()),
978-
);
949+
self.block_aux_data_table.insert(block_id, aux_data);
979950
self.main_chain_blocks_table.insert(block_height, block_id);
980-
self.best_block = BlockAuxData::new(block_id.into(), block_height, block.block.timestamp());
951+
952+
// Handle a degenerate case when the block is stored several times using different heights
953+
// (to be consistent with the postgres implementation).
954+
if let Some(previously_stored_height) = previously_stored_height {
955+
if previously_stored_height != block_height {
956+
self.main_chain_blocks_table.remove(&previously_stored_height);
957+
}
958+
}
959+
981960
Ok(())
982961
}
983962

api-server/api-server-common/src/storage/impls/postgres/queries.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,11 +1134,19 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> {
11341134
let height = Self::block_height_to_postgres_friendly(block_height);
11351135
let timestamp = Self::block_time_to_postgres_friendly(block.block.timestamp())?;
11361136

1137+
// Note: we need to update the block height on conflict, because the block may have been
1138+
// added as a stale one in the past, in which case its block_height hasn't been set.
1139+
// Regarding the rest of the columns, updating them on conflict makes no sense from the
1140+
// consensus point of view, because the data, if calculated correctly, can never change,
1141+
// provided that the block id stays the same. However, this is a low-level db call,
1142+
// so it'd be better if the caller decides what data can change and what can't.
1143+
// Also, this way it's consistent with the in-memory implementation.
11371144
self.tx
11381145
.execute(
1139-
"INSERT INTO ml.blocks (block_id, block_height, block_timestamp, block_data) VALUES ($1, $2, $3, $4)
1146+
"INSERT INTO ml.blocks (block_id, block_height, block_timestamp, block_data)
1147+
VALUES ($1, $2, $3, $4)
11401148
ON CONFLICT (block_id) DO UPDATE
1141-
SET block_data = $4, block_height = $2;",
1149+
SET block_height = $2, block_timestamp = $3, block_data = $4;",
11421150
&[&block_id.encode(), &height, &timestamp, &block.encode()],
11431151
)
11441152
.await
@@ -2705,6 +2713,7 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> {
27052713
) -> Result<(), ApiServerStorageError> {
27062714
logging::log::debug!("Inserting block aux data with block_id {}", block_id);
27072715

2716+
// Note: we update the data on block id conflict, the reasons are the same as in set_mainchain_block.
27082717
self.tx
27092718
.execute(
27102719
"INSERT INTO ml.block_aux_data (block_id, aux_data) VALUES ($1, $2)

api-server/api-server-common/src/storage/storage_api/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,11 @@ pub trait ApiServerStorageRead: Sync {
611611
block_id: Id<Block>,
612612
) -> Result<Option<BlockAuxData>, ApiServerStorageError>;
613613

614+
// Note:
615+
// 1) The input time range is inclusive on both ends.
616+
// 2) The returned heights won't include the genesis height normally.
617+
// However, if there are no blocks on the mainchain in the specified time range (except genesis),
618+
// the function will return (0, 0).
614619
async fn get_block_range_from_time_range(
615620
&self,
616621
time_range: (BlockTimestamp, BlockTimestamp),

api-server/stack-test-suite/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ chainstate = { path = "../../chainstate" }
1313
chainstate-test-framework = { path = "../../chainstate/test-framework" }
1414
common = { path = "../../common" }
1515
crypto = { path = "../../crypto" }
16+
logging = { path = "../../logging" }
1617
randomness = { path = "../../randomness" }
1718
serialization = { path = "../../serialization" }
1819
test-utils = { path = "../../test-utils" }
@@ -23,10 +24,11 @@ mempool = { path = "../../mempool" }
2324

2425
async-trait.workspace = true
2526
axum.workspace = true
27+
ctor.workspace = true
2628
hex.workspace = true
2729
libtest-mimic.workspace = true
2830
reqwest = "0.11"
31+
rstest.workspace = true
2932
serde.workspace = true
3033
serde_json.workspace = true
3134
tokio = { workspace = true, features = ["full"] }
32-
rstest.workspace = true

api-server/stack-test-suite/tests/v2/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ use std::{
8383
};
8484
use test_utils::random::{make_seedable_rng, Rng, Seed};
8585

86+
#[ctor::ctor]
87+
fn init() {
88+
logging::init_logging();
89+
}
90+
8691
#[tokio::test]
8792
async fn chain_genesis() {
8893
let url = "/api/v2/chain/genesis";

api-server/stack-test-suite/tests/v2/transaction.rs

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -191,20 +191,20 @@ async fn multiple_tx_in_same_block(#[case] seed: Seed) {
191191
let transaction = signed_tx2.transaction();
192192

193193
let expected_transaction = json!({
194-
"block_id": block_id.to_hash().encode_hex::<String>(),
195-
"timestamp": block.timestamp().to_string(),
196-
"confirmations": BlockHeight::new(0).to_string(),
197-
"version_byte": transaction.version_byte(),
198-
"is_replaceable": transaction.is_replaceable(),
199-
"flags": transaction.flags(),
200-
"inputs": transaction.inputs().iter().zip(utxos).map(|(inp, utxo)| json!({
201-
"input": tx_input_to_json(inp, &TokenDecimals::Single(None), &chain_config),
202-
"utxo": utxo.as_ref().map(|txo| txoutput_to_json(txo, &chain_config, &TokenDecimals::Single(None))),
194+
"block_id": block_id.to_hash().encode_hex::<String>(),
195+
"timestamp": block.timestamp().to_string(),
196+
"confirmations": BlockHeight::new(0).to_string(),
197+
"version_byte": transaction.version_byte(),
198+
"is_replaceable": transaction.is_replaceable(),
199+
"flags": transaction.flags(),
200+
"inputs": transaction.inputs().iter().zip(utxos).map(|(inp, utxo)| json!({
201+
"input": tx_input_to_json(inp, &TokenDecimals::Single(None), &chain_config),
202+
"utxo": utxo.as_ref().map(|txo| txoutput_to_json(txo, &chain_config, &TokenDecimals::Single(None))),
203203
})).collect::<Vec<_>>(),
204-
"outputs": transaction.outputs()
205-
.iter()
206-
.map(|out| txoutput_to_json(out, &chain_config, &TokenDecimals::Single(None)))
207-
.collect::<Vec<_>>(),
204+
"outputs": transaction.outputs()
205+
.iter()
206+
.map(|out| txoutput_to_json(out, &chain_config, &TokenDecimals::Single(None)))
207+
.collect::<Vec<_>>(),
208208
});
209209

210210
_ = tx.send((
@@ -337,20 +337,20 @@ async fn ok(#[case] seed: Seed) {
337337
});
338338

339339
let expected_transaction = json!({
340-
"block_id": block_id.to_hash().encode_hex::<String>(),
341-
"timestamp": block.timestamp().to_string(),
342-
"confirmations": BlockHeight::new((n_blocks - block_height) as u64).to_string(),
343-
"version_byte": transaction.version_byte(),
344-
"is_replaceable": transaction.is_replaceable(),
345-
"flags": transaction.flags(),
346-
"inputs": transaction.inputs().iter().zip(utxos).map(|(inp, utxo)| json!({
347-
"input": tx_input_to_json(inp, &TokenDecimals::Single(None), &chain_config),
348-
"utxo": utxo.as_ref().map(|txo| txoutput_to_json(txo, &chain_config, &TokenDecimals::Single(None))),
340+
"block_id": block_id.to_hash().encode_hex::<String>(),
341+
"timestamp": block.timestamp().to_string(),
342+
"confirmations": BlockHeight::new((n_blocks - block_height) as u64).to_string(),
343+
"version_byte": transaction.version_byte(),
344+
"is_replaceable": transaction.is_replaceable(),
345+
"flags": transaction.flags(),
346+
"inputs": transaction.inputs().iter().zip(utxos).map(|(inp, utxo)| json!({
347+
"input": tx_input_to_json(inp, &TokenDecimals::Single(None), &chain_config),
348+
"utxo": utxo.as_ref().map(|txo| txoutput_to_json(txo, &chain_config, &TokenDecimals::Single(None))),
349349
})).collect::<Vec<_>>(),
350-
"outputs": transaction.outputs()
351-
.iter()
352-
.map(|out| txoutput_to_json(out, &chain_config, &TokenDecimals::Single(None)))
353-
.collect::<Vec<_>>(),
350+
"outputs": transaction.outputs()
351+
.iter()
352+
.map(|out| txoutput_to_json(out, &chain_config, &TokenDecimals::Single(None)))
353+
.collect::<Vec<_>>(),
354354
});
355355

356356
_ = tx.send((

api-server/stack-test-suite/tests/v2/transactions.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@
1313
// See the License for the specific language governing permissions and
1414
// limitations under the License.
1515

16+
use serde_json::Value;
17+
1618
use api_server_common::storage::storage_api::{
1719
block_aux_data::BlockAuxData, TransactionInfo, TxAdditionalInfo,
1820
};
1921
use api_web_server::api::json_helpers::to_tx_json_with_block_info;
20-
use serde_json::Value;
2122

2223
use super::*;
2324

0 commit comments

Comments
 (0)