Skip to content

Commit

Permalink
feat: Add block level script cycle limit
Browse files Browse the repository at this point in the history
  • Loading branch information
xxuejie committed Jan 4, 2019
1 parent a1bdf98 commit 22adb37
Show file tree
Hide file tree
Showing 14 changed files with 205 additions and 42 deletions.
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ pub use crate::error::Error;
pub type PublicKey = numext_fixed_hash::H512;
pub type BlockNumber = u64;
pub type Capacity = u64;
pub type Cycle = u64;
3 changes: 2 additions & 1 deletion nodes_template/spec/dev.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
"uncles_hash": "0x0000000000000000000000000000000000000000000000000000000000000000"
},
"params": {
"initial_block_reward": 50000
"initial_block_reward": 50000,
"max_block_cycles": 100000000
},
"system_cells": [
{"path": "cells/always_success"}
Expand Down
5 changes: 3 additions & 2 deletions pool/src/txs_pool/pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ where
if unknowns.is_empty() {
// TODO: Parallel
TransactionVerifier::new(&rtx)
.verify()
.verify(self.shared.consensus().max_block_cycles())
.map_err(PoolError::InvalidTx)?;
}
}
Expand All @@ -480,7 +480,8 @@ where

for tx in txs {
let rtx = self.resolve_transaction(&tx);
let rs = TransactionVerifier::new(&rtx).verify();
let rs =
TransactionVerifier::new(&rtx).verify(self.shared.consensus().max_block_cycles());
if rs.is_ok() {
self.pool.add_transaction(tx);
} else if rs == Err(TransactionError::DoubleSpent) {
Expand Down
2 changes: 1 addition & 1 deletion script/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ byteorder = "1.2.2"
crypto = {path = "../util/crypto"}
ckb-core = { path = "../core" }
hash = {path = "../util/hash"}
ckb-vm = { git = "https://github.com/nervosnetwork/ckb-vm", rev = "f32bdcb" }
ckb-vm = { git = "https://github.com/nervosnetwork/ckb-vm", rev = "dd90ed1" }
faster-hex = "0.3"
fnv = "1.0.3"
flatbuffers = "0.5.0"
Expand Down
79 changes: 79 additions & 0 deletions script/src/cost_model.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use ckb_vm::{
instructions::{i, m, rvc},
Instruction,
};

pub fn instruction_cycles(i: &Instruction) -> u64 {
match i {
Instruction::I(i) => match i {
i::Instruction::I(i) => match i.inst() {
i::ItypeInstruction::JALR => 3,
i::ItypeInstruction::LD => 2,
i::ItypeInstruction::LW => 3,
i::ItypeInstruction::LH => 3,
i::ItypeInstruction::LB => 3,
i::ItypeInstruction::LWU => 3,
i::ItypeInstruction::LHU => 3,
i::ItypeInstruction::LBU => 3,
_ => 1,
},
i::Instruction::S(s) => match s.inst() {
// Here we choose to be explicit so as to avoid potential confusions.
i::StypeInstruction::SB => 3,
i::StypeInstruction::SH => 3,
i::StypeInstruction::SW => 3,
i::StypeInstruction::SD => 2,
},
i::Instruction::B(_) => 3,
// Cycles for Env instructions will be processed in the Env code.
i::Instruction::Env(_) => 0,
i::Instruction::JAL { .. } => 3,
_ => 1,
},
Instruction::RVC(i) => match i {
rvc::Instruction::Iu(i) => match i.inst() {
rvc::ItypeUInstruction::LW => 3,
rvc::ItypeUInstruction::LD => 2,
_ => 1,
},
rvc::Instruction::Su(s) => match s.inst() {
rvc::StypeUInstruction::SW => 3,
rvc::StypeUInstruction::SD => 2,
_ => 1,
},
rvc::Instruction::Uu(u) => match u.inst() {
rvc::UtypeUInstruction::LWSP => 3,
rvc::UtypeUInstruction::LDSP => 2,
_ => 1,
},
rvc::Instruction::CSS(c) => match c.inst() {
rvc::CSSformatInstruction::SWSP => 3,
rvc::CSSformatInstruction::SDSP => 2,
_ => 1,
},
rvc::Instruction::BEQZ { .. } => 3,
rvc::Instruction::BNEZ { .. } => 3,
rvc::Instruction::JAL { .. } => 3,
rvc::Instruction::J { .. } => 3,
rvc::Instruction::JR { .. } => 3,
rvc::Instruction::JALR { .. } => 3,
rvc::Instruction::EBREAK => 0,
_ => 1,
},
Instruction::M(m::Instruction(i)) => match i.inst() {
m::RtypeInstruction::MUL => 5,
m::RtypeInstruction::MULW => 5,
m::RtypeInstruction::MULH => 5,
m::RtypeInstruction::MULHU => 5,
m::RtypeInstruction::MULHSU => 5,
m::RtypeInstruction::DIV => 16,
m::RtypeInstruction::DIVW => 16,
m::RtypeInstruction::DIVU => 16,
m::RtypeInstruction::DIVUW => 16,
m::RtypeInstruction::REM => 16,
m::RtypeInstruction::REMW => 16,
m::RtypeInstruction::REMU => 16,
m::RtypeInstruction::REMUW => 16,
},
}
}
1 change: 1 addition & 0 deletions script/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod cost_model;
mod syscalls;
mod verify;

Expand Down
86 changes: 70 additions & 16 deletions script/src/verify.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
use crate::syscalls::{build_tx, Debugger, LoadCell, LoadCellByField, LoadInputByField, LoadTx};
use crate::ScriptError;
use crate::{
cost_model::instruction_cycles,
syscalls::{build_tx, Debugger, LoadCell, LoadCellByField, LoadInputByField, LoadTx},
ScriptError,
};
use ckb_core::cell::ResolvedTransaction;
use ckb_core::script::Script;
use ckb_core::transaction::{CellInput, CellOutput};
use ckb_core::Cycle;
use ckb_protocol::{FlatbuffersVectorIterator, Script as FbsScript};
use ckb_vm::{DefaultMachine, SparseMemory};
use ckb_vm::{CoreMachine, DefaultMachine, SparseMemory};
use flatbuffers::{get_root, FlatBufferBuilder};
use fnv::FnvHashMap;
use log::info;
Expand Down Expand Up @@ -141,13 +145,17 @@ impl<'a> TransactionScriptsVerifier<'a> {
prefix: &str,
current_cell: &'a CellOutput,
current_input: Option<&'a CellInput>,
) -> Result<(), ScriptError> {
max_cycles: Cycle,
) -> Result<Cycle, ScriptError> {
let mut args = vec![b"verify".to_vec()];
self.extract_script(script, &mut args)
.and_then(|script_binary| {
args.extend_from_slice(&script.args.as_slice());

let mut machine = DefaultMachine::<u64, SparseMemory>::default();
let mut machine = DefaultMachine::<u64, SparseMemory>::new_with_cost_model(
Box::new(instruction_cycles),
max_cycles,
);
machine.add_syscall_module(Box::new(self.build_load_tx()));
machine.add_syscall_module(Box::new(self.build_load_cell(current_cell)));
machine.add_syscall_module(Box::new(self.build_load_cell_by_field(current_cell)));
Expand All @@ -158,32 +166,33 @@ impl<'a> TransactionScriptsVerifier<'a> {
.map_err(ScriptError::VMError)
.and_then(|code| {
if code == 0 {
Ok(())
Ok(machine.cycles())
} else {
Err(ScriptError::ValidationFailure(code))
}
})
})
}

pub fn verify(&self) -> Result<(), ScriptError> {
pub fn verify(&self, max_cycles: Cycle) -> Result<Cycle, ScriptError> {
let mut cycles = 0;
for (i, input) in self.inputs.iter().enumerate() {
let prefix = format!("Transaction {}, input {}", self.hash, i);
self.verify_script(&input.unlock, &prefix, self.input_cells[i], Some(input)).map_err(|e| {
cycles += self.verify_script(&input.unlock, &prefix, self.input_cells[i], Some(input), max_cycles - cycles).map_err(|e| {
info!(target: "script", "Error validating input {} of transaction {}: {:?}", i, self.hash, e);
e
})?;
}
for (i, output) in self.outputs.iter().enumerate() {
if let Some(ref type_) = output.type_ {
let prefix = format!("Transaction {}, output {}", self.hash, i);
self.verify_script(type_, &prefix, output, None).map_err(|e| {
cycles += self.verify_script(type_, &prefix, output, None, max_cycles - cycles).map_err(|e| {
info!(target: "script", "Error validating output {} of transaction {}: {:?}", i, self.hash, e);
e
})?;
}
}
Ok(())
Ok(cycles)
}
}

Expand Down Expand Up @@ -255,7 +264,52 @@ mod tests {

let verifier = TransactionScriptsVerifier::new(&rtx);

assert!(verifier.verify().is_ok());
assert!(verifier.verify(100000000).is_ok());
}

#[test]
fn check_signature_with_not_enough_cycles() {
let mut file = open_cell_verify();
let mut buffer = Vec::new();
file.read_to_end(&mut buffer).unwrap();

let gen = Generator::new();
let privkey = gen.random_privkey();
let mut args = vec![b"foo".to_vec(), b"bar".to_vec()];

let mut bytes = vec![];
for argument in &args {
bytes.write(argument).unwrap();
}
let hash1 = sha3_256(&bytes);
let hash2 = sha3_256(hash1);
let signature = privkey.sign_recoverable(&hash2.into()).unwrap();

let signature_der = signature.serialize_der();
let mut hex_signature = vec![0; signature_der.len() * 2];
hex_encode(&signature_der, &mut hex_signature).expect("hex privkey");
args.insert(0, hex_signature);

let privkey = privkey.pubkey().unwrap().serialize();
let mut hex_privkey = vec![0; privkey.len() * 2];
hex_encode(&privkey, &mut hex_privkey).expect("hex privkey");

let script = Script::new(0, args, None, Some(buffer), vec![hex_privkey]);
let input = CellInput::new(OutPoint::null(), script);

let transaction = TransactionBuilder::default().input(input.clone()).build();

let dummy_cell = CellOutput::new(100, vec![], H256::default(), None);

let rtx = ResolvedTransaction {
transaction,
dep_cells: vec![],
input_cells: vec![CellStatus::Live(dummy_cell)],
};

let verifier = TransactionScriptsVerifier::new(&rtx);

assert!(verifier.verify(100).is_err());
}

#[test]
Expand Down Expand Up @@ -302,7 +356,7 @@ mod tests {

let verifier = TransactionScriptsVerifier::new(&rtx);

assert!(verifier.verify().is_err());
assert!(verifier.verify(100000000).is_err());
}

#[test]
Expand Down Expand Up @@ -357,7 +411,7 @@ mod tests {

let verifier = TransactionScriptsVerifier::new(&rtx);

assert!(verifier.verify().is_ok());
assert!(verifier.verify(100000000).is_ok());
}

#[test]
Expand Down Expand Up @@ -408,7 +462,7 @@ mod tests {

let verifier = TransactionScriptsVerifier::new(&rtx);

assert!(verifier.verify().is_err());
assert!(verifier.verify(100000000).is_err());
}

fn create_always_success_script() -> Script {
Expand Down Expand Up @@ -465,7 +519,7 @@ mod tests {

let verifier = TransactionScriptsVerifier::new(&rtx);

assert!(verifier.verify().is_ok());
assert!(verifier.verify(100000000).is_ok());
}

#[test]
Expand Down Expand Up @@ -516,6 +570,6 @@ mod tests {

let verifier = TransactionScriptsVerifier::new(&rtx);

assert!(verifier.verify().is_err());
assert!(verifier.verify(100000000).is_err());
}
}
16 changes: 15 additions & 1 deletion spec/src/consensus.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use ckb_core::block::{Block, BlockBuilder};
use ckb_core::header::HeaderBuilder;
use ckb_core::transaction::Capacity;
use ckb_core::BlockNumber;
use ckb_core::{BlockNumber, Cycle};
use ckb_pow::{Pow, PowEngine};
use numext_fixed_uint::U256;
use std::sync::Arc;
Expand All @@ -20,6 +20,8 @@ pub const ORPHAN_RATE_TARGET: f32 = 0.1;
pub const POW_TIME_SPAN: u64 = 12 * 60 * 60 * 1000; // 12 hours
pub const POW_SPACING: u64 = 15 * 1000; //15s

pub const MAX_BLOCK_CYCLES: Cycle = 100000000;

#[derive(Clone, PartialEq, Debug)]
pub struct Consensus {
pub id: String,
Expand All @@ -40,6 +42,8 @@ pub struct Consensus {
pub cellbase_maturity: usize,
// This parameter indicates the count of past blocks used in the median time calculation
pub median_time_block_count: usize,
// Maximum cycles that all the scripts in all the commit transactions can take
pub max_block_cycles: Cycle,
}

// genesis difficulty should not be zero
Expand All @@ -63,6 +67,7 @@ impl Default for Consensus {
verification: true,
cellbase_maturity: CELLBASE_MATURITY,
median_time_block_count: MEDIAN_TIME_BLOCK_COUNT,
max_block_cycles: MAX_BLOCK_CYCLES,
}
}
}
Expand Down Expand Up @@ -93,6 +98,11 @@ impl Consensus {
self
}

pub fn set_max_block_cycles(mut self, max_block_cycles: Cycle) -> Self {
self.max_block_cycles = max_block_cycles;
self
}

pub fn genesis_block(&self) -> &Block {
&self.genesis_block
}
Expand Down Expand Up @@ -132,4 +142,8 @@ impl Consensus {
pub fn median_time_block_count(&self) -> usize {
self.median_time_block_count
}

pub fn max_block_cycles(&self) -> Cycle {
self.max_block_cycles
}
}
Loading

0 comments on commit 22adb37

Please sign in to comment.