diff --git a/examples/deploy_script_with_type_id.rs b/examples/deploy_script_with_type_id.rs new file mode 100644 index 00000000..25b7a2f5 --- /dev/null +++ b/examples/deploy_script_with_type_id.rs @@ -0,0 +1,82 @@ +use ckb_sdk::{ + constants, + transaction::{ + builder::{CkbTransactionBuilder, SimpleTransactionBuilder}, + input::InputIterator, + signer::{SignContexts, TransactionSigner}, + TransactionBuilderConfiguration, + }, + Address, CkbRpcClient, NetworkInfo, ScriptId, +}; +use ckb_types::{ + core::Capacity, + h256, + packed::{Bytes, CellOutput}, + prelude::*, +}; +use std::{ + error::Error as StdErr, + fs::File, + io::{BufReader, Read}, + str::FromStr, +}; + +fn main() -> Result<(), Box> { + let network_info = NetworkInfo::testnet(); + let configuration = TransactionBuilderConfiguration::new_with_network(network_info.clone())?; + + let deployer = Address::from_str("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2qf8keemy2p5uu0g0gn8cd4ju23s5269qk8rg4r")?; + let (output, data) = build_output_and_data(&deployer); + + let iterator = InputIterator::new_with_address(&[deployer], &network_info); + let mut builder = SimpleTransactionBuilder::new(configuration, iterator); + builder.add_output_and_data(output, data); + + let mut tx_with_groups = builder.build(&Default::default())?; + + let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + let private_keys = vec![h256!( + "0x6c9ed03816e3111e49384b8d180174ad08e29feb1393ea1b51cef1c505d4e36a" + )]; + TransactionSigner::new(&network_info).sign_transaction( + &mut tx_with_groups, + &SignContexts::new_sighash_h256(private_keys)?, + )?; + + let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + let tx_hash = CkbRpcClient::new(network_info.url.as_str()) + .send_transaction(json_tx.inner, None) + .expect("send transaction"); + + println!(">>> tx {} sent! <<<", tx_hash); + + Ok(()) +} + +fn build_output_and_data(deployer: &Address) -> (CellOutput, Bytes) { + let script_binary = File::open("./src/test-data/always_success").unwrap(); + let mut reader = BufReader::new(script_binary); + let mut buffer = Vec::new(); + reader.read_to_end(&mut buffer).unwrap(); + let data_capacity = Capacity::bytes(buffer.len()).unwrap(); + + let type_script = + ScriptId::new_type(constants::TYPE_ID_CODE_HASH.clone()).dummy_type_id_script(); + let dummy_output = CellOutput::new_builder() + .lock(deployer.into()) + .type_(Some(type_script).pack()) + .build(); + let required_capacity = dummy_output + .occupied_capacity(data_capacity) + .unwrap() + .pack(); + let output = dummy_output + .as_builder() + .capacity(required_capacity) + .build(); + (output, buffer.pack()) +} diff --git a/examples/send_ckb_example.rs b/examples/send_ckb_example.rs index 50da98a0..742fa1fa 100644 --- a/examples/send_ckb_example.rs +++ b/examples/send_ckb_example.rs @@ -15,7 +15,7 @@ fn main() -> Result<(), Box> { let configuration = TransactionBuilderConfiguration::new_with_network(network_info.clone())?; let sender = Address::from_str("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2qf8keemy2p5uu0g0gn8cd4ju23s5269qk8rg4r")?; - let receiver=Address::from_str("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqv5dsed9par23x4g58seaw58j3ym5ml2hs8ztche")?; + let receiver= Address::from_str("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqv5dsed9par23x4g58seaw58j3ym5ml2hs8ztche")?; let iterator = InputIterator::new_with_address(&[sender], &network_info); let mut builder = SimpleTransactionBuilder::new(configuration, iterator); diff --git a/src/tests/transaction/mod.rs b/src/tests/transaction/mod.rs index 3050d6d3..ac42796e 100644 --- a/src/tests/transaction/mod.rs +++ b/src/tests/transaction/mod.rs @@ -1 +1,2 @@ pub mod sighash; +pub mod typeid; diff --git a/src/tests/transaction/typeid.rs b/src/tests/transaction/typeid.rs new file mode 100644 index 00000000..d29cc9aa --- /dev/null +++ b/src/tests/transaction/typeid.rs @@ -0,0 +1,68 @@ +use ckb_types::{packed::CellOutput, prelude::*}; + +use crate::{ + constants::{self, ONE_CKB}, + tests::{build_sighash_script, init_context, ACCOUNT1_ARG, ACCOUNT1_KEY, FEE_RATE}, + transaction::{ + builder::{CkbTransactionBuilder, SimpleTransactionBuilder}, + input::InputIterator, + signer::{SignContexts, TransactionSigner}, + TransactionBuilderConfiguration, + }, + NetworkInfo, ScriptId, +}; + +#[test] +fn test_deploy_id() { + let sender = build_sighash_script(ACCOUNT1_ARG); + let ctx = init_context(Vec::new(), vec![(sender.clone(), Some(10_0000 * ONE_CKB))]); + + let network_info = NetworkInfo::testnet(); + let type_script = + ScriptId::new_type(constants::TYPE_ID_CODE_HASH.clone()).dummy_type_id_script(); + + let output = CellOutput::new_builder() + .capacity((120 * ONE_CKB).pack()) + .lock(sender.clone()) + .type_(Some(type_script).pack()) + .build(); + let configuration = + TransactionBuilderConfiguration::new_with_network(network_info.clone()).unwrap(); + + let iterator = InputIterator::new_with_cell_collector( + vec![sender.clone()], + Box::new(ctx.to_live_cells_context()) as Box<_>, + ); + let mut builder = SimpleTransactionBuilder::new(configuration, iterator); + builder.add_output_and_data(output, bytes::Bytes::from(vec![0x01u8; 64]).pack()); + + let mut tx_with_groups = builder.build(&Default::default()).expect("build failed"); + + let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + TransactionSigner::new(&network_info) + .sign_transaction( + &mut tx_with_groups, + &SignContexts::new_sighash_h256(vec![ACCOUNT1_KEY.clone()]).unwrap(), + ) + .unwrap(); + + let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + let tx = tx_with_groups.get_tx_view().clone(); + let script_groups = tx_with_groups.script_groups.clone(); + assert_eq!(script_groups.len(), 2); + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 1); + assert_eq!(tx.inputs().len(), 1); + for out_point in tx.input_pts_iter() { + assert_eq!(ctx.get_input(&out_point).unwrap().0.lock(), sender); + } + assert_eq!(tx.outputs().len(), 2); + // assert_eq!(tx.output(0).unwrap(), output); + assert_eq!(tx.output(1).unwrap().lock(), sender); + + ctx.verify(tx, FEE_RATE).unwrap(); +} diff --git a/src/transaction/builder/mod.rs b/src/transaction/builder/mod.rs index 491fa73a..3d1848fc 100644 --- a/src/transaction/builder/mod.rs +++ b/src/transaction/builder/mod.rs @@ -166,12 +166,12 @@ impl CkbTransactionBuilder for SimpleTransactionBuilder { } // handle script groups - let script_groups = lock_groups + let mut script_groups: Vec = lock_groups .into_values() .chain(type_groups.into_values()) .collect(); - for script_group in &script_groups { + for script_group in script_groups.iter_mut() { for handler in configuration.get_script_handlers() { for context in &contexts.contexts { if handler.build_transaction(&mut tx, script_group, context.as_ref())? { diff --git a/src/transaction/handler/mod.rs b/src/transaction/handler/mod.rs index 4a96bd34..705a5f55 100644 --- a/src/transaction/handler/mod.rs +++ b/src/transaction/handler/mod.rs @@ -9,6 +9,7 @@ use self::sighash::Secp256k1Blake160SighashAllScriptContext; pub mod multisig; pub mod sighash; +pub mod typeid; pub trait ScriptHandler { /// Try to build transaction with the given script_group and context. @@ -17,7 +18,7 @@ pub trait ScriptHandler { fn build_transaction( &self, tx_builder: &mut TransactionBuilder, - script_group: &ScriptGroup, + script_group: &mut ScriptGroup, context: &dyn HandlerContext, ) -> Result; @@ -43,7 +44,10 @@ pub struct HandlerContexts { impl Default for HandlerContexts { fn default() -> Self { Self { - contexts: vec![Box::new(Secp256k1Blake160SighashAllScriptContext {})], + contexts: vec![ + Box::new(Secp256k1Blake160SighashAllScriptContext), + Box::new(typeid::TypeIdContext), + ], } } } @@ -51,7 +55,7 @@ impl Default for HandlerContexts { impl HandlerContexts { pub fn new_sighash() -> Self { Self { - contexts: vec![Box::new(Secp256k1Blake160SighashAllScriptContext {})], + contexts: vec![Box::new(Secp256k1Blake160SighashAllScriptContext)], } } diff --git a/src/transaction/handler/multisig.rs b/src/transaction/handler/multisig.rs index 046078bb..549b36db 100644 --- a/src/transaction/handler/multisig.rs +++ b/src/transaction/handler/multisig.rs @@ -43,7 +43,7 @@ impl ScriptHandler for Secp256k1Blake160MultisigAllScriptHandler { fn build_transaction( &self, tx_builder: &mut TransactionBuilder, - script_group: &ScriptGroup, + script_group: &mut ScriptGroup, context: &dyn HandlerContext, ) -> Result { if !self.is_match(&script_group.script) { diff --git a/src/transaction/handler/sighash.rs b/src/transaction/handler/sighash.rs index 51a8308c..13f10501 100644 --- a/src/transaction/handler/sighash.rs +++ b/src/transaction/handler/sighash.rs @@ -16,7 +16,7 @@ pub struct Secp256k1Blake160SighashAllScriptHandler { cell_deps: Vec, } -pub struct Secp256k1Blake160SighashAllScriptContext {} +pub struct Secp256k1Blake160SighashAllScriptContext; impl HandlerContext for Secp256k1Blake160SighashAllScriptContext {} @@ -35,7 +35,7 @@ impl ScriptHandler for Secp256k1Blake160SighashAllScriptHandler { fn build_transaction( &self, tx_builder: &mut TransactionBuilder, - script_group: &ScriptGroup, + script_group: &mut ScriptGroup, context: &dyn HandlerContext, ) -> Result { if !self.is_match(&script_group.script) { diff --git a/src/transaction/handler/typeid.rs b/src/transaction/handler/typeid.rs new file mode 100644 index 00000000..8069646f --- /dev/null +++ b/src/transaction/handler/typeid.rs @@ -0,0 +1,66 @@ +use ckb_hash::new_blake2b; +use ckb_types::{ + packed::{Bytes, CellInput}, + prelude::*, +}; + +use crate::{ + core::TransactionBuilder, tx_builder::TxBuilderError, NetworkInfo, ScriptGroup, ScriptId, +}; + +use super::{HandlerContext, ScriptHandler}; + +/// Type ID script handler, it will setup the [Type ID](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0022-transaction-structure/0022-transaction-structure.md#type-id) script's args automatically. +pub struct TypeIdHandler; + +pub struct TypeIdContext; + +impl HandlerContext for TypeIdContext {} + +impl ScriptHandler for TypeIdHandler { + fn build_transaction( + &self, + tx_builder: &mut TransactionBuilder, + script_group: &mut ScriptGroup, + context: &dyn HandlerContext, + ) -> Result { + if context.as_any().is::() + && ScriptId::from(&script_group.script).is_type_id() + && script_group.input_indices.is_empty() + && script_group.output_indices.len() == 1 + { + let input = tx_builder.get_inputs().first().unwrap(); + let index = *script_group.output_indices.last().unwrap(); + let args: Bytes = calculate_type_id(input, index as u64).to_vec().pack(); + let output = tx_builder.get_outputs().get(index).unwrap().clone(); + let output_type_script = output + .type_() + .to_opt() + .unwrap() + .as_builder() + .args(args) + .build(); + let updated_output = output + .as_builder() + .type_(Some(output_type_script.clone()).pack()) + .build(); + tx_builder.set_output(index, updated_output); + script_group.script = output_type_script; + return Ok(true); + } + Ok(false) + } + + fn init(&mut self, _network: &NetworkInfo) -> Result<(), TxBuilderError> { + Ok(()) + } +} + +fn calculate_type_id(first_cell_input: &CellInput, output_index: u64) -> [u8; 32] { + let mut blake2b = new_blake2b(); + blake2b.update(first_cell_input.as_slice()); + blake2b.update(&output_index.to_le_bytes()); + let mut ret = [0u8; 32]; + blake2b.finalize(&mut ret); + ret +} diff --git a/src/transaction/mod.rs b/src/transaction/mod.rs index c8610e03..4fafc57b 100644 --- a/src/transaction/mod.rs +++ b/src/transaction/mod.rs @@ -53,6 +53,7 @@ impl TransactionBuilderConfiguration { network, )?, ) as Box<_>, + Box::new(handler::typeid::TypeIdHandler) as Box<_>, ]; Ok(ret) } diff --git a/src/types/script_id.rs b/src/types/script_id.rs index cb759209..6b86c348 100644 --- a/src/types/script_id.rs +++ b/src/types/script_id.rs @@ -30,10 +30,13 @@ impl ScriptId { pub fn is_type_id(&self) -> bool { self.code_hash == TYPE_ID_CODE_HASH && self.hash_type == ScriptHashType::Type } - pub fn dummy_script(&self) -> Script { + + /// Generate a dummy TypeId script with a placeholder args + pub fn dummy_type_id_script(&self) -> Script { Script::new_builder() .code_hash(self.code_hash.pack()) .hash_type(self.hash_type.into()) + .args(vec![0u8; 32].pack()) .build() } }