Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
f50435e
add block_proof column and DB queries for storing block proofs
sergerad Mar 1, 2026
9101df8
decouple proving from apply_block and persist proving inputs
sergerad Mar 1, 2026
0ef989a
add concurrent proof scheduler with FuturesOrdered for FIFO completion
sergerad Mar 1, 2026
34f0cdc
Simplify prove fns
sergerad Mar 1, 2026
7a5022a
Simplify retry fn
sergerad Mar 2, 2026
5062cd0
Fix data dir issue
sergerad Mar 2, 2026
ecc067e
add finality parameter to SyncChainMmr endpoint
sergerad Mar 2, 2026
f116fa3
RM flake
sergerad Mar 2, 2026
866b324
Fix lint
sergerad Mar 2, 2026
1f2919b
Undo fmt
sergerad Mar 2, 2026
14e16f4
Wrap up signed block todo
sergerad Mar 2, 2026
1b21962
Pass blockproofrequest down
sergerad Mar 2, 2026
671630a
Lint
sergerad Mar 2, 2026
b6f3d3c
Fix stress tests
sergerad Mar 2, 2026
6856585
Changelog
sergerad Mar 2, 2026
3b0ba10
Fix proving inputs
sergerad Mar 2, 2026
98c0aa6
Handle docstring
sergerad Mar 2, 2026
1189d46
Update genesis comment
sergerad Mar 2, 2026
a2b5952
RM arc clone
sergerad Mar 2, 2026
8a65851
load_proving_inputs comments
sergerad Mar 2, 2026
d355dc5
Comments
sergerad Mar 2, 2026
769d2bf
refactor errors and retries
sergerad Mar 2, 2026
633f761
Merge branch 'next' of github.com:0xMiden/miden-node into sergerad-de…
sergerad Mar 2, 2026
549d808
Tidy up future results
sergerad Mar 2, 2026
dab79e4
Comments
sergerad Mar 2, 2026
98d56fa
Merge branch 'next' of github.com:0xMiden/miden-node into sergerad-de…
sergerad Mar 3, 2026
c08b657
Fix compile
sergerad Mar 3, 2026
258bafc
Rm dead code fields
sergerad Mar 3, 2026
bd8dad7
impl conv::SqlTypeConv for BlockProof
sergerad Mar 3, 2026
2b44192
Add index update query
sergerad Mar 3, 2026
92878d8
Bump timeout
sergerad Mar 3, 2026
7d5ed4c
Update notify
sergerad Mar 3, 2026
2c5a7d7
Specify proving block batch size
sergerad Mar 3, 2026
8b803f7
static lifetime
sergerad Mar 3, 2026
2ca641f
backticks
sergerad Mar 3, 2026
6e52650
replace match
sergerad Mar 3, 2026
295f9d8
Store proofs to file
sergerad Mar 3, 2026
5bfba05
update select proving inputs return value
sergerad Mar 3, 2026
37cb516
RM pub crate
sergerad Mar 3, 2026
ec89137
Fix changelog
sergerad Mar 3, 2026
bc0bd54
Changelog
sergerad Mar 4, 2026
c8a76d4
finality unspecified
sergerad Mar 4, 2026
506ea80
unspecified comment
sergerad Mar 4, 2026
97b320b
More comments
sergerad Mar 4, 2026
820261d
source not from
sergerad Mar 4, 2026
b3f0238
arc clone
sergerad Mar 4, 2026
228d38b
rename proof scheduler handle
sergerad Mar 4, 2026
d272347
refactor proof concurrency
sergerad Mar 4, 2026
4c216c2
Merge branch 'next' of github.com:0xMiden/miden-node into sergerad-de…
sergerad Mar 4, 2026
9b2f3c8
lint
sergerad Mar 4, 2026
03b0415
Merge branch 'next' of github.com:0xMiden/miden-node into sergerad-de…
sergerad Mar 4, 2026
da22438
RM unused query and index
sergerad Mar 4, 2026
359a056
Merge branch 'next' of github.com:0xMiden/miden-node into sergerad-de…
sergerad Mar 8, 2026
0e3145f
Move retry logic to prove_and_save
sergerad Mar 8, 2026
75cca6e
PendingJoinSet
sergerad Mar 8, 2026
20c1720
Simplify scheduling logic
sergerad Mar 8, 2026
3bbb943
Rename var
sergerad Mar 8, 2026
433085b
instrument field name
sergerad Mar 9, 2026
aa4f32a
parameterize max concurrent proofs
sergerad Mar 9, 2026
ee6b74a
Merge branch 'next' of github.com:0xMiden/miden-node into sergerad-de…
sergerad Mar 10, 2026
8a70c04
joinset type specific
sergerad Mar 10, 2026
4238cf1
flatten error
sergerad Mar 10, 2026
2720950
unwrap or
sergerad Mar 10, 2026
e0d9b55
retry loop
sergerad Mar 10, 2026
4e71764
instrument refactor
sergerad Mar 10, 2026
1cb0546
Fix info block num fields
sergerad Mar 10, 2026
3bfce8c
Merge branch 'next' of github.com:0xMiden/miden-node into sergerad-de…
sergerad Mar 12, 2026
3e1e3a9
rm is_proven and wipe proving_inputs on proven
sergerad Mar 12, 2026
28f359f
Merge branch 'next' of github.com:0xMiden/miden-node into sergerad-de…
sergerad Mar 12, 2026
1b0712d
nonzerousize
sergerad Mar 12, 2026
8292df7
Update select block num comment
sergerad Mar 12, 2026
d8bb994
anyhow context
sergerad Mar 12, 2026
3044cea
another anyhow context
sergerad Mar 12, 2026
b8aa91c
Fix fields
sergerad Mar 12, 2026
905a298
log loop errs
sergerad Mar 12, 2026
ca54ff2
assert_matches
sergerad Mar 12, 2026
a605383
std io err
sergerad Mar 12, 2026
7c8fba7
Simplify schedule logic
sergerad Mar 12, 2026
1dd667a
mark proven in prove_and_save
sergerad Mar 12, 2026
e539861
saturating sub
sergerad Mar 16, 2026
3acd808
Merge branch 'next' of github.com:0xMiden/miden-node into sergerad-de…
sergerad Mar 16, 2026
346bf15
rm too many lines
sergerad Mar 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- Introduce `SyncChainMmr` RPC endpoint to sync chain MMR deltas within specified block ranges ([#1591](https://github.com/0xMiden/node/issues/1591)).
- Fixed `TransactionHeader` serialization for row insertion on database & fixed transaction cursor on retrievals ([#1701](https://github.com/0xMiden/node/issues/1701)).
- Added KMS signing support in validator ([#1677](https://github.com/0xMiden/node/pull/1677)).
- Added finality field for `SyncChainMmr` requests ([#1725](https://github.com/0xMiden/miden-node/pull/1725)).
- Added per-IP gRPC rate limiting across services as well as global concurrent connection limit ([#1746](https://github.com/0xMiden/node/issues/1746)).

### Changes
Expand Down
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 14 additions & 1 deletion bin/node/src/commands/bundled.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use std::collections::HashMap;
use std::num::NonZeroUsize;
use std::path::PathBuf;

use anyhow::Context;
use miden_node_block_producer::BlockProducer;
use miden_node_rpc::Rpc;
use miden_node_store::Store;
use miden_node_store::{DEFAULT_MAX_CONCURRENT_PROOFS, Store};
use miden_node_utils::clap::{GrpcOptionsExternal, StorageOptions};
use miden_node_utils::grpc::UrlExt;
use miden_node_validator::{Validator, ValidatorSigner};
Expand Down Expand Up @@ -82,6 +83,14 @@ pub enum BundledCommand {
#[arg(long = "enable-otel", default_value_t = false, env = ENV_ENABLE_OTEL, value_name = "BOOL")]
enable_otel: bool,

/// Maximum number of concurrent block proofs to be scheduled.
#[arg(
long = "max-concurrent-proofs",
default_value_t = DEFAULT_MAX_CONCURRENT_PROOFS,
value_name = "NUM"
)]
max_concurrent_proofs: NonZeroUsize,

#[command(flatten)]
grpc_options: GrpcOptionsExternal,

Expand Down Expand Up @@ -124,6 +133,7 @@ impl BundledCommand {
validator,
enable_otel: _,
grpc_options,
max_concurrent_proofs,
storage_options,
} => {
Self::start(
Expand All @@ -134,6 +144,7 @@ impl BundledCommand {
ntx_builder,
validator,
grpc_options,
max_concurrent_proofs,
storage_options,
)
.await
Expand All @@ -150,6 +161,7 @@ impl BundledCommand {
ntx_builder: NtxBuilderConfig,
validator: BundledValidatorConfig,
grpc_options: GrpcOptionsExternal,
max_concurrent_proofs: NonZeroUsize,
storage_options: StorageOptions,
) -> anyhow::Result<()> {
// Start listening on all gRPC urls so that inter-component connections can be created
Expand Down Expand Up @@ -208,6 +220,7 @@ impl BundledCommand {
data_directory: data_directory_clone,
block_prover_url,
grpc_options: grpc_options.into(),
max_concurrent_proofs,
storage_options,
}
.serve()
Expand Down
15 changes: 14 additions & 1 deletion bin/node/src/commands/store.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use std::num::NonZeroUsize;
use std::path::{Path, PathBuf};

use anyhow::Context;
use miden_node_store::Store;
use miden_node_store::genesis::GenesisBlock;
use miden_node_store::{DEFAULT_MAX_CONCURRENT_PROOFS, Store};
use miden_node_utils::clap::{GrpcOptionsInternal, StorageOptions};
use miden_node_utils::fs::ensure_empty_directory;
use miden_node_utils::grpc::UrlExt;
Expand Down Expand Up @@ -65,6 +66,13 @@ pub enum StoreCommand {
#[arg(long = "enable-otel", default_value_t = false, env = ENV_ENABLE_OTEL, value_name = "BOOL")]
enable_otel: bool,

/// Maximum number of concurrent block proofs to be scheduled.
#[arg(
long = "max-concurrent-proofs",
default_value_t = DEFAULT_MAX_CONCURRENT_PROOFS,
value_name = "NUM"
)]
max_concurrent_proofs: NonZeroUsize,
#[command(flatten)]
grpc_options: GrpcOptionsInternal,

Expand All @@ -89,6 +97,7 @@ impl StoreCommand {
data_directory,
enable_otel: _,
grpc_options,
max_concurrent_proofs,
storage_options,
} => {
Self::start(
Expand All @@ -98,6 +107,7 @@ impl StoreCommand {
block_prover_url,
data_directory,
grpc_options,
max_concurrent_proofs,
storage_options,
)
.await
Expand All @@ -113,13 +123,15 @@ impl StoreCommand {
}
}

#[expect(clippy::too_many_arguments)]
async fn start(
rpc_url: Url,
ntx_builder_url: Url,
block_producer_url: Url,
block_prover_url: Option<Url>,
data_directory: PathBuf,
grpc_options: GrpcOptionsInternal,
max_concurrent_proofs: NonZeroUsize,
storage_options: StorageOptions,
) -> anyhow::Result<()> {
let rpc_listener = rpc_url
Expand Down Expand Up @@ -150,6 +162,7 @@ impl StoreCommand {
block_producer_listener,
data_directory,
grpc_options,
max_concurrent_proofs,
storage_options,
}
.serve()
Expand Down
12 changes: 9 additions & 3 deletions bin/stress-test/src/seeding/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ pub async fn seed_store(
let faucet = create_faucet();
let fee_params = FeeParameters::new(faucet.id(), 0).unwrap();
let signer = EcdsaSecretKey::new();
let genesis_state = GenesisState::new(vec![faucet.clone()], fee_params, 1, 1, signer);
let genesis_state = GenesisState::new(vec![faucet.clone()], fee_params, 1, 1, signer.clone());
let genesis_block = genesis_state
.clone()
.into_block()
Expand All @@ -118,6 +118,7 @@ pub async fn seed_store(
&store_client,
data_directory,
accounts_filepath,
&signer,
)
.await;

Expand All @@ -129,6 +130,7 @@ pub async fn seed_store(
///
/// The first transaction in each batch sends assets from the faucet to 255 accounts.
/// The rest of the transactions consume the notes created by the faucet in the previous block.
#[expect(clippy::too_many_arguments)]
async fn generate_blocks(
num_accounts: usize,
public_accounts_percentage: u8,
Expand All @@ -137,6 +139,7 @@ async fn generate_blocks(
store_client: &StoreClient,
data_directory: DataDirectory,
accounts_filepath: PathBuf,
signer: &EcdsaSecretKey,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this always an Ecdsa key type?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. As opposed to what?

) -> SeedingMetrics {
// Each block is composed of [`BATCHES_PER_BLOCK`] batches, and each batch is composed of
// [`TRANSACTIONS_PER_BATCH`] txs. The first note of the block is always a send assets tx
Expand Down Expand Up @@ -215,7 +218,8 @@ async fn generate_blocks(
let block_inputs = get_block_inputs(store_client, &batches, &mut metrics).await;

// update blocks
prev_block_header = apply_block(batches, block_inputs, store_client, &mut metrics).await;
prev_block_header =
apply_block(batches, block_inputs, store_client, &mut metrics, signer).await;
if current_anchor_header.block_epoch() != prev_block_header.block_epoch() {
current_anchor_header = prev_block_header.clone();
}
Expand Down Expand Up @@ -250,11 +254,12 @@ async fn apply_block(
block_inputs: BlockInputs,
store_client: &StoreClient,
metrics: &mut SeedingMetrics,
signer: &EcdsaSecretKey,
) -> BlockHeader {
let proposed_block = ProposedBlock::new(block_inputs, batches).unwrap();
let (header, body) = proposed_block.clone().into_header_and_body().unwrap();
let block_size: usize = header.to_bytes().len() + body.to_bytes().len();
let signature = EcdsaSecretKey::new().sign(header.commitment());
let signature = signer.sign(header.commitment());
// SAFETY: The header, body, and signature are known to correspond to each other.
let signed_block = SignedBlock::new_unchecked(header, body, signature);
let ordered_batches = proposed_block.batches().clone();
Expand Down Expand Up @@ -555,6 +560,7 @@ pub async fn start_store(
block_producer_listener,
data_directory: dir,
grpc_options: GrpcOptionsInternal::bench(),
max_concurrent_proofs: miden_node_store::DEFAULT_MAX_CONCURRENT_PROOFS,
storage_options: StorageOptions::bench(),
}
.serve()
Expand Down
1 change: 1 addition & 0 deletions bin/stress-test/src/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,7 @@ async fn sync_chain_mmr(
) -> SyncChainMmrRun {
let sync_request = proto::rpc::SyncChainMmrRequest {
block_range: Some(proto::rpc::BlockRange { block_from, block_to: Some(block_to) }),
finality: proto::rpc::Finality::Committed.into(),
};

let start = Instant::now();
Expand Down
3 changes: 2 additions & 1 deletion crates/block-producer/src/server/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::num::NonZeroUsize;
use std::time::Duration;

use miden_node_proto::generated::block_producer::api_client as block_producer_client;
use miden_node_store::{GenesisState, Store};
use miden_node_store::{DEFAULT_MAX_CONCURRENT_PROOFS, GenesisState, Store};
use miden_node_utils::clap::{GrpcOptionsInternal, StorageOptions};
use miden_node_utils::fee::test_fee_params;
use miden_node_validator::{Validator, ValidatorSigner};
Expand Down Expand Up @@ -159,6 +159,7 @@ async fn start_store(
block_prover_url: None,
data_directory: dir,
grpc_options: GrpcOptionsInternal::bench(),
max_concurrent_proofs: DEFAULT_MAX_CONCURRENT_PROOFS,
storage_options: StorageOptions::bench(),
}
.serve()
Expand Down
5 changes: 4 additions & 1 deletion crates/rpc/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use http::{HeaderMap, HeaderValue};
use miden_node_proto::clients::{Builder, GrpcClient, Interceptor, RpcClient};
use miden_node_proto::generated::rpc::api_client::ApiClient as ProtoClient;
use miden_node_proto::generated::{self as proto};
use miden_node_store::Store;
use miden_node_store::genesis::config::GenesisConfig;
use miden_node_store::{DEFAULT_MAX_CONCURRENT_PROOFS, Store};
use miden_node_utils::clap::{GrpcOptionsExternal, GrpcOptionsInternal, StorageOptions};
use miden_node_utils::fee::test_fee;
use miden_node_utils::limiter::{
Expand Down Expand Up @@ -480,6 +480,7 @@ async fn start_store(store_listener: TcpListener) -> (Runtime, TempDir, Word, So
block_producer_listener,
data_directory: dir,
grpc_options: GrpcOptionsInternal::test(),
max_concurrent_proofs: DEFAULT_MAX_CONCURRENT_PROOFS,
storage_options: StorageOptions::default(),
}
.serve()
Expand Down Expand Up @@ -523,6 +524,7 @@ async fn restart_store(store_addr: SocketAddr, data_directory: &std::path::Path)
block_producer_listener,
data_directory: dir,
grpc_options: GrpcOptionsInternal::test(),
max_concurrent_proofs: DEFAULT_MAX_CONCURRENT_PROOFS,
storage_options: StorageOptions::default(),
}
.serve()
Expand Down Expand Up @@ -599,6 +601,7 @@ async fn sync_chain_mmr_returns_delta() {

let request = proto::rpc::SyncChainMmrRequest {
block_range: Some(proto::rpc::BlockRange { block_from: 0, block_to: None }),
finality: proto::rpc::Finality::Committed.into(),
};
let response = rpc_client.sync_chain_mmr(request).await.expect("sync_chain_mmr should succeed");
let response = response.into_inner();
Expand Down
1 change: 0 additions & 1 deletion crates/store/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ deadpool-diesel = { features = ["sqlite"], version = "0.6" }
diesel = { features = ["numeric", "sqlite"], version = "2.3" }
diesel_migrations = { features = ["sqlite"], version = "2.3" }
fs-err = { workspace = true }
futures = { workspace = true }
hex = { version = "0.4" }
indexmap = { workspace = true }
libsqlite3-sys = { workspace = true }
Expand Down
64 changes: 46 additions & 18 deletions crates/store/src/blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,7 @@ impl BlockStore {
Ok(Self { store_dir })
}

pub async fn load_block(
&self,
block_num: BlockNumber,
) -> Result<Option<Vec<u8>>, std::io::Error> {
pub async fn load_block(&self, block_num: BlockNumber) -> std::io::Result<Option<Vec<u8>>> {
match tokio::fs::read(self.block_path(block_num)).await {
Ok(data) => Ok(Some(data)),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
Expand All @@ -81,11 +78,7 @@ impl BlockStore {
err,
fields(block_size = data.len())
)]
pub async fn save_block(
&self,
block_num: BlockNumber,
data: &[u8],
) -> Result<(), std::io::Error> {
pub async fn save_block(&self, block_num: BlockNumber, data: &[u8]) -> std::io::Result<()> {
let (epoch_path, block_path) = self.epoch_block_path(block_num)?;
if !epoch_path.exists() {
tokio::fs::create_dir_all(epoch_path).await?;
Expand All @@ -94,11 +87,7 @@ impl BlockStore {
tokio::fs::write(block_path, data).await
}

pub fn save_block_blocking(
&self,
block_num: BlockNumber,
data: &[u8],
) -> Result<(), std::io::Error> {
pub fn save_block_blocking(&self, block_num: BlockNumber, data: &[u8]) -> std::io::Result<()> {
let (epoch_path, block_path) = self.epoch_block_path(block_num)?;
if !epoch_path.exists() {
fs_err::create_dir_all(epoch_path)?;
Expand All @@ -107,6 +96,34 @@ impl BlockStore {
fs_err::write(block_path, data)
}

// PROOF STORAGE
// --------------------------------------------------------------------------------------------

#[expect(dead_code)]
pub async fn load_proof(&self, block_num: BlockNumber) -> std::io::Result<Option<Vec<u8>>> {
match tokio::fs::read(self.proof_path(block_num)).await {
Ok(data) => Ok(Some(data)),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
}

#[instrument(
target = COMPONENT,
name = "store.block_store.save_proof",
skip(self, data),
err,
fields(proof_size = data.len())
)]
pub async fn save_proof(&self, block_num: BlockNumber, data: &[u8]) -> std::io::Result<()> {
let (epoch_path, proof_path) = self.epoch_proof_path(block_num)?;
if !epoch_path.exists() {
tokio::fs::create_dir_all(epoch_path).await?;
}

tokio::fs::write(proof_path, data).await
}

// HELPER FUNCTIONS
// --------------------------------------------------------------------------------------------

Expand All @@ -117,16 +134,27 @@ impl BlockStore {
epoch_dir.join(format!("block_{block_num:08x}.dat"))
}

fn epoch_block_path(
&self,
block_num: BlockNumber,
) -> Result<(PathBuf, PathBuf), std::io::Error> {
fn proof_path(&self, block_num: BlockNumber) -> PathBuf {
let block_num = block_num.as_u32();
let epoch = block_num >> 16;
let epoch_dir = self.store_dir.join(format!("{epoch:04x}"));
epoch_dir.join(format!("proof_{block_num:08x}.dat"))
}

fn epoch_block_path(&self, block_num: BlockNumber) -> std::io::Result<(PathBuf, PathBuf)> {
let block_path = self.block_path(block_num);
let epoch_path = block_path.parent().ok_or(std::io::Error::from(ErrorKind::NotFound))?;

Ok((epoch_path.to_path_buf(), block_path))
}

fn epoch_proof_path(&self, block_num: BlockNumber) -> std::io::Result<(PathBuf, PathBuf)> {
let proof_path = self.proof_path(block_num);
let epoch_path = proof_path.parent().ok_or(std::io::Error::from(ErrorKind::NotFound))?;

Ok((epoch_path.to_path_buf(), proof_path))
}

pub fn display(&self) -> std::path::Display<'_> {
self.store_dir.display()
}
Expand Down
11 changes: 7 additions & 4 deletions crates/store/src/db/migrations/2025062000000_setup/up.sql
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
CREATE TABLE block_headers (
block_num INTEGER NOT NULL,
block_header BLOB NOT NULL,
signature BLOB NOT NULL,
commitment BLOB NOT NULL,
block_num INTEGER NOT NULL,
block_header BLOB NOT NULL,
signature BLOB NOT NULL,
commitment BLOB NOT NULL,
proving_inputs BLOB, -- Serialized BlockProofRequest needed for deferred proving. NULL if it has been proven or never proven (genesis block).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: leave a TODO that the size might become a problem in the future


PRIMARY KEY (block_num),
CONSTRAINT block_header_block_num_is_u32 CHECK (block_num BETWEEN 0 AND 0xFFFFFFFF)
);

CREATE INDEX block_headers_proven_desc ON block_headers(block_num DESC) WHERE proving_inputs IS NULL;

CREATE TABLE account_codes (
code_commitment BLOB NOT NULL,
code BLOB NOT NULL,
Expand Down
Loading
Loading