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
+6 -5
View File
@@ -711,22 +711,23 @@ mod tests {
;; input_data_len: u32
;; ) -> u32
(import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result 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))
(func (export "deploy")
)
(func (export "call")
(block $fail
;; fail if ext_input_size != 4
;; load and check the input data (which is stored in the scratch buffer).
;; fail if the input size is not != 4
(br_if $fail
(i32.ne
(i32.const 4)
(call $ext_input_size)
(call $ext_scratch_size)
)
)
(call $ext_input_copy
(call $ext_scratch_copy
(i32.const 0)
(i32.const 0)
(i32.const 4)
+2 -2
View File
@@ -58,8 +58,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("node"),
impl_name: create_runtime_str!("substrate-node"),
authoring_version: 10,
spec_version: 96,
impl_version: 96,
spec_version: 97,
impl_version: 97,
apis: RUNTIME_API_VERSIONS,
};
-12
View File
@@ -348,18 +348,6 @@ This function serializes the current block's timestamp into the scratch buffer.
**complexity**: Assuming that the timestamp is of constant size, this function has constant complexity.
## ext_input_size
**complexity**: This function is of constant complexity.
## ext_input_copy
This function copies slice of data from the input buffer to the sandbox memory. The calling code specifies the slice length. Execution of the function consists of the following steps:
1. Storing a specified slice of the input data into the sandbox memory (see sandboxing memory set)
**complexity**: The computing complexity of this function is proportional to the length of the slice. No additional memory is required.
## ext_scratch_size
This function returns the size of the scratch buffer.
+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() {