Skip to content

Commit

Permalink
feat: vm syscall adjustments
Browse files Browse the repository at this point in the history
* Remove script data from mmap_tx syscall
* Add new syscall to return script type hash for any lock/contract scripts
  • Loading branch information
xxuejie authored and doitian committed Nov 19, 2018
1 parent b0aaaae commit 99be228
Show file tree
Hide file tree
Showing 5 changed files with 321 additions and 23 deletions.
19 changes: 15 additions & 4 deletions script/src/syscalls/builder.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use ckb_protocol::{
Bytes as FbsBytes, CellInput as FbsCellInput, CellOutput as FbsCellOutput, CellOutputBuilder,
OutPoint as FbsOutPoint, Transaction as FbsTransaction, TransactionBuilder,
Bytes as FbsBytes, CellInput as FbsCellInput, CellInputBuilder, CellOutput as FbsCellOutput,
CellOutputBuilder, OutPoint as FbsOutPoint, Transaction as FbsTransaction, TransactionBuilder,
};
use core::transaction::{CellOutput, Transaction};
use core::transaction::{CellInput, CellOutput, Transaction};
use flatbuffers::{FlatBufferBuilder, WIPOffset};

pub fn build_tx<'b>(
Expand All @@ -19,7 +19,7 @@ pub fn build_tx<'b>(
let vec = tx
.inputs()
.iter()
.map(|cell_input| FbsCellInput::build(fbb, cell_input))
.map(|cell_input| build_input(fbb, cell_input))
.collect::<Vec<_>>();
let inputs = fbb.create_vector(&vec);

Expand Down Expand Up @@ -48,3 +48,14 @@ fn build_output<'b>(
builder.add_lock(lock);
builder.finish()
}

fn build_input<'b>(
fbb: &mut FlatBufferBuilder<'b>,
input: &CellInput,
) -> WIPOffset<FbsCellInput<'b>> {
let hash = FbsBytes::build(fbb, &input.previous_output.hash);
let mut builder = CellInputBuilder::new(fbb);
builder.add_hash(hash);
builder.add_index(input.previous_output.index);
builder.finish()
}
87 changes: 87 additions & 0 deletions script/src/syscalls/fetch_script_hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use bigint::H256;
use core::transaction::{CellInput, CellOutput};
use syscalls::{
Category, Source, FETCH_SCRIPT_HASH_SYSCALL_NUMBER, ITEM_MISSING, OVERRIDE_LEN, SUCCESS,
};
use vm::{CoreMachine, Error as VMError, Memory, Register, Syscalls, A0, A1, A2, A3, A4, A7};

#[derive(Debug)]
pub struct FetchScriptHash<'a> {
outputs: &'a [&'a CellOutput],
inputs: &'a [&'a CellInput],
input_cells: &'a [&'a CellOutput],
}

impl<'a> FetchScriptHash<'a> {
pub fn new(
outputs: &'a [&'a CellOutput],
inputs: &'a [&'a CellInput],
input_cells: &'a [&'a CellOutput],
) -> FetchScriptHash<'a> {
FetchScriptHash {
outputs,
inputs,
input_cells,
}
}

fn fetch_hash(&self, source: Source, category: Category, index: usize) -> Option<H256> {
match (source, category) {
(Source::INPUT, Category::LOCK) => {
self.inputs.get(index).map(|input| input.unlock.type_hash())
}
(Source::INPUT, Category::CONTRACT) => {
self.input_cells.get(index).and_then(|input_cell| {
input_cell
.contract
.as_ref()
.map(|contract| contract.type_hash())
})
}
(Source::OUTPUT, Category::LOCK) => None,
(Source::OUTPUT, Category::CONTRACT) => self.outputs.get(index).and_then(|output| {
output
.contract
.as_ref()
.map(|contract| contract.type_hash())
}),
}
}
}

impl<'a, R: Register, M: Memory> Syscalls<R, M> for FetchScriptHash<'a> {
fn initialize(&mut self, _machine: &mut CoreMachine<R, M>) -> Result<(), VMError> {
Ok(())
}

fn ecall(&mut self, machine: &mut CoreMachine<R, M>) -> Result<bool, VMError> {
if machine.registers()[A7].to_u64() != FETCH_SCRIPT_HASH_SYSCALL_NUMBER {
return Ok(false);
}

let addr = machine.registers()[A0].to_usize();
let size_addr = machine.registers()[A1].to_usize();
let size = machine.memory_mut().load64(size_addr)? as usize;

let index = machine.registers()[A2].to_usize();
let source = Source::parse_from_u64(machine.registers()[A3].to_u64())?;
let category = Category::parse_from_u64(machine.registers()[A4].to_u64())?;

match self.fetch_hash(source, category, index) {
Some(hash) => {
let hash: &[u8] = &hash;
machine.memory_mut().store64(size_addr, hash.len() as u64)?;
if size >= hash.len() {
machine.memory_mut().store_bytes(addr, hash)?;
machine.registers_mut()[A0] = R::from_u8(SUCCESS);
} else {
machine.registers_mut()[A0] = R::from_u8(OVERRIDE_LEN);
}
}
None => {
machine.registers_mut()[A0] = R::from_u8(ITEM_MISSING);
}
};
Ok(true)
}
}
18 changes: 1 addition & 17 deletions script/src/syscalls/mmap_cell.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,10 @@
use core::transaction::CellOutput;
use std::cmp;
use std::rc::Rc;
use syscalls::{Mode, MMAP_CELL_SYSCALL_NUMBER, OVERRIDE_LEN, SUCCESS};
use syscalls::{Mode, Source, MMAP_CELL_SYSCALL_NUMBER, OVERRIDE_LEN, SUCCESS};
use vm::memory::PROT_READ;
use vm::{CoreMachine, Error as VMError, Memory, Register, Syscalls, A0, A1, A2, A3, A4, A5, A7};

#[derive(Debug, PartialEq, Clone, Copy, Eq)]
enum Source {
INPUT,
OUTPUT,
}

impl Source {
fn parse_from_u64(i: u64) -> Result<Source, VMError> {
match i {
0 => Ok(Source::INPUT),
1 => Ok(Source::OUTPUT),
_ => Err(VMError::ParseError),
}
}
}

#[derive(Debug)]
pub struct MmapCell<'a> {
outputs: &'a [&'a CellOutput],
Expand Down
213 changes: 212 additions & 1 deletion script/src/syscalls/mod.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
mod builder;
mod fetch_script_hash;
mod mmap_cell;
mod mmap_tx;

pub use self::builder::build_tx;
pub use self::fetch_script_hash::FetchScriptHash;
pub use self::mmap_cell::MmapCell;
pub use self::mmap_tx::MmapTx;

use vm::Error;

pub const SUCCESS: u8 = 0;
pub const OVERRIDE_LEN: u8 = 1;
pub const ITEM_MISSING: u8 = 2;

pub const MMAP_TX_SYSCALL_NUMBER: u64 = 2049;
pub const MMAP_CELL_SYSCALL_NUMBER: u64 = 2050;
pub const FETCH_SCRIPT_HASH_SYSCALL_NUMBER: u64 = 2051;

#[derive(Debug, PartialEq, Clone, Copy, Eq)]
pub enum Mode {
Expand All @@ -30,11 +34,44 @@ impl Mode {
}
}

#[derive(Debug, PartialEq, Clone, Copy, Eq)]
pub enum Category {
LOCK,
CONTRACT,
}

impl Category {
pub fn parse_from_u64(i: u64) -> Result<Category, Error> {
match i {
0 => Ok(Category::LOCK),
1 => Ok(Category::CONTRACT),
_ => Err(Error::ParseError),
}
}
}

#[derive(Debug, PartialEq, Clone, Copy, Eq)]
enum Source {
INPUT,
OUTPUT,
}

impl Source {
fn parse_from_u64(i: u64) -> Result<Source, Error> {
match i {
0 => Ok(Source::INPUT),
1 => Ok(Source::OUTPUT),
_ => Err(Error::ParseError),
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use bigint::H256;
use core::transaction::CellOutput;
use core::script::Script;
use core::transaction::{CellInput, CellOutput, OutPoint};
use proptest::collection::size_range;
use proptest::prelude::any_with;
use vm::machine::DefaultCoreMachine;
Expand Down Expand Up @@ -307,4 +344,178 @@ mod tests {
_test_mmap_cell_partial(data);
}
}

fn _test_fetch_script_hash_input_lock(data: Vec<u8>) {
let mut machine = DefaultCoreMachine::<u64, SparseMemory>::default();
let size_addr = 0;
let addr = 100;

machine.registers_mut()[A0] = addr; // addr
machine.registers_mut()[A1] = size_addr; // size_addr
machine.registers_mut()[A2] = 0; // index
machine.registers_mut()[A3] = 0; // source: 0 input
machine.registers_mut()[A4] = 0; // category: 0 lock
machine.registers_mut()[A7] = FETCH_SCRIPT_HASH_SYSCALL_NUMBER; // syscall number

assert!(machine.memory_mut().store64(size_addr as usize, 32).is_ok());

let script = Script::new(0, Vec::new(), None, Some(data), Vec::new());
let input = CellInput::new(OutPoint::default(), script.clone());
let inputs = vec![&input];
let input_cells = Vec::new();
let outputs = Vec::new();

let mut fetch_script_hash = FetchScriptHash::new(&outputs, &inputs, &input_cells);

assert!(fetch_script_hash.ecall(&mut machine).is_ok());
assert_eq!(machine.registers()[A0], SUCCESS as u64);

let hash = &script.type_hash();
for (i, addr) in (addr as usize..addr as usize + hash.len()).enumerate() {
assert_eq!(machine.memory_mut().load8(addr), Ok(hash[i]))
}
}

proptest! {
#[test]
fn test_fetch_script_hash_input_lock(data in any_with::<Vec<u8>>(size_range(1000).lift())) {
_test_fetch_script_hash_input_lock(data);
}
}

fn _test_fetch_script_hash_input_contract(data: Vec<u8>) {
let mut machine = DefaultCoreMachine::<u64, SparseMemory>::default();
let size_addr = 0;
let addr = 100;

machine.registers_mut()[A0] = addr; // addr
machine.registers_mut()[A1] = size_addr; // size_addr
machine.registers_mut()[A2] = 0; // index
machine.registers_mut()[A3] = 0; // source: 0 input
machine.registers_mut()[A4] = 1; // category: 1 contract
machine.registers_mut()[A7] = FETCH_SCRIPT_HASH_SYSCALL_NUMBER; // syscall number

assert!(machine.memory_mut().store64(size_addr as usize, 32).is_ok());

let script = Script::new(0, Vec::new(), None, Some(data), Vec::new());
let output = CellOutput::new(0, Vec::new(), H256::from(0), Some(script.clone()));
let inputs = Vec::new();
let input_cells = vec![&output];
let outputs = Vec::new();

let mut fetch_script_hash = FetchScriptHash::new(&outputs, &inputs, &input_cells);

assert!(fetch_script_hash.ecall(&mut machine).is_ok());
assert_eq!(machine.registers()[A0], SUCCESS as u64);

let hash = &script.type_hash();
for (i, addr) in (addr as usize..addr as usize + hash.len()).enumerate() {
assert_eq!(machine.memory_mut().load8(addr), Ok(hash[i]))
}
}

proptest! {
#[test]
fn test_fetch_script_hash_input_contract(data in any_with::<Vec<u8>>(size_range(1000).lift())) {
_test_fetch_script_hash_input_contract(data);
}
}

fn _test_fetch_script_hash_output_contract(data: Vec<u8>) {
let mut machine = DefaultCoreMachine::<u64, SparseMemory>::default();
let size_addr = 0;
let addr = 100;

machine.registers_mut()[A0] = addr; // addr
machine.registers_mut()[A1] = size_addr; // size_addr
machine.registers_mut()[A2] = 0; // index
machine.registers_mut()[A3] = 1; // source: 1 output
machine.registers_mut()[A4] = 1; // category: 1 contract
machine.registers_mut()[A7] = FETCH_SCRIPT_HASH_SYSCALL_NUMBER; // syscall number

assert!(machine.memory_mut().store64(size_addr as usize, 32).is_ok());

let script = Script::new(0, Vec::new(), None, Some(data), Vec::new());
let output = CellOutput::new(0, Vec::new(), H256::from(0), Some(script.clone()));
let inputs = Vec::new();
let input_cells = Vec::new();
let outputs = vec![&output];

let mut fetch_script_hash = FetchScriptHash::new(&outputs, &inputs, &input_cells);

assert!(fetch_script_hash.ecall(&mut machine).is_ok());
assert_eq!(machine.registers()[A0], SUCCESS as u64);

let hash = &script.type_hash();
for (i, addr) in (addr as usize..addr as usize + hash.len()).enumerate() {
assert_eq!(machine.memory_mut().load8(addr), Ok(hash[i]))
}
}

proptest! {
#[test]
fn test_fetch_script_hash_output_contract(data in any_with::<Vec<u8>>(size_range(1000).lift())) {
_test_fetch_script_hash_output_contract(data);
}
}

fn _test_fetch_script_hash_not_enough_space(data: Vec<u8>) {
let mut machine = DefaultCoreMachine::<u64, SparseMemory>::default();
let size_addr = 0;
let addr = 100;

machine.registers_mut()[A0] = addr; // addr
machine.registers_mut()[A1] = size_addr; // size_addr
machine.registers_mut()[A2] = 0; // index
machine.registers_mut()[A3] = 1; // source: 1 output
machine.registers_mut()[A4] = 1; // category: 1 contract
machine.registers_mut()[A7] = FETCH_SCRIPT_HASH_SYSCALL_NUMBER; // syscall number

assert!(machine.memory_mut().store64(size_addr as usize, 16).is_ok());

let script = Script::new(0, Vec::new(), None, Some(data), Vec::new());
let output = CellOutput::new(0, Vec::new(), H256::from(0), Some(script.clone()));
let inputs = Vec::new();
let input_cells = Vec::new();
let outputs = vec![&output];

let mut fetch_script_hash = FetchScriptHash::new(&outputs, &inputs, &input_cells);

assert!(fetch_script_hash.ecall(&mut machine).is_ok());
assert_eq!(machine.registers()[A0], OVERRIDE_LEN as u64);

assert_eq!(machine.memory_mut().load64(size_addr as usize), Ok(32));
}

proptest! {
#[test]
fn test_fetch_script_hash_not_enough_space(data in any_with::<Vec<u8>>(size_range(1000).lift())) {
_test_fetch_script_hash_not_enough_space(data);
}
}

#[test]
fn test_fetch_script_hash_missing_item() {
let mut machine = DefaultCoreMachine::<u64, SparseMemory>::default();
let size_addr = 0;
let addr = 100;

machine.registers_mut()[A0] = addr; // addr
machine.registers_mut()[A1] = size_addr; // size_addr
machine.registers_mut()[A2] = 0; // index
machine.registers_mut()[A3] = 1; // source: 1 output
machine.registers_mut()[A4] = 1; // category: 1 contract
machine.registers_mut()[A7] = FETCH_SCRIPT_HASH_SYSCALL_NUMBER; // syscall number

assert!(machine.memory_mut().store64(size_addr as usize, 16).is_ok());

let inputs = Vec::new();
let input_cells = Vec::new();
let outputs = Vec::new();

let mut fetch_script_hash = FetchScriptHash::new(&outputs, &inputs, &input_cells);

assert!(fetch_script_hash.ecall(&mut machine).is_ok());
assert_eq!(machine.registers()[A0], ITEM_MISSING as u64);
}
}
Loading

0 comments on commit 99be228

Please sign in to comment.