diff --git a/Cargo.lock b/Cargo.lock index e7b08b6137..31b7f3f027 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -316,6 +316,7 @@ dependencies = [ "ckb-shared 0.8.0-pre", "ckb-traits 0.8.0-pre", "criterion 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "hash 0.8.0-pre", "numext-fixed-hash 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "numext-fixed-uint 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -338,6 +339,7 @@ dependencies = [ "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "faketime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "hash 0.8.0-pre", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "lru-cache 0.1.0 (git+https://github.com/nervosnetwork/lru-cache)", "numext-fixed-hash 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -353,8 +355,6 @@ version = "0.8.0-pre" dependencies = [ "ckb-core 0.8.0-pre", "ckb-pow 0.8.0-pre", - "ckb-protocol 0.8.0-pre", - "flatbuffers 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "numext-fixed-hash 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "numext-fixed-uint 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", @@ -628,6 +628,7 @@ dependencies = [ "faketime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "flatbuffers 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "hash 0.8.0-pre", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "numext-fixed-hash 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "numext-fixed-uint 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/benches/Cargo.toml b/benches/Cargo.toml index a4870b2a06..d66b50df58 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -22,6 +22,7 @@ numext-fixed-uint = { version = "0.1", features = ["support_rand", "support_heap rand = "0.6" tempfile = "3.0" ckb-traits = { path = "../traits" } +hash = {path = "../util/hash"} [[bench]] name = "cuckoo" diff --git a/benches/benches/process_block.rs b/benches/benches/process_block.rs index 6dc843dadb..2740c45ab5 100644 --- a/benches/benches/process_block.rs +++ b/benches/benches/process_block.rs @@ -16,9 +16,6 @@ use criterion::{criterion_group, criterion_main, Criterion}; use numext_fixed_hash::H256; use numext_fixed_uint::U256; use rand::random; -use std::fs::File; -use std::io::Read; -use std::path::Path; use std::sync::Arc; use tempfile::{tempdir, TempDir}; @@ -139,17 +136,20 @@ fn new_chain() -> ( ) { let cellbase = TransactionBuilder::default() .input(CellInput::new_cellbase_input(0)) - .output(CellOutput::new(0, vec![], H256::zero(), None)) + .output(CellOutput::new(0, vec![], Script::default(), None)) .build(); - let script = create_script(); - // create genesis block with 100 tx let commit_transactions: Vec = (0..100) .map(|i| { TransactionBuilder::default() - .input(CellInput::new(OutPoint::null(), script.clone())) - .output(CellOutput::new(50000, vec![i], script.type_hash(), None)) + .input(CellInput::new(OutPoint::null(), vec![])) + .output(CellOutput::new( + 50000, + vec![i], + Script::always_success(), + None, + )) .build() }) .collect(); @@ -185,7 +185,7 @@ fn gen_block(blocks: &mut Vec, parent_index: usize) { let cellbase = TransactionBuilder::default() .input(CellInput::new_cellbase_input(number)) - .output(CellOutput::new(0, vec![], H256::zero(), None)) + .output(CellOutput::new(0, vec![], Script::default(), None)) .build(); // spent n-2 block's tx and proposal n-1 block's tx @@ -225,20 +225,13 @@ fn gen_block(blocks: &mut Vec, parent_index: usize) { } fn create_transaction(hash: H256) -> Transaction { - let script = create_script(); TransactionBuilder::default() - .output(CellOutput::new(50000, vec![], script.type_hash(), None)) - .input(CellInput::new(OutPoint::new(hash, 0), script)) + .output(CellOutput::new( + 50000, + vec![], + Script::always_success(), + None, + )) + .input(CellInput::new(OutPoint::new(hash, 0), vec![])) .build() } - -fn create_script() -> Script { - let mut file = File::open( - Path::new(env!("CARGO_MANIFEST_DIR")).join("../nodes_template/spec/cells/always_success"), - ) - .unwrap(); - let mut buffer = Vec::new(); - file.read_to_end(&mut buffer).unwrap(); - - Script::new(0, Vec::new(), None, Some(buffer), Vec::new()) -} diff --git a/chain/Cargo.toml b/chain/Cargo.toml index 17875ccb98..5a6690e20a 100644 --- a/chain/Cargo.toml +++ b/chain/Cargo.toml @@ -23,6 +23,7 @@ lru-cache = { git = "https://github.com/nervosnetwork/lru-cache" } serde = "1.0" ckb-traits = { path = "../traits" } failure = "0.1.5" +hash = {path = "../util/hash"} [dev-dependencies] env_logger = "0.6" diff --git a/chain/src/tests/basic.rs b/chain/src/tests/basic.rs index 3a0c9dac00..2493e6e896 100644 --- a/chain/src/tests/basic.rs +++ b/chain/src/tests/basic.rs @@ -4,10 +4,10 @@ use ckb_core::block::Block; use ckb_core::block::BlockBuilder; use ckb_core::cell::{CellProvider, CellStatus}; use ckb_core::header::HeaderBuilder; +use ckb_core::script::Script; use ckb_core::transaction::{CellInput, CellOutput, OutPoint, TransactionBuilder}; use ckb_shared::error::SharedError; use ckb_traits::ChainProvider; -use numext_fixed_hash::H256; use numext_fixed_uint::U256; use std::sync::Arc; @@ -19,7 +19,7 @@ fn test_genesis_transaction_spend() { CellOutput::new( 100_000_000, vec![], - H256::default(), + Script::default(), None ); 100 @@ -349,7 +349,7 @@ fn test_genesis_transaction_fetch() { CellOutput::new( 100_000_000, vec![], - H256::default(), + Script::default(), None ); 100 diff --git a/chain/src/tests/util.rs b/chain/src/tests/util.rs index 14517b5134..59a15503c1 100644 --- a/chain/src/tests/util.rs +++ b/chain/src/tests/util.rs @@ -15,9 +15,6 @@ use ckb_shared::store::ChainKVStore; use faketime::unix_time_as_millis; use numext_fixed_hash::H256; use numext_fixed_uint::U256; -use std::fs::File; -use std::io::Read; -use std::path::Path; pub(crate) fn start_chain( consensus: Option, @@ -42,7 +39,7 @@ fn create_cellbase(number: BlockNumber) -> Transaction { .output(CellOutput::new( 5000, vec![], - create_script().type_hash(), + Script::always_success(), None, )) .build() @@ -78,24 +75,13 @@ pub(crate) fn gen_block( } pub(crate) fn create_transaction(parent: H256, unique_data: u8) -> Transaction { - let script = create_script(); TransactionBuilder::default() .output(CellOutput::new( 5000, vec![unique_data], - script.type_hash(), + Script::always_success(), None, )) - .input(CellInput::new(OutPoint::new(parent, 0), script)) + .input(CellInput::new(OutPoint::new(parent, 0), vec![])) .build() } - -fn create_script() -> Script { - let mut file = File::open( - Path::new(env!("CARGO_MANIFEST_DIR")).join("../nodes_template/spec/cells/always_success"), - ) - .unwrap(); - let mut buffer = Vec::new(); - file.read_to_end(&mut buffer).unwrap(); - Script::new(0, Vec::new(), None, Some(buffer), Vec::new()) -} diff --git a/core/src/cell.rs b/core/src/cell.rs index 4640f406e6..6d4aaaeb6c 100644 --- a/core/src/cell.rs +++ b/core/src/cell.rs @@ -203,6 +203,7 @@ impl ResolvedTransaction { #[cfg(test)] mod tests { + use super::super::script::Script; use super::*; use numext_fixed_hash::H256; use std::collections::HashMap; @@ -241,7 +242,7 @@ mod tests { let o = CellOutput { capacity: 2, data: vec![], - lock: H256::default(), + lock: Script::default(), type_: None, }; diff --git a/core/src/script.rs b/core/src/script.rs index 83de334772..f9449151d1 100644 --- a/core/src/script.rs +++ b/core/src/script.rs @@ -7,60 +7,20 @@ use std::fmt; use std::io::Write; use std::mem; +pub const ALWAYS_SUCCESS_HASH: [u8; 32] = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, +]; + // TODO: when flatbuffer work is done, remove Serialize/Deserialize here and // implement proper From trait #[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct Script { pub version: u8, pub args: Vec>, - - // There're 2 ways of specifying script: one way is directly embed - // the script to run in binary part; however, a common use case is - // that CKB would provide common system cells containing common verification - // algorithm, such as P2SH-SHA3-SECP256K1, in the meantime, crypto experts might - // also put alternative advanced verfication algorithms on the chain. So another - // way of loading a script, is that reference can be used to specify - // an existing cell, when CKB runs the script, CKB will load the script from the - // existing cell. This has the benefit of promoting code reuse, and reducing - // transaction size: a typical secp256k1 verfication algorithm can take 1.2 MB - // in space, which is not ideal to put in every tx input. - // Note that the referenced cell here might also be included in transaction's - // deps part, otherwise CKB will fail to verify the script. - // CKB only enforces that reference and binary cannot both be - // None, when they both contains actual value(though this is not recommended), - // binary will be used. - // When calculating script type hash, reference, binary, - // and signed_args will all be included. - pub reference: Option, - pub binary: Option>, - // Pre-defined arguments that are considered part of the script. - // When signed_args contains , , and args contains , - // , , binary will then be executed with arguments , , - // , , . - // This can be useful when binary is fixed, but depending on different - // use case, we might have different initial parameters. For example, in - // secp256k1 verification, we need to provide pubkey first, this cannot be - // part of arguments, otherwise users can provide signatures signed by - // arbitrary private keys. On the other hand, include pubkey inside - // binary is not good for distribution, since the script here can - // be over 1 megabytes. So signed_args helps here to preserve one common - // binary, while enable us to provide different pubkeys for different - // transactions. - // For most verification algorithms, args will contain the signature - // and any additional parameters needed by cell validator, while - // signed_args will contain pubkey used in the signing part. - pub signed_args: Vec>, -} - -struct OptionDisplay(Option); - -impl fmt::Display for OptionDisplay { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.0 { - Some(ref v) => write!(f, "Some({})", v), - None => write!(f, "None"), - } - } + // Binary hash here can be used to refer to binary in one of the dep + // cells of current transaction. The hash here must match the hash of + // cell data so as to reference a dep cell. + pub binary_hash: H256, } fn prefix_hex(bytes: &[u8]) -> String { @@ -78,98 +38,47 @@ impl fmt::Debug for Script { .entries(self.args.iter().map(|arg| prefix_hex(arg))) .finish()?; - write!( - f, - ", reference: {}", - OptionDisplay( - self.reference - .as_ref() - .map(|reference| format!("{:#x}", reference)) - ) - )?; - - write!( - f, - ", binary: {}", - OptionDisplay(self.binary.as_ref().map(|binary| prefix_hex(binary))) - )?; - - write!(f, " , signed_args: ")?; - - f.debug_list() - .entries( - self.signed_args - .iter() - .map(|signed_arg| prefix_hex(signed_arg)), - ) - .finish()?; + write!(f, ", binary_hash: {:#x}", self.binary_hash,)?; write!(f, " }}") } } -type ScriptTuple = ( - u8, - Vec>, - Option, - Option>, - Vec>, -); +type ScriptTuple = (u8, Vec>, H256); const VEC_WRITE_ALL_EXPECT: &str = "Essentially, Vec::write_all invoke extend_from_slice, should not fail"; impl Script { - pub fn new( - version: u8, - args: Vec>, - reference: Option, - binary: Option>, - signed_args: Vec>, - ) -> Self { + pub fn new(version: u8, args: Vec>, binary_hash: H256) -> Self { Script { version, args, - reference, - binary, - signed_args, + binary_hash, } } + pub fn always_success() -> Self { + Self::new(0, vec![], H256(ALWAYS_SUCCESS_HASH)) + } + pub fn destruct(self) -> ScriptTuple { let Script { version, args, - reference, - binary, - signed_args, + binary_hash, } = self; - (version, args, reference, binary, signed_args) + (version, args, binary_hash) } - pub fn type_hash(&self) -> H256 { + pub fn hash(&self) -> H256 { match self.version { 0 => { let mut bytes = vec![]; - // TODO: switch to flatbuffer serialization once we - // can do stable serialization using flatbuffer. - if let Some(ref data) = self.reference { - bytes - .write_all(data.as_bytes()) - .expect(VEC_WRITE_ALL_EXPECT); - } - // A separator is used here to prevent the rare case - // that some binary might contain the exactly - // same data as reference. In this case we might - // still want to distinguish between the 2 script in - // the hash. Note this might not solve every problem, - // when flatbuffer change is done, we can leverage flatbuffer - // serialization directly, which will be more reliable. - bytes.write_all(b"|").expect(VEC_WRITE_ALL_EXPECT); - if let Some(ref data) = self.binary { - bytes.write_all(&data).expect(VEC_WRITE_ALL_EXPECT) - } - for argument in &self.signed_args { + bytes + .write_all(self.binary_hash.as_bytes()) + .expect(VEC_WRITE_ALL_EXPECT); + for argument in &self.args { bytes.write_all(argument).expect(VEC_WRITE_ALL_EXPECT); } blake2b_256(bytes).into() @@ -181,50 +90,43 @@ impl Script { impl OccupiedCapacity for Script { fn occupied_capacity(&self) -> usize { - mem::size_of::() - + self.args.occupied_capacity() - + self.reference.occupied_capacity() - + self.binary.occupied_capacity() - + self.signed_args.occupied_capacity() + mem::size_of::() + self.args.occupied_capacity() + self.binary_hash.occupied_capacity() } } #[cfg(test)] mod tests { use super::{Script, H256}; + use hash::blake2b_256; #[test] - fn empty_script_type_hash() { - let script = Script::new(0, vec![], None, None, vec![]); + fn empty_script_hash() { + let script = Script::new(0, vec![], H256::zero()); let expect = - H256::from_hex_str("4b29eb5168ba6f74bff824b15146246109c732626abd3c0578cbf147d8e28479") + H256::from_hex_str("266cec97cbede2cfbce73666f08deed9560bdf7841a7a5a51b3a3f09da249e21") .unwrap(); - assert_eq!(script.type_hash(), expect); + assert_eq!(script.hash(), expect); } #[test] - fn always_success_script_type_hash() { + fn always_success_script_hash() { let always_success = include_bytes!("../../nodes_template/spec/cells/always_success"); - let script = Script::new(0, vec![], None, Some(always_success.to_vec()), vec![]); + let always_success_hash: H256 = (&blake2b_256(&always_success[..])).into(); + + let script = Script::new(0, vec![], always_success_hash); let expect = - H256::from_hex_str("9f94d2511b787387638faa4a5bfd448baf21aa5fde3afaa54bb791188b5cf002") + H256::from_hex_str("9a9a6bdbc38d4905eace1822f85237e3a1e238bb3f277aa7b7c8903441123510") .unwrap(); - assert_eq!(script.type_hash(), expect); + assert_eq!(script.hash(), expect); } #[test] - fn one_script_type_hash() { - let one = Script::new( - 0, - vec![vec![1]], - Some(H256::zero()), - Some(vec![1]), - vec![vec![1]], - ); + fn one_script_hash() { + let one = Script::new(0, vec![vec![1]], H256::zero()); let expect = - H256::from_hex_str("afb140d0673571ed5710d220d6146d41bd8bc18a3a4ff723dad4331da5af5bb6") + H256::from_hex_str("dade0e507e27e2a5995cf39c8cf454b6e70fa80d03c1187db7a4cb2c9eab79da") .unwrap(); - assert_eq!(one.type_hash(), expect); + assert_eq!(one.hash(), expect); } } diff --git a/core/src/transaction.rs b/core/src/transaction.rs index c0b7b89d05..67820cf676 100644 --- a/core/src/transaction.rs +++ b/core/src/transaction.rs @@ -65,36 +65,30 @@ pub struct CellInput { pub previous_output: OutPoint, // Depends on whether the operation is Transform or Destroy, this is the proof to transform // lock or destroy lock. - pub unlock: Script, + pub args: Vec>, } impl CellInput { - pub fn new(previous_output: OutPoint, unlock: Script) -> Self { + pub fn new(previous_output: OutPoint, args: Vec>) -> Self { CellInput { previous_output, - unlock, + args, } } pub fn new_cellbase_input(block_number: BlockNumber) -> Self { CellInput { previous_output: OutPoint::null(), - unlock: Script::new( - 0, - Vec::new(), - None, - Some(block_number.to_le_bytes().to_vec()), - Vec::new(), - ), + args: vec![block_number.to_le_bytes().to_vec()], } } - pub fn destruct(self) -> (OutPoint, Script) { + pub fn destruct(self) -> (OutPoint, Vec>) { let CellInput { previous_output, - unlock, + args, } = self; - (previous_output, unlock) + (previous_output, args) } } @@ -103,7 +97,7 @@ pub struct CellOutput { pub capacity: Capacity, #[serde(with = "serde_bytes")] pub data: Vec, - pub lock: H256, + pub lock: Script, #[serde(rename = "type")] pub type_: Option