From 6e6a2ed82192c49d7fe0b8b25dc9b431a60dd6b4 Mon Sep 17 00:00:00 2001 From: Boyu Yang Date: Sun, 24 Mar 2024 01:31:11 +0800 Subject: [PATCH] feat: deploy SPV contracts with type ID --- src/cli/deploy.rs | 64 +++++++++++++++++++++++------------ src/cli/init.rs | 47 ++++++++++++++++++++----- src/cli/mod.rs | 4 +-- src/components/ckb_client.rs | 2 +- src/components/spv_service.rs | 6 ++-- 5 files changed, 86 insertions(+), 37 deletions(-) diff --git a/src/cli/deploy.rs b/src/cli/deploy.rs index 87d6b9a..239dc1f 100644 --- a/src/cli/deploy.rs +++ b/src/cli/deploy.rs @@ -2,6 +2,7 @@ use ckb_jsonrpc_types::TransactionView; use ckb_sdk::{ + constants::TYPE_ID_CODE_HASH, transaction::{ builder::{CkbTransactionBuilder, SimpleTransactionBuilder}, input::InputIterator, @@ -11,7 +12,7 @@ use ckb_sdk::{ types::{ Address as CkbAddress, AddressPayload as CkbAddressPayload, HumanCapacity, NetworkInfo, }, - SECP256K1, + ScriptId, SECP256K1, }; use ckb_types::{bytes::Bytes, core::Capacity, packed, prelude::*}; use clap::Parser; @@ -31,25 +32,17 @@ pub struct Args { #[clap(flatten)] pub(crate) ckb: super::CkbArgs, - /// A binary file, which should contain the Bitcoin SPV contract. - /// - /// The repository of the contract source code is - /// . - /// - /// ### Warnings - /// - /// Under the development phase, the compatibility has chance to be broken - /// without any declaration. - /// - /// Please always use the latest versions of both the service and the contract. - /// - /// TODO Matched versions of the contracts should be list. + /// A binary file, which should contain a contract that users want to deploy. #[arg( long = "contract-file", value_name = "CONTRACT_FILE", required = true, value_parser = value_parsers::BinaryFileValueParser )] pub(crate) contract_data: Bytes, + /// Enable the type ID when deploy the contract. + #[arg(long)] + pub(crate) enable_type_id: bool, + /// The contract owner's address. #[arg(long="contract-owner", value_parser = value_parsers::AddressValueParser)] pub(crate) contract_owner: CkbAddress, @@ -60,7 +53,6 @@ pub struct Args { } impl Args { - // TODO Deploy the Bitcoin SPV contract as type script. pub fn execute(&self) -> Result<()> { log::info!("Try to deploy a contract on CKB"); @@ -86,13 +78,19 @@ impl Args { tmp }; - let output = packed::CellOutput::new_builder() - .lock((&self.contract_owner).into()) - .build_exact_capacity(contract_data_capacity) - .map_err(|err| { - let msg = format!("failed to calculate the capacity for the output since {err}"); - Error::other(msg) - })?; + let output_builder = packed::CellOutput::new_builder().lock((&self.contract_owner).into()); + + let output = if self.enable_type_id { + let type_script = ScriptId::new_type(TYPE_ID_CODE_HASH.clone()).dummy_type_id_script(); + output_builder.type_(Some(type_script).pack()) + } else { + output_builder + } + .build_exact_capacity(contract_data_capacity) + .map_err(|err| { + let msg = format!("failed to calculate the capacity for the output since {err}"); + Error::other(msg) + })?; let (deployer, deployer_key) = SecretKey::from_slice(&self.common.private_key.as_ref()[..]) .map(|sk| { @@ -117,6 +115,28 @@ impl Args { )?; let tx_json = TransactionView::from(tx_with_groups.get_tx_view().clone()); + + if self.enable_type_id { + let type_script: packed::Script = tx_json + .inner + .outputs + .first() + .ok_or_else(|| { + let msg = "at least one output should be existed"; + Error::other(msg) + })? + .type_ + .as_ref() + .ok_or_else(|| { + let msg = "the final output must contain a type script"; + Error::other(msg) + })? + .to_owned() + .into(); + let type_hash = type_script.calc_script_hash(); + log::info!("The contract type hash is {type_hash:#x}"); + } + self.ckb .client() .send_transaction_ext(tx_json, self.dry_run)?; diff --git a/src/cli/init.rs b/src/cli/init.rs index d1e155f..9a48ec0 100644 --- a/src/cli/init.rs +++ b/src/cli/init.rs @@ -31,7 +31,7 @@ use ckb_types::{ prelude::*, H256, }; -use clap::Parser; +use clap::{Args as ClapArgs, Parser}; use secp256k1::SecretKey; use crate::{ @@ -68,9 +68,8 @@ pub struct Args { #[arg(long, required = true)] pub(crate) spv_clients_count: u8, - /// The data hash of the Bitcoin SPV contract. - #[arg(long, value_parser = value_parsers::H256ValueParser)] - pub(crate) spv_contract_data_hash: H256, + #[clap(flatten)] + pub(crate) spv_contract_code_hash: CodeHash, /// The out point of the Bitcoin SPV contract. #[arg(long, value_parser = value_parsers::OutPointValueParser)] @@ -102,6 +101,18 @@ pub struct Args { pub(crate) dry_run: bool, } +#[derive(ClapArgs)] +#[group(required = true, multiple = false)] +pub struct CodeHash { + /// The data hash of the Bitcoin SPV contract. + #[arg(long, value_parser = value_parsers::H256ValueParser)] + pub(crate) spv_contract_data_hash: Option, + + /// The type hash of the Bitcoin SPV contract. + #[arg(long, value_parser = value_parsers::H256ValueParser)] + pub(crate) spv_contract_type_hash: Option, +} + impl Args { // TODO Split this method into several smaller methods. pub fn execute(&self) -> Result<()> { @@ -187,11 +198,29 @@ impl Args { .clients_count(self.spv_clients_count.into()) .flags(flags.into()) .build(); - Script::new_builder() - .code_hash(self.spv_contract_data_hash.pack()) - .hash_type(ScriptHashType::Data1.into()) - .args(Pack::pack(&args.as_bytes())) - .build() + match self.spv_contract_code_hash { + CodeHash { + spv_contract_data_hash: Some(ref data_hash), + spv_contract_type_hash: None, + } => Script::new_builder() + .code_hash(data_hash.pack()) + .hash_type(ScriptHashType::Data1.into()) + .args(Pack::pack(&args.as_bytes())) + .build(), + CodeHash { + spv_contract_data_hash: None, + spv_contract_type_hash: Some(ref type_hash), + } => Script::new_builder() + .code_hash(type_hash.pack()) + .hash_type(ScriptHashType::Type.into()) + .args(Pack::pack(&args.as_bytes())) + .build(), + _ => { + let msg = "only one of data hash and type hash for SPV contract \ + should be input, and at least one should be input"; + return Err(Error::other(msg)); + } + } }; storage.save_cells_state( diff --git a/src/cli/mod.rs b/src/cli/mod.rs index e8a09fa..33af8bb 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -27,8 +27,8 @@ pub struct Cli { pub enum Commands { /// Deploy a contract on CKB. /// - /// This command is used to deploy the Bitcoin SPV contract. - /// Also, users can deploy the contract in their own way. + /// This command can be used to deploy any contract and; + /// also, users can deploy the contract in their own way. Deploy(deploy::Args), /// Initialize a Bitcoin SPV instance on CKB. Init(init::Args), diff --git a/src/components/ckb_client.rs b/src/components/ckb_client.rs index addb40d..b8d8be1 100644 --- a/src/components/ckb_client.rs +++ b/src/components/ckb_client.rs @@ -33,7 +33,7 @@ impl CkbRpcClientExtension for CkbRpcClient { if !dry_run { let tx_hash = self.send_transaction(tx_json.inner, None)?; - log::info!("Transaction hash: {tx_hash:#x}."); + log::info!("Transaction hash: {tx_hash:#x}"); println!("Send transaction: {tx_hash:#x}"); } diff --git a/src/components/spv_service.rs b/src/components/spv_service.rs index 04c88cd..6ad6eee 100644 --- a/src/components/spv_service.rs +++ b/src/components/spv_service.rs @@ -117,7 +117,7 @@ impl SpvService { Error::other(msg) })? .to_owned(); - log::trace!("[onchain] tip SPV client {}", spv_client_curr.client); + log::info!("[onchain] tip SPV client {}", spv_client_curr.client); let spv_header_root_curr = &spv_client_curr.client.headers_mmr_root; let spv_height_curr = spv_header_root_curr.max_height; @@ -259,7 +259,7 @@ impl SpvService { return Ok(true); } - log::info!("Try to find the height when fork happened."); + log::info!("Try to find the height when fork happened"); let (stg_base_height, _) = spv.storage.base_state()?; let mut fork_point = None; @@ -271,7 +271,7 @@ impl SpvService { log::debug!("[bitcoin] header#{height:07}, {btc_hash:#x}"); if stg_hash == btc_hash { - log::info!("Fork happened at height {height}."); + log::info!("Fork happened at height {height}"); fork_point = Some((height, btc_hash)); } }