Skip to content

Commit

Permalink
refactor: VM syscall refactoring
Browse files Browse the repository at this point in the history
After this change, we only have the following syscalls:

* `LOAD_TX` which loads transaction metadata
* `LOAD_CELL` which loads a cell as a whole
* `LOAD_CELL_BY_FIELD` which loads a single field in a cell
* `DEBUG` which prints debug information for contract debugging

`LOAD_CELL`/`LOAD_CELL_BY_FIELD` can be configured to load input cell,
output cell or current cell. Note that with `LOAD_CELL_BY_FIELD`, we
won't need separate fetch script hash syscalls anymore.
  • Loading branch information
xxuejie committed Dec 11, 2018
1 parent 313b2ea commit 9573905
Show file tree
Hide file tree
Showing 8 changed files with 486 additions and 520 deletions.
1 change: 1 addition & 0 deletions script/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ edition = "2018"

[dependencies]
numext-fixed-hash = { version = "0.1", features = ["support_rand", "support_heapsize", "support_serde"] }
byteorder = "1.2.2"
crypto = {path = "../util/crypto"}
ckb-core = { path = "../core" }
serde = "1.0"
Expand Down
81 changes: 81 additions & 0 deletions script/src/syscalls/load_cell.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use crate::syscalls::{Source, LOAD_CELL_SYSCALL_NUMBER, SUCCESS};
use ckb_core::transaction::CellOutput;
use ckb_protocol::CellOutput as FbsCellOutput;
use ckb_vm::{CoreMachine, Error as VMError, Memory, Register, Syscalls, A0, A1, A2, A3, A4, A7};
use flatbuffers::FlatBufferBuilder;
use std::cmp;

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

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

fn fetch_cell(&self, source: Source, index: usize) -> Option<&CellOutput> {
match source {
Source::Input => self.input_cells.get(index).cloned(),
Source::Output => self.outputs.get(index).cloned(),
Source::Current => Some(self.current),
}
}
}

impl<'a, R: Register, M: Memory> Syscalls<R, M> for LoadCell<'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() != LOAD_CELL_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()[A3].to_usize();
let source = Source::parse_from_u64(machine.registers()[A4].to_u64())?;

let cell = self
.fetch_cell(source, index)
.ok_or_else(|| VMError::OutOfBound)?;

// NOTE: this is a very expensive operation here since we need to copy
// everything in a cell to a flatbuffer object, serialize the object
// into a buffer, and then copy requested data to VM memory space. So
// we should charge cycles proportional to the full Cell size no matter
// how much data the actual script is requesting, the per-byte cycle charged
// here, should also be significantly higher than LOAD_CELL_BY_FIELD.
// Also, while this is debatable, I suggest we charge full cycles for
// subsequent calls even if we have cache implemented here.
// TODO: find a way to cache this without consuming too much memory
let mut builder = FlatBufferBuilder::new();
let offset = FbsCellOutput::build(&mut builder, cell);
builder.finish(offset, None);
let data = builder.finished_data();

let offset = machine.registers()[A2].to_usize();
let real_size = cmp::min(size, data.len() - offset);
machine.memory_mut().store64(size_addr, real_size as u64)?;
machine
.memory_mut()
.store_bytes(addr, &data[offset..offset + real_size])?;
machine.registers_mut()[A0] = R::from_u8(SUCCESS);
Ok(true)
}
}
112 changes: 112 additions & 0 deletions script/src/syscalls/load_cell_by_field.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
use byteorder::{LittleEndian, WriteBytesExt};
use ckb_core::transaction::CellOutput;
use ckb_protocol::Script as FbsScript;
use crate::syscalls::{Field, Source, ITEM_MISSING, LOAD_CELL_BY_FIELD_SYSCALL_NUMBER, SUCCESS};
use ckb_vm::{
CoreMachine, Error as VMError, Memory, Register, Syscalls, A0, A1, A2, A3, A4, A5, A7,
};
use flatbuffers::FlatBufferBuilder;
use std::cmp;

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

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

fn fetch_cell(&self, source: Source, index: usize) -> Option<&CellOutput> {
match source {
Source::Input => self.input_cells.get(index).cloned(),
Source::Output => self.outputs.get(index).cloned(),
Source::Current => Some(self.current),
}
}
}

fn store_data<R: Register, M: Memory>(
machine: &mut CoreMachine<R, M>,
data: &[u8],
) -> Result<(), VMError> {
let addr = machine.registers()[A0].to_usize();
let size_addr = machine.registers()[A1].to_usize();
let offset = machine.registers()[A2].to_usize();

let size = machine.memory_mut().load64(size_addr)? as usize;
let real_size = cmp::min(size, data.len() - offset);
machine.memory_mut().store64(size_addr, real_size as u64)?;
machine
.memory_mut()
.store_bytes(addr, &data[offset..offset + real_size])?;
Ok(())
}

impl<'a, R: Register, M: Memory> Syscalls<R, M> for LoadCellByField<'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() != LOAD_CELL_BY_FIELD_SYSCALL_NUMBER {
return Ok(false);
}

let index = machine.registers()[A3].to_usize();
let source = Source::parse_from_u64(machine.registers()[A4].to_u64())?;
let field = Field::parse_from_u64(machine.registers()[A5].to_u64())?;

let cell = self
.fetch_cell(source, index)
.ok_or_else(|| VMError::OutOfBound)?;

let return_code = match field {
Field::Capacity => {
let mut buffer = vec![];
buffer.write_u64::<LittleEndian>(cell.capacity)?;
store_data(machine, &buffer)?;
SUCCESS
}
Field::Data => {
store_data(machine, &cell.data)?;
SUCCESS
}
Field::LockHash => {
store_data(machine, &cell.lock.as_bytes())?;
SUCCESS
}
Field::Contract => match cell.contract {
Some(ref contract) => {
let mut builder = FlatBufferBuilder::new();
let offset = FbsScript::build(&mut builder, &contract);
builder.finish(offset, None);
let data = builder.finished_data();
store_data(machine, data)?;
SUCCESS
}
None => ITEM_MISSING,
},
Field::ContractHash => match cell.contract {
Some(ref contract) => {
store_data(machine, &contract.type_hash().as_bytes())?;
SUCCESS
}
None => ITEM_MISSING,
},
};
machine.registers_mut()[A0] = R::from_u8(return_code);
Ok(true)
}
}
40 changes: 40 additions & 0 deletions script/src/syscalls/load_tx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use ckb_vm::{CoreMachine, Error as VMError, Memory, Register, Syscalls, A0, A1, A2, A7};
use std::cmp;
use syscalls::{LOAD_TX_SYSCALL_NUMBER, SUCCESS};

pub struct LoadTx<'a> {
tx: &'a [u8],
}

impl<'a> LoadTx<'a> {
pub fn new(tx: &'a [u8]) -> LoadTx<'a> {
LoadTx { tx }
}
}

impl<'a, R: Register, M: Memory> Syscalls<R, M> for LoadTx<'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() != LOAD_TX_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 data = self.tx;

let offset = machine.registers()[A2].to_usize();
let real_size = cmp::min(size, data.len() - offset);
machine.memory_mut().store64(size_addr, real_size as u64)?;
machine
.memory_mut()
.store_bytes(addr, &data[offset..offset + real_size])?;
machine.registers_mut()[A0] = R::from_u8(SUCCESS);
Ok(true)
}
}
82 changes: 0 additions & 82 deletions script/src/syscalls/mmap_cell.rs

This file was deleted.

62 changes: 0 additions & 62 deletions script/src/syscalls/mmap_tx.rs

This file was deleted.

Loading

0 comments on commit 9573905

Please sign in to comment.