mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 17:41:08 +00:00
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:
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user