Ditch the input buffer (#2911)

* Pass input data via the scratch buffer.

Remove calls to ext_input_*.

* Fix tests and docs

* Bump the version.

* Remove ext_input_* from COMPLEXITY.md

* Return back the length check and add more comments

* Update the documentation of the scratch buffer.

* Fix a silly mistake.
This commit is contained in:
Sergei Pepyakin
2019-06-20 17:06:12 +02:00
committed by GitHub
parent 32a14ba2b8
commit 83d3881542
6 changed files with 89 additions and 88 deletions
+67 -30
View File
@@ -527,8 +527,8 @@ const CODE_SET_RENT: &str = r#"
(import "env" "ext_dispatch_call" (func $ext_dispatch_call (param i32 i32)))
(import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32 i32)))
(import "env" "ext_set_rent_allowance" (func $ext_set_rent_allowance (param i32 i32)))
(import "env" "ext_input_size" (func $ext_input_size (result i32)))
(import "env" "ext_input_copy" (func $ext_input_copy (param i32 i32 i32)))
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
(import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32)))
(import "env" "memory" (memory 1 1))
;; insert a value of 4 bytes into storage
@@ -575,7 +575,7 @@ const CODE_SET_RENT: &str = r#"
(func (export "call")
(local $input_size i32)
(set_local $input_size
(call $ext_input_size)
(call $ext_scratch_size)
)
(block $IF_ELSE
(block $IF_2
@@ -602,16 +602,16 @@ const CODE_SET_RENT: &str = r#"
;; Set call set_rent_allowance with input
(func (export "deploy")
(local $input_size i32)
(set_local $input_size
(call $ext_scratch_size)
)
(call $ext_set_storage
(i32.const 0)
(i32.const 1)
(i32.const 0)
(i32.const 4)
)
(set_local $input_size
(call $ext_input_size)
)
(call $ext_input_copy
(call $ext_scratch_copy
(i32.const 0)
(i32.const 0)
(get_local $input_size)
@@ -631,7 +631,7 @@ const CODE_SET_RENT: &str = r#"
"#;
// Use test_hash_and_code test to get the actual hash if the code changed.
const HASH_SET_RENT: [u8; 32] = hex!("21d6b1d59aa6038fcad632488e9026893a1bbb48581774c771b8f24320697f05");
const HASH_SET_RENT: [u8; 32] = hex!("69aedfb4f6c1c398e97f8a5204de0f95ad5e7dc3540960beab11a86c569fbfcf");
/// Input data for each call in set_rent code
mod call {
@@ -1041,8 +1041,12 @@ const CODE_RESTORATION: &str = r#"
(func (export "call")
(call $ext_dispatch_call
(i32.const 200) ;; Pointer to the start of encoded call buffer
(i32.const 115) ;; Length of the buffer
;; Pointer to the start of the encoded call buffer
(i32.const 200)
;; The length of the encoded call buffer.
;;
;; NB: This is required to keep in sync with the values in `restoration`.
(i32.const 115)
)
)
(func (export "deploy")
@@ -1069,16 +1073,17 @@ const CODE_RESTORATION: &str = r#"
;; ACL
(data (i32.const 100) "\01")
;; Call
(data (i32.const 200) "\01\05\02\00\00\00\00\00\00\00\21\d6\b1\d5\9a\a6\03\8f\ca\d6\32\48\8e\90"
"\26\89\3a\1b\bb\48\58\17\74\c7\71\b8\f2\43\20\69\7f\05\32\00\00\00\00\00\00\00\08\01\00\00"
"\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\01"
"\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00"
"\00"
;; Serialized version of `T::Call` that encodes a call to `restore_to` function. For more
;; details check out the `ENCODED_CALL_LITERAL`.
(data (i32.const 200)
"\01\05\02\00\00\00\00\00\00\00\69\ae\df\b4\f6\c1\c3\98\e9\7f\8a\52\04\de\0f\95\ad\5e\7d\c3"
"\54\09\60\be\ab\11\a8\6c\56\9f\bf\cf\32\00\00\00\00\00\00\00\08\01\00\00\00\00\00\00\00\00"
"\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\01\00\00\00\00\00\00"
"\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00"
)
)
"#;
const HASH_RESTORATION: [u8; 32] = hex!("b393bfa8de97b02f08ba1580b46100a80c9489a97c8d870691c9ff7236b29bc7");
const HASH_RESTORATION: [u8; 32] = hex!("02988182efba70fe605031f5c55bfa59e47f72c0a4707f22b6b74fffbf7803dc");
#[test]
fn restorations_dirty_storage_and_different_storage() {
@@ -1116,12 +1121,25 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage:
vec![acl_key, acl_key],
))));
let literal = "0105020000000000000021d6b1d59aa6038fcad632488e9026893a1bbb48581774c771b8f243206\
97f053200000000000000080100000000000000000000000000000000000000000000000000000000000000010\
0000000000000000000000000000000000000000000000000000000000000";
assert_eq!(encoded, literal);
assert_eq!(115, hex::decode(literal).unwrap().len());
// `ENCODED_CALL_LITERAL` is encoded `T::Call` represented as a byte array. There is an exact
// same copy of this (modulo hex notation differences) in `CODE_RESTORATION`.
//
// When this assert is triggered make sure that you update the literals here and in
// `CODE_RESTORATION`. Hopefully, we switch to automatical injection of the code.
const ENCODED_CALL_LITERAL: &str =
"0105020000000000000069aedfb4f6c1c398e97f8a5204de0f95ad5e7dc3540960beab11a86c569fbfcf320000\
0000000000080100000000000000000000000000000000000000000000000000000000000000010000000000000\
0000000000000000000000000000000000000000000000000";
assert_eq!(
encoded,
ENCODED_CALL_LITERAL,
"The literal was changed and requires updating here and in `CODE_RESTORATION`",
);
assert_eq!(
hex::decode(ENCODED_CALL_LITERAL).unwrap().len(),
115,
"The size of the literal was changed and requires updating in `CODE_RESTORATION`",
);
let restoration_wasm = wabt::wat2wasm(CODE_RESTORATION).unwrap();
let set_rent_wasm = wabt::wat2wasm(CODE_SET_RENT).unwrap();
@@ -1153,14 +1171,18 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage:
},
]);
// Create an account with address `BOB` with code `HASH_SET_RENT`.
// The input parameter sets the rent allowance to 0.
assert_ok!(Contract::create(
Origin::signed(ALICE),
30_000,
100_000, HASH_SET_RENT.into(),
100_000,
HASH_SET_RENT.into(),
<Test as balances::Trait>::Balance::from(0u32).encode()
));
// Check creation
// Check if `BOB` was created successfully and that the rent allowance is
// set to 0.
let bob_contract = ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap();
assert_eq!(bob_contract.rent_allowance, 0);
@@ -1172,36 +1194,49 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage:
);
}
// Advance 4 blocks
// Advance 4 blocks, to the 5th.
System::initialize(&5, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default());
// Trigger rent through call
// Call `BOB`, which makes it pay rent. Since the rent allowance is set to 0
// we expect that it will get removed leaving tombstone.
assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()));
assert!(ContractInfoOf::<Test>::get(BOB).unwrap().get_tombstone().is_some());
/// Create another account with the address `DJANGO` with `CODE_RESTORATION`.
///
/// Note that we can't use `ALICE` for creating `DJANGO` so we create yet another
/// account `CHARLIE` and create `DJANGO` with it.
Balances::deposit_creating(&CHARLIE, 1_000_000);
assert_ok!(Contract::create(
Origin::signed(CHARLIE),
30_000,
100_000, HASH_RESTORATION.into(),
100_000,
HASH_RESTORATION.into(),
<Test as balances::Trait>::Balance::from(0u32).encode()
));
// Before performing a call to `DJANGO` save its original trie id.
let django_trie_id = ContractInfoOf::<Test>::get(DJANGO).unwrap()
.get_alive().unwrap().trie_id;
if !test_restore_to_with_dirty_storage {
// Advance 1 blocks
// Advance 1 block, to the 6th.
System::initialize(&6, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default());
}
// Perform a call to `DJANGO`. This should either perform restoration successfully or
// fail depending on the test parameters.
assert_ok!(Contract::call(
Origin::signed(ALICE),
DJANGO, 0, 100_000,
DJANGO,
0,
100_000,
vec![],
));
if test_different_storage || test_restore_to_with_dirty_storage {
// Parametrization of the test imply restoration failure. Check that `DJANGO` aka
// restoration contract is still in place and also that `BOB` doesn't exist.
assert!(ContractInfoOf::<Test>::get(BOB).unwrap().get_tombstone().is_some());
let django_contract = ContractInfoOf::<Test>::get(DJANGO).unwrap()
.get_alive().unwrap();
@@ -1209,6 +1244,8 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage:
assert_eq!(django_contract.trie_id, django_trie_id);
assert_eq!(django_contract.deduct_block, System::block_number());
} else {
// Here we expect that the restoration is succeeded. Check that the restoration
// contract `DJANGO` ceased to exist and that `BOB` returned back.
let bob_contract = ContractInfoOf::<Test>::get(BOB).unwrap()
.get_alive().unwrap();
assert_eq!(bob_contract.rent_allowance, 50);
+1 -1
View File
@@ -134,7 +134,7 @@ impl<'a, T: Trait> crate::exec::Vm<T> for WasmVm<'a, T> {
let mut runtime = Runtime::new(
ext,
input_data,
input_data.to_vec(),
empty_output_buf,
&self.schedule,
memory,
+13 -38
View File
@@ -39,9 +39,8 @@ enum SpecialTrap {
}
/// Can only be used for one call.
pub(crate) struct Runtime<'a, 'data, E: Ext + 'a> {
pub(crate) struct Runtime<'a, E: Ext + 'a> {
ext: &'a mut E,
input_data: &'data [u8],
// A VM can return a result only once and only by value. So
// we wrap output buffer to make it possible to take the buffer out.
empty_output_buf: Option<EmptyOutputBuf>,
@@ -51,10 +50,10 @@ pub(crate) struct Runtime<'a, 'data, E: Ext + 'a> {
gas_meter: &'a mut GasMeter<E::T>,
special_trap: Option<SpecialTrap>,
}
impl<'a, 'data, E: Ext + 'a> Runtime<'a, 'data, E> {
impl<'a, E: Ext + 'a> Runtime<'a, E> {
pub(crate) fn new(
ext: &'a mut E,
input_data: &'data [u8],
input_data: Vec<u8>,
empty_output_buf: EmptyOutputBuf,
schedule: &'a Schedule<<E::T as Trait>::Gas>,
memory: sandbox::Memory,
@@ -62,9 +61,9 @@ impl<'a, 'data, E: Ext + 'a> Runtime<'a, 'data, E> {
) -> Self {
Runtime {
ext,
input_data,
empty_output_buf: Some(empty_output_buf),
scratch_buf: Vec::new(),
// Put the input data into the scratch buffer immediately.
scratch_buf: input_data,
schedule,
memory,
gas_meter,
@@ -443,6 +442,8 @@ define_env!(Env, <E: Ext>,
// Save a data buffer as a result of the execution, terminate the execution and return a
// successful result to the caller.
//
// This is the only way to return a data buffer to the caller.
ext_return(ctx, data_ptr: u32, data_len: u32) => {
match ctx
.gas_meter
@@ -573,45 +574,19 @@ define_env!(Env, <E: Ext>,
Ok(())
},
// Returns the size of the input buffer.
ext_input_size(ctx) -> u32 => {
Ok(ctx.input_data.len() as u32)
},
// Copy data from the input buffer starting from `offset` with length `len` into the contract memory.
// The region at which the data should be put is specified by `dest_ptr`.
ext_input_copy(ctx, dest_ptr: u32, offset: u32, len: u32) => {
let offset = offset as usize;
if offset > ctx.input_data.len() {
// Offset can't be larger than input buffer length.
return Err(sandbox::HostError);
}
// This can't panic since `offset <= ctx.input_data.len()`.
let src = &ctx.input_data[offset..];
if src.len() != len as usize {
return Err(sandbox::HostError);
}
// Finally, perform the write.
write_sandbox_memory(
ctx.schedule,
ctx.gas_meter,
&ctx.memory,
dest_ptr,
src,
)?;
Ok(())
},
// Returns the size of the scratch buffer.
//
// For more details on the scratch buffer see `ext_scratch_copy`.
ext_scratch_size(ctx) -> u32 => {
Ok(ctx.scratch_buf.len() as u32)
},
// Copy data from the scratch buffer starting from `offset` with length `len` into the contract memory.
// The region at which the data should be put is specified by `dest_ptr`.
//
// In order to get size of the scratch buffer use `ext_scratch_size`. At the start of contract
// execution, the scratch buffer is filled with the input data. Whenever a contract calls
// function that uses the scratch buffer the contents of the scratch buffer are overwritten.
ext_scratch_copy(ctx, dest_ptr: u32, offset: u32, len: u32) => {
let offset = offset as usize;
if offset > ctx.scratch_buf.len() {