Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,14 @@
* [BREAKING][rust] `Client::remove_address` and `Store::remove_address` return `bool` instead of `()`, reporting whether the address was tracked. When it wasn't, `Client::remove_address` now leaves the derived note tag in place instead of running its cleanup.
* [BREAKING][type][rust] Added the `TransactionRequestError::ForeignProcedureInputsTooLong` variant ([#2187](https://github.com/0xMiden/rust-sdk/pull/2187)).
* [BREAKING][behavior][store] The `output_notes` table gained a nullable `script_root` column referencing `notes_scripts`, and note scripts are fully normalized: an output note's state blob no longer embeds the script, which is stored once in `notes_scripts` and joined back in on read. All tables are now `STRICT`, and the `tags` table stores its rows keyed by a `(tag, source)` primary key (`WITHOUT ROWID`) instead of carrying a separate unique index. This changes the schema fingerprint, so opening a database created before this change fails with `SchemaHashMismatch` and existing stores must be recreated.
* [BREAKING][removal][test] Loose helper functions in `miden_client::testing::common` are now methods on `TestClient`. `TestClient::keystore()` exposes the client's keystore, so `ClientConfig::into_client` and `into_unsynced_client` return just the `TestClient` instead of a client/keystore pair ([#2477](https://github.com/0xMiden/rust-sdk/issues/2477)).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we should include the PR link here

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Indeed, done in f4cb0fb

* [BREAKING][param][store] Settings are split into a client-owned and a user-owned scope. `Store::set_setting`, `Store::get_setting`, `Store::remove_setting`, `Store::list_setting_keys` and `Store::apply_settings_mutations` take a `SettingScope` as their first argument, and implementations must persist it so the same key name in each scope addresses a different entry. The `Client` methods are unchanged and always operate on `SettingScope::User` ([#2456](https://github.com/0xMiden/rust-sdk/pull/2456)).
* [BREAKING][behavior][rust] `Client::list_setting_keys` returns only the user's keys. The client's own entries, such as the note transport cursor and the cached RPC limits, no longer appear in the listing and can no longer be read or overwritten through the `Client` settings API ([#2456](https://github.com/0xMiden/rust-sdk/pull/2456)).
* [BREAKING][behavior][store] The SQLite `settings` table now carries a `scope` column with `(scope, name)` as its primary key. This changes the schema fingerprint, so opening a database created before this change fails with `SchemaDrift` and existing stores must be recreated ([#2456](https://github.com/0xMiden/rust-sdk/pull/2456)).

### Fixes

* [FIX][test] The integration tests now run against a fee-charging chain. The testing node's genesis charges a fee by default (`MIDEN_VERIFICATION_BASE_FEE`, default `500`), generates the native fee faucet itself so the accounts it deploys can be seeded with that asset, and pre-funds a pool of basic wallets the suite draws from via a new `--funders` argument (`MIDEN_FUNDER_ACCOUNTS_DIR`). Accounts created by the `miden_client::testing::common` helpers are funded and deployed automatically, and `miden_client::testing::fee::deploy_account` does the same for accounts a test builds itself. The AggLayer accounts are consequently always part of genesis (the `AGGLAYER_GENESIS` env var and the `start-node-agglayer` target are gone) and the AggLayer tests load them from `AGGLAYER_ACCOUNTS_DIR` ([#2446](https://github.com/0xMiden/rust-sdk/issues/2446)).
* [FIX][test] The integration tests now run against a fee-charging chain. The testing node's genesis charges a fee by default (`MIDEN_VERIFICATION_BASE_FEE`, default `500`), generates the native fee faucet itself so the accounts it deploys can be seeded with that asset, and pre-funds a pool of basic wallets the suite draws from via a new `--funders` argument (`MIDEN_FUNDER_ACCOUNTS_DIR`). A path naming no `.mac` file leaves the run without funders, which is all a fee-free genesis needs, since it declares no wallets for the path to hold. Accounts created by the `miden_client::testing::common` helpers are funded and deployed automatically, and `miden_client::testing::fee::deploy_account` does the same for accounts a test builds itself. The AggLayer accounts are consequently always part of genesis (the `AGGLAYER_GENESIS` env var and the `start-node-agglayer` target are gone) and the AggLayer tests load them from `AGGLAYER_ACCOUNTS_DIR` ([#2446](https://github.com/0xMiden/rust-sdk/issues/2446)).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: should this change be included in a separate entry instead of modifying the existing one?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Splitted in two in f4cb0fb

* [FIX][test] The AggLayer genesis accounts now declare their zero-fee policy in the faucet the generated genesis charges fees in, rather than the mock chain's. A network account settles its fee against the faucet its own policy names, so the bridge's and faucet's network transactions could not be executed and their notes sat unconsumed, failing `agglayer_update_ger` and `agglayer_note_reader_reads_consumed_notes` ([#2446](https://github.com/0xMiden/rust-sdk/issues/2446)).
* [FIX][rust] An empty auth argument no longer suppresses the fee conversion info the client attaches, and an account whose auth component reads that argument as a caller-chosen salt (`AuthMultisig`, `AuthGuardedMultisig`) is now rejected with `TransactionRequestError::FeeConversionInfoRequired` instead of failing inside the VM, unless the request declares a salt with `TransactionRequestBuilder::fee_conversion_salt`. Accounts carrying an auth component the client cannot classify are left alone rather than panicking ([#2446](https://github.com/0xMiden/rust-sdk/issues/2446)).
* [FIX][rust] Note screening on a fee-charging chain no longer reports notes with a custom script as unconsumable. Screening runs the full transaction kernel, auth procedure included, so `fee::pay_fee` aborted when a non-zero fee met auth args carrying no conversion info, and the note was dropped from the sync. Screening now commits the same native conversion info the execution path attaches, drawn from one shared source so the two cannot disagree. Standard notes were unaffected: their consumability is answered without executing anything ([#2446](https://github.com/0xMiden/rust-sdk/issues/2446)).
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ TEST_MIDEN_NOTE_TRANSPORT_URL?=http://127.0.0.1:57292

# Pre-funded wallets the integration tests draw transaction fees from, either one `.mac` file or a
# directory of them, written here by `start-test-node.sh`. Against a deployed network, point this at
# wallets funded out of band. Unused on a fee-free chain, where no account needs funding.
# wallets funded out of band. A path naming no `.mac` file, means no funders.
MIDEN_FUNDER_ACCOUNTS_DIR?=$(CURDIR)/data/funders

# Pre-deployed agglayer accounts the agglayer tests transact with, written here by
Expand Down Expand Up @@ -163,7 +163,7 @@ integration-test-dev: ## Run integration tests with debug assertions enabled via

.PHONY: integration-test-binary
integration-test-binary: ## Run the integration tests using the standalone binary (requires note transport service)
TEST_MIDEN_NOTE_TRANSPORT_URL=$(TEST_MIDEN_NOTE_TRANSPORT_URL) AGGLAYER_ACCOUNTS_DIR=$(AGGLAYER_ACCOUNTS_DIR) cargo run --package miden-client-integration-tests --release --locked -- --funders $(MIDEN_FUNDER_ACCOUNTS_DIR)
TEST_MIDEN_NOTE_TRANSPORT_URL=$(TEST_MIDEN_NOTE_TRANSPORT_URL) MIDEN_FUNDER_ACCOUNTS_DIR=$(MIDEN_FUNDER_ACCOUNTS_DIR) AGGLAYER_ACCOUNTS_DIR=$(AGGLAYER_ACCOUNTS_DIR) cargo run --package miden-client-integration-tests --release --locked

# --- Installing ----------------------------------------------------------------------------------

Expand Down
7 changes: 5 additions & 2 deletions bin/integration-tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,9 @@ MIDEN_VERIFICATION_BASE_FEE=0 make start-node-background
```

The suite never mints. It draws the native asset from pre-funded basic wallets named by `--funders`
(or `MIDEN_FUNDER_ACCOUNTS_DIR`), either one `.mac` file or a directory of them.
(or `MIDEN_FUNDER_ACCOUNTS_DIR`), either one `.mac` file or a directory of them. A path naming no
`.mac` file means no funders (useful for fee-free chains) so the Make targets can pass the
same path either way.

```bash
# Local node: its genesis pre-funds the wallets and start-test-node.sh writes them here.
Expand Down Expand Up @@ -242,7 +244,8 @@ chain before every payment, which is what makes sharing one between test process

### Environment variables

- `MIDEN_FUNDER_ACCOUNTS_DIR` - funder `.mac` file or directory, same as `--funders`
- `MIDEN_FUNDER_ACCOUNTS_DIR` - funder `.mac` file or directory, same as `--funders`. Unset, empty
or naming no `.mac` file leaves the run without funders
- `MIDEN_VERIFICATION_BASE_FEE` - genesis `verification_base_fee` for the testing node (default
`500`; `0` runs the node fee-free and declares no funder wallets)
- `MIDEN_NUM_FUNDER_WALLETS` - number of wallets a fee-charging genesis pre-funds (default `16`)
Expand Down
12 changes: 9 additions & 3 deletions bin/integration-tests/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,9 @@ struct Args {
note_transport_url: Option<String>,

/// Path to the pre-funded basic wallets the tests draw transaction fees from: either one
/// `.mac` account file or a directory of them.
#[arg(long, env = fee_funding::FUNDER_ACCOUNTS_ENV)]
/// `.mac` account file or a directory of them. Defaults to `MIDEN_FUNDER_ACCOUNTS_DIR`. A path
/// naming no such file leaves the run without funders.
#[arg(long)]
funders: Option<PathBuf>,

/// Enable verbose tracing output (info-level logs from tests and client).
Expand Down Expand Up @@ -236,12 +237,17 @@ impl TryFrom<Args> for BaseConfig {
}
};

let funders = args
.funders
.or_else(fee_funding::funders_path_from_env)
.filter(|path| !path.as_os_str().is_empty());

Ok(BaseConfig {
rpc_endpoint: endpoint,
timeout: timeout_ms,
prover_endpoint,
note_transport_endpoint,
funders: args.funders,
funders,
verbose: args.verbose,
})
}
Expand Down
80 changes: 26 additions & 54 deletions bin/integration-tests/src/tests/agglayer/agglayer_bridge_in_out.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,7 @@ use miden_agglayer::{
use miden_client::account::AccountType;
use miden_client::agglayer::{EthAddress, EthEmbeddedAccountId};
use miden_client::asset::{Asset, AssetAmount, FungibleAsset};
use miden_client::auth::RPO_FALCON_SCHEME_ID;
use miden_client::note::NoteAssets;
use miden_client::testing::common::{
insert_new_wallet,
wait_for_blocks,
wait_for_consumable_notes,
wait_for_tx,
};
use miden_client::transaction::TransactionRequestBuilder;
use miden_client_test_harness::ClientConfig;

Expand Down Expand Up @@ -84,26 +77,18 @@ pub async fn test_agglayer_bridge_in_out(client_config: ClientConfig) -> Result<
// SETUP: Destination account (always fresh) + faucet
// ============================================================================================

let (destination_account, ..) = insert_new_wallet(
&mut user.client,
AccountType::Public,
&user.keystore,
RPO_FALCON_SCHEME_ID,
)
.await?;
let destination_account = user.insert_wallet(AccountType::Public).await?;
println!("[bridge_in_out] Destination account created: {:?}", destination_account.id());

user.client.deploy_account(destination_account.id()).await?;
user.deploy_account(destination_account.id()).await?;
println!("[bridge_in_out] Destination account deployed on-chain");

// The agglayer faucet is an `AuthNetworkAccount`, so like the bridge it is pre-deployed and
// imported rather than created here.
let agglayer_faucet_id = agglayer_config.faucet_id();
println!("[bridge_in_out] Importing faucet: {agglayer_faucet_id}");
for pair in [&mut bridge_admin, &mut ger_manager, &mut user] {
agglayer_config
.import_account(agglayer_faucet_id, &mut pair.client, &pair.keystore)
.await?;
for client in [&mut bridge_admin, &mut ger_manager, &mut user] {
agglayer_config.import_account(agglayer_faucet_id, client).await?;
}

// Register the faucet on the (genesis-deployed, unconfigured) bridge via a CONFIG_AGG_BRIDGE
Expand All @@ -125,20 +110,17 @@ pub async fn test_agglayer_bridge_in_out(client_config: ClientConfig) -> Result<
},
bridge_admin_id,
bridge_id,
bridge_admin.client.rng(),
bridge_admin.rng(),
)?;
let config_output_tx =
TransactionRequestBuilder::new().own_output_notes(vec![config_note]).build()?;
let tx_id = bridge_admin
.client
.submit_new_transaction(bridge_admin_id, config_output_tx)
.await?;
wait_for_tx(&mut bridge_admin.client, tx_id).await?;
let tx_id = bridge_admin.submit_new_transaction(bridge_admin_id, config_output_tx).await?;
bridge_admin.wait_for_tx(tx_id).await?;
println!("[bridge_in_out] CONFIG_AGG_BRIDGE note submitted");

// Wait for the bridge to consume the config note as a network transaction. In CI the node's
// network transaction queue may be congested, so allow more blocks than the local minimum.
wait_for_blocks(&mut bridge_admin.client, 5).await;
bridge_admin.wait_for_blocks(5).await?;
println!("[bridge_in_out] Waited for bridge to consume CONFIG_AGG_BRIDGE note");

// ============================================================================================
Expand All @@ -162,19 +144,19 @@ pub async fn test_agglayer_bridge_in_out(client_config: ClientConfig) -> Result<
"foundry-generated destination must match our wallet's AccountId"
);

ger_manager.client.sync_state().await?;
ger_manager.sync_state().await?;

// Submit UPDATE_GER note: done by the ger manager
let update_ger_note =
UpdateGerNote::create(ger, ger_manager_id, bridge_id, ger_manager.client.rng())?;
UpdateGerNote::create(ger, ger_manager_id, bridge_id, ger_manager.rng())?;
let tx_request = TransactionRequestBuilder::new()
.own_output_notes(vec![update_ger_note])
.build()?;
let tx_id = ger_manager.client.submit_new_transaction(ger_manager_id, tx_request).await?;
wait_for_tx(&mut ger_manager.client, tx_id).await?;
let tx_id = ger_manager.submit_new_transaction(ger_manager_id, tx_request).await?;
ger_manager.wait_for_tx(tx_id).await?;
println!("[bridge_in_out] Round {round}: UPDATE_GER note submitted");

wait_for_blocks(&mut ger_manager.client, 5).await;
ger_manager.wait_for_blocks(5).await?;
println!("[bridge_in_out] Round {round}: waited for bridge to consume UPDATE_GER note");

// Submit CLAIM note: done by the user (or could also be a claim manager entity)
Expand All @@ -189,25 +171,20 @@ pub async fn test_agglayer_bridge_in_out(client_config: ClientConfig) -> Result<
leaf_data,
miden_claim_amount,
};
let claim_note = ClaimNote::create(
claim_inputs,
bridge_id,
destination_account.id(),
user.client.rng(),
)?;
let claim_note =
ClaimNote::create(claim_inputs, bridge_id, destination_account.id(), user.rng())?;
let tx_request =
TransactionRequestBuilder::new().own_output_notes(vec![claim_note]).build()?;
let tx_id =
user.client.submit_new_transaction(destination_account.id(), tx_request).await?;
wait_for_tx(&mut user.client, tx_id).await?;
let tx_id = user.submit_new_transaction(destination_account.id(), tx_request).await?;
user.wait_for_tx(tx_id).await?;
println!("[bridge_in_out] Round {round}: CLAIM note submitted");

// Wait for the P2ID note to arrive at the destination. The budget spans a network
// transaction round trip (the bridge consuming the CLAIM note and emitting the P2ID),
// which is far more load-sensitive than a directly submitted transaction, so it is set
// well above the ~5 blocks that round trip normally takes.
let consumable_notes =
wait_for_consumable_notes(&mut user.client, destination_account.id(), 120).await;
user.wait_for_consumable_notes(destination_account.id(), 120).await?;
println!(
"[bridge_in_out] Round {round}: found {} consumable notes for destination",
consumable_notes.len()
Expand All @@ -218,14 +195,12 @@ pub async fn test_agglayer_bridge_in_out(client_config: ClientConfig) -> Result<
.map(|(note, _)| note.try_into().map_err(|e| anyhow::anyhow!("{e}")))
.collect::<Result<Vec<_>, _>>()?;
let consume_tx = TransactionRequestBuilder::new().build_consume_notes(notes_to_consume)?;
let tx_id =
user.client.submit_new_transaction(destination_account.id(), consume_tx).await?;
wait_for_tx(&mut user.client, tx_id).await?;
let tx_id = user.submit_new_transaction(destination_account.id(), consume_tx).await?;
user.wait_for_tx(tx_id).await?;
println!("[bridge_in_out] Round {round}: destination consumed P2ID note");

user.client.sync_state().await?;
user.sync_state().await?;
let dest_balance = user
.client
.account_reader(destination_account.id())
.get_balance(agglayer_faucet_id)
.await?;
Expand All @@ -242,7 +217,7 @@ pub async fn test_agglayer_bridge_in_out(client_config: ClientConfig) -> Result<
// PHASE 2: BRIDGE-OUT
// ============================================================================================

user.client.sync_state().await?;
user.sync_state().await?;

let l1_destination_address =
EthAddress::from_hex(TEST_L1_DESTINATION).expect("valid L1 destination address");
Expand All @@ -260,22 +235,19 @@ pub async fn test_agglayer_bridge_in_out(client_config: ClientConfig) -> Result<
NoteAssets::new(vec![bridge_asset])?,
bridge_id,
destination_account.id(),
user.client.rng(),
user.rng(),
)?;
println!("[bridge_in_out] B2AGG note created with amount: {}", BRIDGE_OUT_AMOUNT);

let b2agg_output_tx =
TransactionRequestBuilder::new().own_output_notes(vec![b2agg_note]).build()?;
let tx_id = user
.client
.submit_new_transaction(destination_account.id(), b2agg_output_tx)
.await?;
wait_for_tx(&mut user.client, tx_id).await?;
let tx_id = user.submit_new_transaction(destination_account.id(), b2agg_output_tx).await?;
user.wait_for_tx(tx_id).await?;
println!("[bridge_in_out] B2AGG note submitted from destination account");

// Wait for bridge to consume the B2AGG note as network transaction.
// Allow extra blocks for CI where the node processes many concurrent network transactions.
wait_for_blocks(&mut user.client, 5).await;
user.wait_for_blocks(5).await?;
println!("[bridge_in_out] Waited for bridge to consume B2AGG note");

println!("[bridge_in_out] Test completed successfully");
Expand Down
Loading
Loading