From f1cc42f4f5e646be9ebfea46428a20fe28abb4cc Mon Sep 17 00:00:00 2001 From: Juan Girini Date: Tue, 8 Oct 2024 14:30:57 +0200 Subject: [PATCH 1/3] feat: make `create` and `prepare_execute` not return payload --- lib/src/bin/murmur/main.rs | 73 ++++++++++------ lib/src/lib.rs | 173 ++++++++++++++++--------------------- 2 files changed, 120 insertions(+), 126 deletions(-) diff --git a/lib/src/bin/murmur/main.rs b/lib/src/bin/murmur/main.rs index a945986..b04e5a1 100644 --- a/lib/src/bin/murmur/main.rs +++ b/lib/src/bin/murmur/main.rs @@ -15,9 +15,7 @@ */ use clap::{Parser, Subcommand}; use murmur_lib::{ - create, - etf::{self, runtime_types::node_template_runtime::RuntimeCall::Balances}, - idn_connect, prepare_execute, BlockNumber, MurmurStore, + create, etf, idn_connect, prepare_execute, BlockNumber, BoundedVec, MurmurStore, RuntimeCall, }; use sp_core::crypto::Ss58Codec; use std::fs::File; @@ -95,6 +93,7 @@ async fn main() -> Result<(), Box> { match &cli.commands { Commands::New(args) => { println!("🏭 Murmur: Generating Merkle mountain range"); + // 1. prepare block schedule let mut schedule: Vec = Vec::new(); for i in 2..args.validity + 2 { @@ -102,57 +101,77 @@ async fn main() -> Result<(), Box> { let next_block_number: BlockNumber = current_block_number + i; schedule.push(next_block_number); } + // 2. create mmr - let (call, mmr_store) = create( - args.name.as_bytes().to_vec(), + let create_data = create( args.seed.as_bytes().to_vec(), ephem_msk, schedule, round_pubkey_bytes, ) .map_err(|_| CLIError::MurmurCreationFailed)?; + // 3. add to storage - write_mmr_store(mmr_store.clone(), MMR_STORE_FILEPATH); - // sign and send the call - let from = dev::alice(); - let _events = client + write_mmr_store(create_data.mmr_store.clone(), MMR_STORE_FILEPATH); + + // 4. build the call + let call = etf::tx().murmur().create( + create_data.root, + create_data.size, + BoundedVec(args.name.as_bytes().to_vec()), + ); + + // 5. sign and send the call + client .tx() - .sign_and_submit_then_watch_default(&call, &from) + .sign_and_submit_then_watch_default(&call, &dev::alice()) .await?; + println!("✅ MMR proxy account creation successful!"); } Commands::Execute(args) => { - // build balance transfer + // 1. build proxied call let from_ss58 = sp_core::crypto::AccountId32::from_ss58check(&args.to) .map_err(|_| CLIError::InvalidRecipient)?; - let bytes: &[u8] = from_ss58.as_ref(); let from_ss58_sized: [u8; 32] = bytes.try_into().map_err(|_| CLIError::InvalidRecipient)?; let to = subxt::utils::AccountId32::from(from_ss58_sized); + let balance_transfer_call = + RuntimeCall::Balances(etf::balances::Call::transfer_allow_death { + dest: subxt::utils::MultiAddress::<_, u32>::from(to), + value: args.amount, + }); - let balance_transfer_call = Balances(etf::balances::Call::transfer_allow_death { - dest: subxt::utils::MultiAddress::<_, u32>::from(to), - value: args.amount, - }); - + // 2. load the MMR store let store: MurmurStore = load_mmr_store(MMR_STORE_FILEPATH)?; - let target_block_number: BlockNumber = current_block_number + 1; - println!("💾 Recovered Murmur store from local file"); - let tx = prepare_execute( - args.name.as_bytes().to_vec(), + + // 3. get the proxy data + let proxy_data = prepare_execute( + // args.name.as_bytes().to_vec(), args.seed.as_bytes().to_vec(), - target_block_number, + current_block_number + 1, store, - balance_transfer_call, + &balance_transfer_call, ) .map_err(|_| CLIError::MurmurExecutionFailed)?; - // submit the tx using alice to sign it - let _result = client + + // 4. build the call + let call = etf::tx().murmur().proxy( + BoundedVec(args.name.as_bytes().to_vec()), + proxy_data.position, + proxy_data.hash, + proxy_data.ciphertext, + proxy_data.proof_items, + proxy_data.size, + balance_transfer_call, + ); + // 5. sign and send the call + client .tx() - .sign_and_submit_then_watch_default(&tx, &dev::alice()) - .await; + .sign_and_submit_then_watch_default(&call, &dev::alice()) + .await?; } } println!("Elapsed time: {:.2?}", before.elapsed()); diff --git a/lib/src/lib.rs b/lib/src/lib.rs index fbed771..db23d73 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -14,7 +14,7 @@ * limitations under the License. */ -use etf::runtime_types::bounded_collections::bounded_vec::BoundedVec; +use beefy::{known_payloads, Commitment, Payload}; use murmur_core::types::{Identity, IdentityBuilder}; use subxt::{ backend::rpc::RpcClient, client::OnlineClient, config::SubstrateConfig, ext::codec::Encode, @@ -22,16 +22,14 @@ use subxt::{ use w3f_bls::{DoublePublicKey, SerializableToBytes, TinyBLS377}; use zeroize::Zeroize; -pub use beefy::{known_payloads, Commitment, Payload}; -pub use etf::{ - murmur::calls::types::{Create, Proxy}, - runtime_types::node_template_runtime::RuntimeCall, +pub use etf::runtime_types::{ + bounded_collections::bounded_vec::BoundedVec, + node_template_runtime::RuntimeCall, }; pub use murmur_core::{ murmur::{Error, MurmurStore}, types::BlockNumber, }; -pub use subxt::tx::Payload as TxPayload; // Generate an interface that we can use from the node's metadata. #[subxt::subxt(runtime_metadata_path = "artifacts/metadata.scale")] @@ -51,22 +49,39 @@ impl IdentityBuilder for BasicIdBuilder { Identity::new(&commitment.encode()) } } -/// create a new MMR and use it to generate a valid call to create a murmur wallet -/// returns the call data and the mmr_store + +/// Data needed to build a valid call for creating a murmur wallet. +pub struct CreateData { + /// The root of the MMR + pub root: Vec, + /// The size of the MMR + pub size: u64, + pub mmr_store: MurmurStore, +} + +/// Data needed to build a valid call for a proxied execution. +pub struct ProxyData { + pub position: u64, + /// The hash of the commitment + pub hash: Vec, + pub ciphertext: Vec, + pub proof_items: Vec>, + pub size: u64, +} + +/// Create a new MMR and return the data needed to build a valid call for creating a murmur wallet. /// -/// * `name`: The name of the murmur proxy /// * `seed`: The seed used to generate otp codes /// * `ephem_msk`: An ephemeral secret key TODO: replace with an hkdf? /// * `block_schedule`: A list of block numbers when the wallet will be executable /// * `round_pubkey_bytes`: The Ideal Network randomness beacon public key /// pub fn create( - name: Vec, mut seed: Vec, mut ephem_msk: [u8; 32], block_schedule: Vec, round_pubkey_bytes: Vec, -) -> Result<(TxPayload, MurmurStore), Error> { +) -> Result { let round_pubkey = DoublePublicKey::::from_bytes(&round_pubkey_bytes) .map_err(|_| Error::InvalidPubkey)?; let mmr_store = MurmurStore::new::( @@ -79,35 +94,33 @@ pub fn create( seed.zeroize(); let root = mmr_store.root.clone(); - let call = etf::tx() - .murmur() - .create(root.0, mmr_store.metadata.len() as u64, BoundedVec(name)); - Ok((call, mmr_store)) + Ok(CreateData { + root: root.0, + size: mmr_store.metadata.len() as u64, + mmr_store, + }) } -/// prepare the call for immediate execution -/// Note to self: in the future, we can consider ways to prune the murmurstore as OTP codes are consumed -/// for example, we can take the next values from the map, reducing storage to 0 over time -/// However, to do this we need to think of a way to prove it with a merkle proof -/// my thought is that we would have a subtree, so first we prove that the subtree is indeed in the parent MMR -/// then we prove that the specific leaf is in the subtree. -/// We could potentially use that idea as a way to optimize the execute function in general. Rather than -/// loading the entire MMR into memory, we really only need to load a minimal subtree containing the leaf we want to consume -/// -> add this to the 'future work' section later -/// -/// * `name`: The name of the murmur proxy +/// Return the data needed for the immediate execution of the proxied call. /// * `seed`: The seed used to generate otp codes /// * `when`: The block number when OTP codeds should be generated /// * `store`: A murmur store -/// * `call`: Any valid runtime call +/// * `call`: Proxied call. Any valid runtime call /// +// Note to self: in the future, we can consider ways to prune the murmurstore as OTP codes are consumed +// for example, we can take the next values from the map, reducing storage to 0 over time +// However, to do this we need to think of a way to prove it with a merkle proof +// my thought is that we would have a subtree, so first we prove that the subtree is indeed in the parent MMR +// then we prove that the specific leaf is in the subtree. +// We could potentially use that idea as a way to optimize the execute function in general. Rather than +// loading the entire MMR into memory, we really only need to load a minimal subtree containing the leaf we want to consume +// -> add this to the 'future work' section later pub fn prepare_execute( - name: Vec, mut seed: Vec, when: BlockNumber, store: MurmurStore, - call: RuntimeCall, -) -> Result, Error> { + call: &RuntimeCall, +) -> Result { let (proof, commitment, ciphertext, pos) = store.execute(seed.clone(), when, call.encode())?; seed.zeroize(); let size = proof.mmr_size(); @@ -117,15 +130,13 @@ pub fn prepare_execute( .map(|leaf| leaf.0.clone()) .collect::>(); - Ok(etf::tx().murmur().proxy( - BoundedVec(name), - pos, - commitment, + Ok(ProxyData { + position: pos, + hash: commitment, ciphertext, proof_items, size, - call, - )) + }) } /// Async connection to the Ideal Network @@ -167,87 +178,63 @@ pub async fn idn_connect( #[cfg(test)] mod tests { - use super::*; - use subxt::tx::TxPayload; #[test] - pub fn it_can_create_an_mmr_store_and_call_data() { - let name = b"name".to_vec(); + pub fn it_can_create_an_mmr_store() { let seed = b"seed".to_vec(); let ephem_msk = [1; 32]; let block_schedule = vec![1, 2, 3, 4, 5, 6, 7]; let double_public_bytes = murmur_test_utils::get_dummy_beacon_pubkey(); - let (call, mmr_store) = create( - name.clone(), - seed, + let create_data = create( + seed.clone(), ephem_msk, - block_schedule, - double_public_bytes, + block_schedule.clone(), + double_public_bytes.clone(), ) .unwrap(); - let expected_call = etf::tx().murmur().create( - mmr_store.root.0, - mmr_store.metadata.len() as u64, - BoundedVec(name), - ); - - let actual_details = call.validation_details().unwrap(); - let expected_details = expected_call.validation_details().unwrap(); - - assert_eq!(actual_details.pallet_name, expected_details.pallet_name,); - - assert_eq!(actual_details.call_name, expected_details.call_name,); + let mmr_store = MurmurStore::new::( + seed, + block_schedule, + ephem_msk, + DoublePublicKey::::from_bytes(&double_public_bytes).unwrap(), + ) + .unwrap(); - assert_eq!(actual_details.hash, expected_details.hash,); + assert_eq!(create_data.mmr_store.root, mmr_store.root); + assert_eq!(create_data.size, 7); } #[test] pub fn it_can_prepare_valid_execution_call_data() { - let name = b"name".to_vec(); let seed = b"seed".to_vec(); let ephem_msk = [1; 32]; let block_schedule = vec![1, 2, 3, 4, 5, 6, 7]; let double_public_bytes = murmur_test_utils::get_dummy_beacon_pubkey(); - let (_call, mmr_store) = create( - name.clone(), - seed.clone(), - ephem_msk, - block_schedule, - double_public_bytes, - ) - .unwrap(); + let create_data = + create(seed.clone(), ephem_msk, block_schedule, double_public_bytes).unwrap(); let bob = subxt_signer::sr25519::dev::bob().public_key(); - let bob2 = subxt_signer::sr25519::dev::bob().public_key(); let balance_transfer_call = - etf::runtime_types::node_template_runtime::RuntimeCall::Balances( + &etf::runtime_types::node_template_runtime::RuntimeCall::Balances( etf::balances::Call::transfer_allow_death { dest: subxt::utils::MultiAddress::<_, u32>::from(bob), value: 1, }, ); - let balance_transfer_call_2 = - etf::runtime_types::node_template_runtime::RuntimeCall::Balances( - etf::balances::Call::transfer_allow_death { - dest: subxt::utils::MultiAddress::<_, u32>::from(bob2), - value: 1, - }, - ); - - let actual_call = prepare_execute( - name.clone(), + let proxy_data = prepare_execute( seed.clone(), 1, - mmr_store.clone(), + create_data.mmr_store.clone(), balance_transfer_call, ) .unwrap(); - let (proof, commitment, ciphertext, _pos) = mmr_store - .execute(seed.clone(), 1, balance_transfer_call_2.encode()) + let (proof, commitment, ciphertext, _pos) = create_data + .mmr_store + .execute(seed.clone(), 1, balance_transfer_call.encode()) .unwrap(); let size = proof.mmr_size(); @@ -256,23 +243,11 @@ mod tests { .iter() .map(|leaf| leaf.0.clone()) .collect::>(); - let expected_call = etf::tx().murmur().proxy( - BoundedVec(name), - 0, - commitment, - ciphertext, - proof_items, - size, - balance_transfer_call_2, - ); - - let actual_details = actual_call.validation_details().unwrap(); - let expected_details = expected_call.validation_details().unwrap(); - - assert_eq!(actual_details.pallet_name, expected_details.pallet_name,); - - assert_eq!(actual_details.call_name, expected_details.call_name,); - assert_eq!(actual_details.hash, expected_details.hash,); + assert_eq!(proxy_data.position, 0); + assert_eq!(proxy_data.hash, commitment); + assert_eq!(proxy_data.ciphertext, ciphertext); + assert_eq!(proxy_data.proof_items, proof_items); + assert_eq!(proxy_data.size, size); } } From cb96ae6328794e3bbd918b7a85a4893e520349c9 Mon Sep 17 00:00:00 2001 From: Juan Girini Date: Wed, 9 Oct 2024 12:03:51 +0200 Subject: [PATCH 2/3] feat: make data serializable --- lib/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/src/lib.rs b/lib/src/lib.rs index db23d73..f51935e 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -16,6 +16,7 @@ use beefy::{known_payloads, Commitment, Payload}; use murmur_core::types::{Identity, IdentityBuilder}; +use serde::Serialize; use subxt::{ backend::rpc::RpcClient, client::OnlineClient, config::SubstrateConfig, ext::codec::Encode, }; @@ -23,8 +24,7 @@ use w3f_bls::{DoublePublicKey, SerializableToBytes, TinyBLS377}; use zeroize::Zeroize; pub use etf::runtime_types::{ - bounded_collections::bounded_vec::BoundedVec, - node_template_runtime::RuntimeCall, + bounded_collections::bounded_vec::BoundedVec, node_template_runtime::RuntimeCall, }; pub use murmur_core::{ murmur::{Error, MurmurStore}, @@ -50,6 +50,7 @@ impl IdentityBuilder for BasicIdBuilder { } } +#[derive(Serialize)] /// Data needed to build a valid call for creating a murmur wallet. pub struct CreateData { /// The root of the MMR @@ -59,6 +60,7 @@ pub struct CreateData { pub mmr_store: MurmurStore, } +#[derive(Serialize)] /// Data needed to build a valid call for a proxied execution. pub struct ProxyData { pub position: u64, From f00241d6984641625a6aba7a48e2b07df4abefae Mon Sep 17 00:00:00 2001 From: Juan Girini Date: Wed, 9 Oct 2024 16:04:20 +0200 Subject: [PATCH 3/3] chore: clean up comment --- lib/src/bin/murmur/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/bin/murmur/main.rs b/lib/src/bin/murmur/main.rs index b04e5a1..8f83c3c 100644 --- a/lib/src/bin/murmur/main.rs +++ b/lib/src/bin/murmur/main.rs @@ -149,7 +149,6 @@ async fn main() -> Result<(), Box> { // 3. get the proxy data let proxy_data = prepare_execute( - // args.name.as_bytes().to_vec(), args.seed.as_bytes().to_vec(), current_block_number + 1, store,