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..65415d2a --- /dev/null +++ b/src/tests/transaction/typeid.rs @@ -0,0 +1,67 @@ +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_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(output, bytes::Bytes::from(vec![0x01u8; 64]).pack()); + builder.set_change_lock(sender.clone()); + 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 fd98b3de..cbaf0ee6 100644 --- a/src/transaction/builder/mod.rs +++ b/src/transaction/builder/mod.rs @@ -134,6 +134,27 @@ impl SimpleTransactionBuilder { tx_builder.set_output(idx, output); Ok(()) } + + fn post_build( + type_groups: &HashMap, + configuration: &TransactionBuilderConfiguration, + tx_builder: &mut TransactionBuilder, + contexts: &HandlerContexts, + ) -> Result<(), TxBuilderError> { + for idx in type_groups + .values() + .flat_map(|group| group.output_indices.iter()) + { + for handler in configuration.get_script_handlers() { + for context in &contexts.contexts { + if handler.post_build(*idx, tx_builder, context.as_ref())? { + break; + } + } + } + } + Ok(()) + } } macro_rules! celloutput_capacity { @@ -269,6 +290,7 @@ impl CkbTransactionBuilder for SimpleTransactionBuilder { if !state.is_success() { return Err(TxBuilderError::BalanceCapacity(state.into())); } + Self::post_build(&type_groups, &self.configuration, &mut self.tx, contexts)?; let script_groups = lock_groups .into_values() .chain(type_groups.into_values()) diff --git a/src/transaction/handler/mod.rs b/src/transaction/handler/mod.rs index 4a96bd34..e0702243 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. @@ -21,6 +22,15 @@ pub trait ScriptHandler { context: &dyn HandlerContext, ) -> Result; + fn post_build( + &self, + _index: usize, + _tx_builder: &mut TransactionBuilder, + _context: &dyn HandlerContext, + ) -> Result { + Ok(false) + } + fn init(&mut self, network: &NetworkInfo) -> Result<(), TxBuilderError>; } @@ -43,7 +53,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 {}), + ], } } } diff --git a/src/transaction/handler/typeid.rs b/src/transaction/handler/typeid.rs new file mode 100644 index 00000000..0ffe5e2d --- /dev/null +++ b/src/transaction/handler/typeid.rs @@ -0,0 +1,104 @@ +use ckb_hash::new_blake2b; +use ckb_types::{ + packed::{CellInput, Script}, + prelude::*, +}; + +use crate::{ + core::TransactionBuilder, tx_builder::TxBuilderError, NetworkInfo, ScriptGroup, + ScriptGroupType, ScriptId, +}; + +use super::{HandlerContext, ScriptHandler}; + +pub struct TypeIdHandler; + +pub struct TypeIdContext {} + +impl HandlerContext for TypeIdContext {} + +impl TypeIdHandler { + pub fn is_match(&self, script: &Script) -> bool { + ScriptId::from(script).is_type_id() + } +} + +// copy from https://github.com/nervosnetwork/ckb-cli/blob/develop/src/utils/other.rs#L325 +pub 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 +} + +impl ScriptHandler for TypeIdHandler { + fn build_transaction( + &self, + tx_builder: &mut TransactionBuilder, + script_group: &ScriptGroup, + context: &dyn HandlerContext, + ) -> Result { + if script_group.group_type != ScriptGroupType::Type + || !self.is_match(&script_group.script) + || script_group.output_indices.is_empty() + { + return Ok(false); + } + if let Some(_args) = context.as_any().downcast_ref::() { + let index = script_group.output_indices.last().unwrap(); + let output = tx_builder.get_outputs()[*index].clone(); + if let Some(type_) = output.type_().to_opt() { + if self.is_match(&type_) && type_.args().is_empty() { + let type_ = type_ + .as_builder() + .args(bytes::Bytes::from(vec![0u8; 32]).pack()) + .build(); + let output = output.as_builder().type_(Some(type_).pack()).build(); + tx_builder.set_output(*index, output); + } + + return Ok(true); + } + } + Ok(false) + } + + fn init(&mut self, _network: &NetworkInfo) -> Result<(), TxBuilderError> { + Ok(()) + } + + fn post_build( + &self, + index: usize, + tx_builder: &mut TransactionBuilder, + _context: &dyn HandlerContext, + ) -> Result { + if let Some(output) = tx_builder.get_outputs().get(index) { + if let Some(type_) = output.type_().to_opt() { + if self.is_match(&type_) { + if type_.args().raw_data()[..] == [0u8; 32] { + let input = tx_builder + .get_inputs() + .get(0) + .ok_or(TxBuilderError::InvalidInputIndex(0))?; + let args = calculate_type_id(input, index as u64); + let type_ = type_ + .as_builder() + .args(bytes::Bytes::from(args.to_vec()).pack()) + .build(); + let output = output + .clone() + .as_builder() + .type_(Some(type_).pack()) + .build(); + tx_builder.set_output(index, output); + } + return Ok(true); + } + } + } + Ok(false) + } +} diff --git a/src/transaction/mod.rs b/src/transaction/mod.rs index ac2b315f..cbce18c3 100644 --- a/src/transaction/mod.rs +++ b/src/transaction/mod.rs @@ -72,6 +72,7 @@ impl TransactionBuilderConfiguration { network, )?, ) as Box<_>, + Box::new(handler::typeid::TypeIdHandler) as Box<_>, ]; Ok(ret) } diff --git a/src/tx_builder/mod.rs b/src/tx_builder/mod.rs index e1164047..d1ff3470 100644 --- a/src/tx_builder/mod.rs +++ b/src/tx_builder/mod.rs @@ -71,6 +71,9 @@ pub enum TxBuilderError { #[error("can not find specifed output to put small change")] NoOutputForSmallChange, + #[error("invalid input index: `{0}`")] + InvalidInputIndex(usize), + #[error("other error: `{0}`")] Other(anyhow::Error), }