Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(engine)!: publish template as substate #1214

Merged

Conversation

ksrichard
Copy link
Collaborator

@ksrichard ksrichard commented Dec 10, 2024

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:
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;
        }
    }
}
  1. Publish the template to swarm
    Example code (using tari-dan dependencies from this branch):
    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);
  1. Test the new template by creating a new component out of that and call it
    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?;
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)
}
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(())
}
  1. Check results in Wallet UI
  • Template publish
    image
    image
    image

  • Component creation
    image
    image

  • Component call
    image
    image

What process can a PR reviewer use to test or verify this change?

Do the test

Breaking Changes

  • None
  • Requires data directory to be deleted
  • Other - Please specify

@ksrichard ksrichard marked this pull request as ready for review December 10, 2024 14:10
@ksrichard ksrichard marked this pull request as draft December 10, 2024 14:11
Copy link

github-actions bot commented Dec 10, 2024

Test Results (CI)

593 tests  +27   593 ✅ +27   3h 18m 35s ⏱️ + 1h 40m 24s
 67 suites +13     0 💤 ± 0 
  2 files   + 1     0 ❌ ± 0 

Results for commit d8c90de. ± Comparison against base commit 27e2516.

♻️ This comment has been updated with latest results.

…h-template-as-substate

# Conflicts:
#	applications/tari_dan_app_utilities/src/base_layer_scanner.rs
#	dan_layer/engine_types/src/substate.rs
@ksrichard ksrichard marked this pull request as ready for review December 11, 2024 12:44
@sdbondi
Copy link
Member

sdbondi commented Dec 12, 2024

Cucumbers need to be updated to register templates in the new way.
Since this will require support from the wallet and on the consensus level perhaps for now we should just keep the base layer scanner as is so that template registration continues to work from the base layer for now, since we wont be able to register templates at all.

These tasks need to happen before we remove the previous registration from the scanner:

  1. wallet support to submit publish template transactions Add publish template interface and backend to wallet daemon #1218
  2. allow validators to share/request templates as needed
  3. update cucumbers to use this method

@sdbondi sdbondi force-pushed the feature/publish-template-as-substate branch from 49571b9 to 221ca6c Compare December 12, 2024 06:36
@sdbondi sdbondi changed the title feat: publish template as substate feat(engine)!: publish template as substate Dec 12, 2024
@sdbondi sdbondi enabled auto-merge December 12, 2024 13:59
@sdbondi sdbondi disabled auto-merge December 12, 2024 14:00
@sdbondi sdbondi merged commit 0f42d39 into tari-project:development Dec 12, 2024
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants