Skip to content

Commit

Permalink
Merge pull request #77 from quake/quake/type-id-handler
Browse files Browse the repository at this point in the history
feat: add TypeId handler
  • Loading branch information
quake authored May 25, 2023
2 parents 0fa2b43 + f14683a commit 302db5d
Show file tree
Hide file tree
Showing 11 changed files with 235 additions and 10 deletions.
82 changes: 82 additions & 0 deletions examples/deploy_script_with_type_id.rs
Original file line number Diff line number Diff line change
@@ -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<dyn StdErr>> {
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())
}
2 changes: 1 addition & 1 deletion examples/send_ckb_example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fn main() -> Result<(), Box<dyn StdErr>> {
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);
Expand Down
1 change: 1 addition & 0 deletions src/tests/transaction/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod sighash;
pub mod typeid;
68 changes: 68 additions & 0 deletions src/tests/transaction/typeid.rs
Original file line number Diff line number Diff line change
@@ -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();
}
4 changes: 2 additions & 2 deletions src/transaction/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,12 +166,12 @@ impl CkbTransactionBuilder for SimpleTransactionBuilder {
}

// handle script groups
let script_groups = lock_groups
let mut script_groups: Vec<ScriptGroup> = 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())? {
Expand Down
10 changes: 7 additions & 3 deletions src/transaction/handler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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<bool, TxBuilderError>;

Expand All @@ -43,15 +44,18 @@ 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),
],
}
}
}

impl HandlerContexts {
pub fn new_sighash() -> Self {
Self {
contexts: vec![Box::new(Secp256k1Blake160SighashAllScriptContext {})],
contexts: vec![Box::new(Secp256k1Blake160SighashAllScriptContext)],
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/transaction/handler/multisig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool, TxBuilderError> {
if !self.is_match(&script_group.script) {
Expand Down
4 changes: 2 additions & 2 deletions src/transaction/handler/sighash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub struct Secp256k1Blake160SighashAllScriptHandler {
cell_deps: Vec<CellDep>,
}

pub struct Secp256k1Blake160SighashAllScriptContext {}
pub struct Secp256k1Blake160SighashAllScriptContext;

impl HandlerContext for Secp256k1Blake160SighashAllScriptContext {}

Expand All @@ -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<bool, TxBuilderError> {
if !self.is_match(&script_group.script) {
Expand Down
66 changes: 66 additions & 0 deletions src/transaction/handler/typeid.rs
Original file line number Diff line number Diff line change
@@ -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<bool, TxBuilderError> {
if context.as_any().is::<TypeIdContext>()
&& 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
}
1 change: 1 addition & 0 deletions src/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ impl TransactionBuilderConfiguration {
network,
)?,
) as Box<_>,
Box::new(handler::typeid::TypeIdHandler) as Box<_>,
];
Ok(ret)
}
Expand Down
5 changes: 4 additions & 1 deletion src/types/script_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
Expand Down

0 comments on commit 302db5d

Please sign in to comment.