Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(engine)!: publish template as substate (#1214)
Description --- When registering a new template on Layer-2 it should be represented as a new substate that contains the whole binary, so it can be used to call functions to create new components etc.. This PR contains the changes to create a new substate from a submitted template registration/publishing. **Please note** that these changes are working only in 1 shard group, multi shard group version (downloading template binaries from foreign shards) will be in another PR! Motivation and Context --- How Has This Been Tested? --- 1. Delete `data/processes` directory to start a fresh swarm (if there was data left) 2. Start a new swarm with possibly 1 shard group (10 VNs max for now) 3. Create a new account 4. Add at least `200.000` tokens to it through free test tokens faucet (on UI) 5. Build an example wasm template locally somewhere I used this example template: ```rust use tari_template_lib::prelude::*; #[template] mod counter { use super::*; pub struct Counter { value: u32, } impl Counter { pub fn new() -> Component<Self> { Component::new(Self { value: 0 }) .with_access_rules(AccessRules::allow_all()) .create() } pub fn value(&self) -> u32 { self.value } pub fn increase(&mut self) { self.value += 1; } pub fn decrease(&mut self) { self.value -= 1; } } } ``` 6. Publish the template to swarm Example code (using `tari-dan` dependencies from this branch): ```rust let wallet_daemon_jrpc_port = 12027; let account_name = String::from("acc"); let mut client = wallet_daemon_client(wallet_daemon_jrpc_port).await?; let AccountGetResponse { account, public_key, } = client .accounts_get(ComponentAddressOrName::Name(account_name.clone())) .await?; let account_component_address = account.address .as_component_address() .expect("Failed to get component address"); // publish wasm template let wasm_binary = fs::read("./templates/counter/target/wasm32-unknown-unknown/release/counter.wasm")?; let tx = Transaction::builder() .fee_transaction_pay_from_component(account_component_address, Amount(200_000)) .publish_template(wasm_binary) .build_unsigned_transaction(); let transaction_submit_req = TransactionSubmitRequest { transaction: tx, signing_key_index: Some(account.key_index), detect_inputs: true, detect_inputs_use_unversioned: true, proof_ids: vec![], autofill_inputs: vec![], }; let resp = client.submit_transaction(transaction_submit_req).await?; println!("Submit RESP: {resp:?}"); let wait_req = TransactionWaitResultRequest { transaction_id: resp.transaction_id, timeout_secs: Some(120), }; let wait_resp = client.wait_transaction_result(wait_req).await?; println!("TX RESP: {wait_resp:?}"); let substate_diff = wait_resp .result .expect("No result") .result .expect("Failed to obtain substate diffs"); let mut substate_id: Option<PublishedTemplateAddress> = None; let mut version: Option<u32> = None; for (addr, data) in substate_diff.up_iter() { if let SubstateId::Template(val) = addr { substate_id = Some(*val); version = Some(data.version()); break; } } let addr = substate_id.unwrap().as_hash()?; println!("Published template address: {}", addr); ``` 7. Test the new template by creating a new component out of that and call it ```rust let counter_template_address = TemplateAddress::from_hex("d7e6f5cd2b717c83c86d3b3abf046a4caa0947e04b4e88de97a94a63ad19e382").unwrap(); println!("Creating counter component..."); let counter_component_result = create_component(wallet_daemon_jrpc_port, account_name.clone(), counter_template_address).await?; println!("Counter component has been created!"); let counter_component_substate_diff = counter_component_result .result .expect("No result") .result .expect("Failed to obtain substate diffs"); let mut component_substate_id: Option<ComponentAddress> = None; let mut component_version: Option<u32> = None; for (addr, data) in counter_component_substate_diff.up_iter() { if let SubstateId::Component(_) = addr { let component = data.substate_value().component().unwrap(); println!("{:?} - {:?}", component.entity_id.to_string(), component.module_name); component_substate_id = Some(ComponentAddress::try_from(addr.clone()).unwrap()); component_version = Some(data.version()); break; } } // call component let counter_comp_address = component_substate_id.unwrap(); let counter_comp_version = component_version.unwrap(); println!("{counter_comp_address:?}, {counter_comp_version:?}"); call_component(wallet_daemon_jrpc_port, account_name, 0, &counter_comp_address, counter_comp_version, "increase").await?; ``` ```rust pub async fn create_component( wallet_daemon_jrpc_port: u16, account_name: String, template_address: TemplateAddress, ) -> anyhow::Result<TransactionWaitResultResponse> { let mut client = wallet_daemon_client(wallet_daemon_jrpc_port).await?; let AccountGetResponse { account, .. } = client .accounts_get(ComponentAddressOrName::Name(account_name.clone())) .await?; let transaction = tari_transaction::Transaction::builder() .fee_transaction_pay_from_component(account.address.as_component_address().unwrap(), Amount(2000)) .call_function(template_address, String::from("new"), vec![]) .build_unsigned_transaction(); let transaction_submit_req = TransactionSubmitRequest { transaction, signing_key_index: Some(account.key_index), detect_inputs: true, detect_inputs_use_unversioned: false, proof_ids: vec![], autofill_inputs: vec![], }; let resp = client.submit_transaction(transaction_submit_req).await?; let wait_req = TransactionWaitResultRequest { transaction_id: resp.transaction_id, timeout_secs: Some(120), }; let wait_resp = client.wait_transaction_result(wait_req).await?; Ok(wait_resp) } ``` ```rust pub async fn call_component( wallet_daemon_jrpc_port: u16, account_name: String, account_version: u32, input_comp_address: &ComponentAddress, input_comp_version: u32, method: &str, ) -> anyhow::Result<()> { let mut client = wallet_daemon_client(wallet_daemon_jrpc_port).await?; let AccountGetResponse { account, .. } = client .accounts_get(ComponentAddressOrName::Name(account_name.clone())) .await?; let account_component_address = account.address .as_component_address() .expect("Failed to get component address"); // build tx let transaction = Transaction::builder() .fee_transaction_pay_from_component(account_component_address, Amount(1000)) .call_method(*input_comp_address, method, vec![]) .with_inputs(vec![ SubstateRequirement::new( account_component_address.into(), Some(account_version), ), SubstateRequirement::new( (*input_comp_address).into(), Some(input_comp_version), ), ]) .build_unsigned_transaction(); // let transaction = Transaction::builder() // .fee_transaction_pay_from_component(account_component_address, Amount(1000)) // .call_method(*input_comp_address, method, vec![]) // .build_unsigned_transaction(); // send transactions (to create rejected ones) let mut join_set = JoinSet::new(); // for _ in 0..2 { for _ in 0..1 { let tx = transaction.clone(); join_set.spawn(async move { let mut client = wallet_daemon_client(wallet_daemon_jrpc_port).await?; let submit_req = TransactionSubmitRequest { transaction: tx, signing_key_index: Some(account.key_index), autofill_inputs: vec![], detect_inputs: true, detect_inputs_use_unversioned: false, proof_ids: vec![], }; let submit_resp = client.submit_transaction(submit_req).await?; let wait_req = TransactionWaitResultRequest { transaction_id: submit_resp.transaction_id, timeout_secs: Some(120), }; let resp = client .wait_transaction_result(wait_req) .await .map_err(|e| anyhow::Error::msg(e.to_string()))?; if let Some(reason) = resp.result.as_ref().and_then(|finalize| finalize.reject()) { bail!("Calling component result rejected: {}", reason); } Ok(()) }); } while let Some(result) = join_set.join_next().await { match result { Ok(res) => match res { Ok(_) => { println!("Transaction sent successfully!"); } Err(error) => { bail!("Transaction submit error: {error:?}"); } } Err(error) => { bail!("Join error: {error:?}"); } } } Ok(()) } ``` 8. Check results in Wallet UI - Template publish ![image](https://github.com/user-attachments/assets/a26904b2-60dc-48b6-8b58-088c62bacc28) ![image](https://github.com/user-attachments/assets/051242e1-15d1-4797-8273-e956bd881bcf) ![image](https://github.com/user-attachments/assets/f10a4e29-c38b-457c-b775-654b151d2bf1) - Component creation ![image](https://github.com/user-attachments/assets/ef5a83d0-d533-47cf-8df7-2f653097d228) ![image](https://github.com/user-attachments/assets/7d6a9182-501f-488f-9953-f4ef83cc5a60) - Component call ![image](https://github.com/user-attachments/assets/5c0ec274-cad4-41c4-8c95-78964724422f) ![image](https://github.com/user-attachments/assets/fd0ab37b-b1fb-4fec-926a-5c4845610111) What process can a PR reviewer use to test or verify this change? --- Do the test Breaking Changes --- - [ ] None - [x] Requires data directory to be deleted - [ ] Other - Please specify --------- Co-authored-by: Stan Bondi <[email protected]>
- Loading branch information