-
Notifications
You must be signed in to change notification settings - Fork 103
Description
Version
testnet v0.13
Other packages versions
miden-client = { version = "0.13.2", features = ["testing", "tonic"] }
miden-client-sqlite-store = { version = "0.13.2" }
What happened?
Initializing an account component's storage slot with an .empty_map() on an arbitrary storage slot appears to not initialize the map / slot at all on the node.
it would appear client locally initializes it correctly and when the empty storage slot is then updated it succeeds on local client but on the second update of its state it throws an error.
This would perhaps indicating that node and client have different state.
What should have happened?
Updating storage slot that initialized with an empty map should always succeed
How can this be reproduced?
An example that creates an account with 2 storage slots.
- the first slot we initialize with a non-empty map
- the second slot we initialize with an empty map
- run a transaction script five times on both to change the storage map value on key 0 to a random u64.
Slot with in initialized map will successfully update itself five times, while the slot with an empty map will "succeed" on the first iteration (i suspect silently fail) and fail on second iteration.
Run cargo run to see it panic.
Cargo.toml:
[package]
name = "test_storagemap"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1.0"
miden-client = { version = "0.13.2", features = ["testing", "tonic"] }
miden-client-sqlite-store = { version = "0.13.2" }
rand = { version = "0.9" }
tokio = { version = "1.46", features = [
"fs",
"macros",
"net",
"rt-multi-thread",
"sync",
] }test_acc.masm
use miden::protocol::active_account
use miden::protocol::native_account
use miden::core::sys
const SLOT_EMPTY_INIT = word("test_acc::storage")
const SLOT_INITIALIZED = word("test_acc::storage_initialized")
pub proc update_initialized_map_slot(key: word, value: word)
push.SLOT_INITIALIZED[0..2]
exec.native_account::set_map_item
exec.sys::truncate_stack
end
pub proc update_map_slot(key: word, value: word)
push.SLOT_EMPTY_INIT [0..2]
exec.native_account::set_map_item
exec.sys::truncate_stack
endmain.rs:
use anyhow::Result;
use miden_client_sqlite_store::ClientBuilderSqliteExt;
use std::{collections::BTreeSet, sync::Arc, thread::sleep, time::Duration};
use miden_client::{
DebugMode, Felt, Word,
account::{
AccountBuilder, AccountComponent, AccountStorageMode, AccountType, StorageMap, StorageSlot,
StorageSlotName, component::BasicWallet,
},
assembly::{DefaultSourceManager, Module, ModuleKind, Path as AssemblyPath},
auth::NoAuth,
builder::ClientBuilder,
keystore::FilesystemKeyStore,
rpc::{Endpoint, GrpcClient},
store::AccountRecordData,
transaction::{TransactionKernel, TransactionRequestBuilder, TransactionScript},
vm::AdviceInputs,
};
use rand::{Rng, RngCore};
#[tokio::main]
async fn main() -> Result<()> {
let rpc_api = Arc::new(GrpcClient::new(&Endpoint::localhost(), 30000));
let keystore = FilesystemKeyStore::new("keystore".into()).unwrap();
let mut client = ClientBuilder::new()
.rpc(rpc_api.clone())
.authenticator(keystore.into())
.in_debug_mode(DebugMode::Enabled)
.filesystem_keystore("keystore")
.sqlite_store("store.sqlite3".into())
.build()
.await
.unwrap();
client.ensure_genesis_in_place().await.unwrap();
client.sync_state().await.unwrap();
let assembler = TransactionKernel::assembler().with_warnings_as_errors(true);
let source_manager = Arc::new(DefaultSourceManager::default());
let module = Module::parser(ModuleKind::Library)
.parse_str(
AssemblyPath::new("test_acc::test"),
std::fs::read_to_string("test_acc.masm").unwrap(),
source_manager.clone(),
)
.unwrap();
let library = assembler.clone().assemble_library([module]).unwrap();
// Storage
let mut storage_map = StorageMap::new();
let storage_key0 = Word::new([Felt::new(0), Felt::new(0), Felt::new(0), Felt::new(0)]);
storage_map.insert(
storage_key0,
Word::new([Felt::new(0), Felt::new(0), Felt::new(0), Felt::new(1)]),
)?;
let storage_slot_name_initialized =
StorageSlotName::new("test_acc::storage_initialized").unwrap();
let storage_slot_initialized =
StorageSlot::with_map(storage_slot_name_initialized.clone(), storage_map);
let storage_slot_name_emptyinit = StorageSlotName::new("test_acc::storage_emptyinit").unwrap();
let storage_slot_emptyinit = StorageSlot::with_empty_map(storage_slot_name_emptyinit.clone());
let storage_component = AccountComponent::new(
library.clone(),
vec![storage_slot_emptyinit, storage_slot_initialized],
)
.unwrap()
.with_supports_all_types();
let mut init_seed = [0_u8; 32];
client.rng().fill_bytes(&mut init_seed);
let storage_contract = AccountBuilder::new(init_seed)
.account_type(AccountType::RegularAccountImmutableCode)
.storage_mode(AccountStorageMode::Public)
.with_component(storage_component)
.with_component(BasicWallet)
.with_auth_component(NoAuth)
.build()
.unwrap();
client.add_account(&storage_contract, true).await?;
let tx_req = TransactionRequestBuilder::new()
.input_notes(vec![])
.build()?;
client
.submit_new_transaction(storage_contract.id(), tx_req)
.await
.unwrap();
println!(
"new acc: {}",
storage_contract
.id()
.to_bech32(miden_client::address::NetworkId::Testnet)
);
client.sync_state().await?;
let mut rng = rand::rng();
let min_inc: u64 = 1;
let max_inc: u64 = 1_000_000_000;
println!("\n\n---- WITH INITIALIZED MAP\n\n");
for i in 0..5 {
let new_val = rng.random_range(min_inc..=max_inc);
let tx_script = format!(
"use miden::core::sys\n
begin\n
push.{new_val}.0.0.0\n\
push.0.0.0.0\n
call.test_acc::test::update_initialized_map_slot\n
exec.sys::truncate_stack\n\
end"
);
let assembler = TransactionKernel::assembler()
.with_warnings_as_errors(true)
.with_static_library(library.clone())
.unwrap();
let program = assembler.assemble_program(tx_script).unwrap();
let compiled_tx_script = TransactionScript::new(program);
println!("[{}] key=(0,0,0,0), new_value={}", i + 1, new_val);
let tx_request = TransactionRequestBuilder::new()
.custom_script(compiled_tx_script.clone())
.build()?;
client
.submit_new_transaction(storage_contract.id(), tx_request)
.await?;
client.sync_state().await?;
let acc = client.get_account(storage_contract.id()).await?.unwrap();
match acc.account_data() {
AccountRecordData::Full(a) => {
let val = a
.storage()
.get_map_item(&storage_slot_name_initialized, storage_key0)
.unwrap();
println!("value in storage: {}", val.as_elements()[0]);
}
_ => {
panic!("missing full account data")
}
}
}
println!("\n\n---- WITH EMPTY MAP\n\n");
for i in 0..5 {
let new_val = rng.random_range(min_inc..=max_inc);
let tx_script = format!(
"use miden::core::sys\n
begin\n
push.{new_val}.0.0.0\n\
push.0.0.0.0\n
call.test_acc::test::update_map_slot\n
exec.sys::truncate_stack\n\
end"
);
let assembler = TransactionKernel::assembler()
.with_warnings_as_errors(true)
.with_static_library(library.clone())
.unwrap();
let program = assembler.assemble_program(tx_script).unwrap();
let compiled_tx_script = TransactionScript::new(program);
client
.execute_program(
storage_contract.id(),
compiled_tx_script.clone(),
AdviceInputs::default(),
BTreeSet::new(),
)
.await
.unwrap();
println!("[{}] key=(0,0,0,0), new_value={}", i + 1, new_val);
let tx_request = TransactionRequestBuilder::new()
.custom_script(compiled_tx_script.clone())
.build()?;
client
.submit_new_transaction(storage_contract.id(), tx_request)
.await?;
client.sync_state().await?;
let acc = client.get_account(storage_contract.id()).await?.unwrap();
match acc.account_data() {
AccountRecordData::Full(a) => {
let val = a
.storage()
.get_map_item(&storage_slot_name_emptyinit, storage_key0)
.unwrap();
println!("value in storage: {}", val.as_elements()[0]);
}
_ => {
panic!("missing full account data")
}
}
}
Ok(())
}
\Relevant log output
---- WITH INITIALIZED MAP
[1] key=(0,0,0,0), new_value=8988305
value in storage: 8988305
[2] key=(0,0,0,0), new_value=162255348
value in storage: 162255348
[3] key=(0,0,0,0), new_value=378648398
value in storage: 378648398
[4] key=(0,0,0,0), new_value=646121662
value in storage: 646121662
[5] key=(0,0,0,0), new_value=123840705
value in storage: 123840705
---- WITH EMPTY MAP
[1] key=(0,0,0,0), new_value=605359220
value in storage: 605359220
[2] key=(0,0,0,0), new_value=146702455
Error: storage error
Caused by:
account storage data with root 0xfc610ad170a0b4758e11fa85d058c02ce054d9cde3cd9d442c4e5484b26102eb not found