diff --git a/substrate/core/cli/src/params.rs b/substrate/core/cli/src/params.rs index 0f58527287..d27976b22e 100644 --- a/substrate/core/cli/src/params.rs +++ b/substrate/core/cli/src/params.rs @@ -705,7 +705,9 @@ impl StructOpt for CoreParams where ) .subcommand( ExportBlocksCmd::augment_clap(SubCommand::with_name("export-blocks")) - .about("Export blocks to a file.") + .about("Export blocks to a file. This file can only be re-imported \ + if it is in binary format (not JSON!)." + ) ) .subcommand( ImportBlocksCmd::augment_clap(SubCommand::with_name("import-blocks")) diff --git a/substrate/core/executor/src/allocator.rs b/substrate/core/executor/src/allocator.rs index 024af84768..d326b96622 100644 --- a/substrate/core/executor/src/allocator.rs +++ b/substrate/core/executor/src/allocator.rs @@ -15,14 +15,45 @@ // along with Substrate. If not, see . //! This module implements a freeing-bump allocator. -//! See more details at https://github.com/paritytech/substrate/issues/1615. +//! +//! The algorithm is as follows: +//! We store `N` linked list heads, where `N` is the total number of sizes +//! of allocations to support. A simple set is powers of two from 8 bytes +//! to 16,777,216 bytes (2^3 - 2^24 inclusive), resulting in `N = 22`: +//! +//! ```ignore +//! let mut heads [u64; N] = [0; N]; +//! fn size(n: u64) -> u64 { 8 << n } +//! let mut bumper = 0; +//! fn bump(n: u64) -> u64 { let res = bumper; bumper += n; res } +//! ``` +//! +//! We assume there is a slab of heap to be allocated: +//! +//! ```ignore +//! let mut heap = [0u8; HEAP_SIZE]; +//! ``` +//! +//! Whenever we allocate, we select the lowest linked list item size that +//! will fit the allocation (i.e. the next highest power of two). +//! We then check to see if the linked list is empty. If empty, we use +//! the bump allocator to get the allocation with an extra 8 bytes +//! preceding it. We initialise those preceding 8 bytes to identify the +//! list to which it belongs (e.g. `0x__ffffffffffffff` where `__` is the +//! linked list index). If it is not empty, we unlink the first item from +//! the linked list and then reset the 8 preceding bytes so they now record +//! the identity of the linked list. +//! +//! To deallocate we use the preceding 8 bytes of the allocation to knit +//! back the allocation into the linked list from the head. use crate::error::{Error, Result}; use log::trace; use wasmi::{MemoryRef, memory_units::Bytes}; use wasm_interface::{Pointer, WordSize}; -// The pointers need to be aligned to 8 bytes. +// The pointers need to be aligned to 8 bytes. This is because the +// maximum value type handled by wasm32 is u64. const ALIGNMENT: u32 = 8; // The pointer returned by `allocate()` needs to fulfill the alignment @@ -32,6 +63,11 @@ const ALIGNMENT: u32 = 8; // index) and then a subsequent item of 2^x bytes, where x = [3..24]. const N: usize = 22; const MAX_POSSIBLE_ALLOCATION: u32 = 16777216; // 2^24 bytes +const MIN_POSSIBLE_ALLOCATION: u32 = 8; + +// Each pointer is prefixed with 8 bytes, which identify the list index +// to which it belongs. +const PREFIX_SIZE: u32 = 8; pub struct FreeingBumpHeapAllocator { bumper: u32, @@ -79,14 +115,17 @@ impl FreeingBumpHeapAllocator { /// Gets requested number of bytes to allocate and returns a pointer. /// The maximum size which can be allocated at once is 16 MiB. + /// There is no minimum size, but whatever size is passed into + /// this function is rounded to the next power of two. If the requested + /// size is below 8 bytes it will be rounded up to 8 bytes. pub fn allocate(&mut self, size: WordSize) -> Result> { if size > MAX_POSSIBLE_ALLOCATION { return Err(Error::RequestedAllocationTooLarge); } - let size = size.max(8); + let size = size.max(MIN_POSSIBLE_ALLOCATION); let item_size = size.next_power_of_two(); - if item_size + 8 + self.total_size > self.max_heap_size { + if item_size + PREFIX_SIZE + self.total_size > self.max_heap_size { return Err(Error::AllocatorOutOfSpace); } @@ -94,19 +133,27 @@ impl FreeingBumpHeapAllocator { let ptr: u32 = if self.heads[list_index] != 0 { // Something from the free list let item = self.heads[list_index]; + let ptr = item + PREFIX_SIZE; + assert!( + ptr + item_size <= self.max_heap_size, + "Pointer is looked up in list of free entries, into which + only valid values are inserted; qed" + ); + let four_bytes = self.get_heap_4bytes(item)?; self.heads[list_index] = Self::le_bytes_to_u32(four_bytes); - item + 8 + ptr } else { // Nothing to be freed. Bump. - self.bump(item_size + 8) + 8 + self.bump(item_size)? + PREFIX_SIZE }; - (1..8).try_for_each(|i| self.set_heap(ptr - i, 255))?; + // Reset prefix + (1..PREFIX_SIZE).try_for_each(|i| self.set_heap(ptr - i, 255))?; - self.set_heap(ptr - 8, list_index as u8)?; + self.set_heap(ptr - PREFIX_SIZE, list_index as u8)?; - self.total_size = self.total_size + item_size + 8; + self.total_size = self.total_size + item_size + PREFIX_SIZE; trace!(target: "wasm-heap", "Heap size is {} bytes after allocation", self.total_size); Ok(Pointer::new(self.ptr_offset + ptr)) @@ -115,31 +162,42 @@ impl FreeingBumpHeapAllocator { /// Deallocates the space which was allocated for a pointer. pub fn deallocate(&mut self, ptr: Pointer) -> Result<()> { let ptr = u32::from(ptr) - self.ptr_offset; - if ptr < 8 { + if ptr < PREFIX_SIZE { return Err(error("Invalid pointer for deallocation")); } - let list_index = usize::from(self.get_heap_byte(ptr - 8)?); - (1..8).try_for_each(|i| self.get_heap_byte(ptr - i).map(|byte| assert!(byte == 255)))?; + let list_index = usize::from(self.get_heap_byte(ptr - PREFIX_SIZE)?); + (1..PREFIX_SIZE).try_for_each(|i| + self.get_heap_byte(ptr - i).map(|byte| assert!(byte == 255)) + )?; let tail = self.heads[list_index]; - self.heads[list_index] = ptr - 8; + self.heads[list_index] = ptr - PREFIX_SIZE; - let mut slice = self.get_heap_4bytes(ptr - 8)?; + let mut slice = self.get_heap_4bytes(ptr - PREFIX_SIZE)?; Self::write_u32_into_le_bytes(tail, &mut slice); - self.set_heap_4bytes(ptr - 8, slice)?; + self.set_heap_4bytes(ptr - PREFIX_SIZE, slice)?; let item_size = Self::get_item_size_from_index(list_index); - self.total_size = self.total_size.checked_sub(item_size as u32 + 8) + self.total_size = self.total_size.checked_sub(item_size as u32 + PREFIX_SIZE) .ok_or_else(|| error("Unable to subtract from total heap size without overflow"))?; trace!(target: "wasm-heap", "Heap size is {} bytes after deallocation", self.total_size); Ok(()) } - fn bump(&mut self, n: u32) -> u32 { + /// Increases the `bumper` by `item_size + PREFIX_SIZE`. + /// + /// Returns the `bumper` from before the increase. + /// Returns an `Error::AllocatorOutOfSpace` if the operation + /// would exhaust the heap. + fn bump(&mut self, item_size: u32) -> Result { + if self.bumper + PREFIX_SIZE + item_size > self.max_heap_size { + return Err(Error::AllocatorOutOfSpace); + } + let res = self.bumper; - self.bumper += n; - res + self.bumper += item_size + PREFIX_SIZE; + Ok(res) } fn le_bytes_to_u32(arr: [u8; 4]) -> u32 { @@ -199,7 +257,8 @@ mod tests { let ptr = heap.allocate(1).unwrap(); // then - assert_eq!(ptr, to_pointer(8)); + // returned pointer must start right after `PREFIX_SIZE` + assert_eq!(ptr, to_pointer(PREFIX_SIZE)); } #[test] @@ -230,14 +289,14 @@ mod tests { // then // a prefix of 8 bytes is prepended to each pointer - assert_eq!(ptr1, to_pointer(8)); + assert_eq!(ptr1, to_pointer(PREFIX_SIZE)); // the prefix of 8 bytes + the content of ptr1 padded to the lowest possible // item size of 8 bytes + the prefix of ptr1 assert_eq!(ptr2, to_pointer(24)); // ptr2 + its content of 16 bytes + the prefix of 8 bytes - assert_eq!(ptr3, to_pointer(24 + 16 + 8)); + assert_eq!(ptr3, to_pointer(24 + 16 + PREFIX_SIZE)); } #[test] @@ -247,7 +306,7 @@ mod tests { let mut heap = FreeingBumpHeapAllocator::new(mem, 0); let ptr1 = heap.allocate(1).unwrap(); // the prefix of 8 bytes is prepended to the pointer - assert_eq!(ptr1, to_pointer(8)); + assert_eq!(ptr1, to_pointer(PREFIX_SIZE)); let ptr2 = heap.allocate(1).unwrap(); // the prefix of 8 bytes + the content of ptr 1 is prepended to the pointer @@ -259,7 +318,7 @@ mod tests { // then // then the heads table should contain a pointer to the // prefix of ptr2 in the leftmost entry - assert_eq!(heap.heads[0], u32::from(ptr2) - 8); + assert_eq!(heap.heads[0], u32::from(ptr2) - PREFIX_SIZE); } #[test] @@ -271,13 +330,13 @@ mod tests { let ptr1 = heap.allocate(1).unwrap(); // the prefix of 8 bytes is prepended to the pointer - assert_eq!(ptr1, to_pointer(padded_offset + 8)); + assert_eq!(ptr1, to_pointer(padded_offset + PREFIX_SIZE)); let ptr2 = heap.allocate(9).unwrap(); // the padded_offset + the previously allocated ptr (8 bytes prefix + // 8 bytes content) + the prefix of 8 bytes which is prepended to the // current pointer - assert_eq!(ptr2, to_pointer(padded_offset + 16 + 8)); + assert_eq!(ptr2, to_pointer(padded_offset + 16 + PREFIX_SIZE)); // when heap.deallocate(ptr2).unwrap(); @@ -285,7 +344,7 @@ mod tests { // then // should have re-allocated - assert_eq!(ptr3, to_pointer(padded_offset + 16 + 8)); + assert_eq!(ptr3, to_pointer(padded_offset + 16 + PREFIX_SIZE)); assert_eq!(heap.heads, [0; N]); } @@ -305,12 +364,12 @@ mod tests { heap.deallocate(ptr3).unwrap(); // then - assert_eq!(heap.heads[0], u32::from(ptr3) - 8); + assert_eq!(heap.heads[0], u32::from(ptr3) - PREFIX_SIZE); let ptr4 = heap.allocate(8).unwrap(); assert_eq!(ptr4, ptr3); - assert_eq!(heap.heads[0], u32::from(ptr2) - 8); + assert_eq!(heap.heads[0], u32::from(ptr2) - PREFIX_SIZE); } #[test] @@ -323,12 +382,9 @@ mod tests { let ptr = heap.allocate(PAGE_SIZE - 13); // then - assert_eq!(ptr.is_err(), true); - if let Err(err) = ptr { - match err { - Error::AllocatorOutOfSpace => {}, - _ => panic!("Expected out of space error"), - } + match ptr.unwrap_err() { + Error::AllocatorOutOfSpace => {}, + e => panic!("Expected allocator out of space error, got: {:?}", e), } } @@ -337,20 +393,17 @@ mod tests { // given let mem = MemoryInstance::alloc(Pages(1), Some(Pages(1))).unwrap(); let mut heap = FreeingBumpHeapAllocator::new(mem, 0); - let ptr1 = heap.allocate((PAGE_SIZE / 2) - 8).unwrap(); - assert_eq!(ptr1, to_pointer(8)); + let ptr1 = heap.allocate((PAGE_SIZE / 2) - PREFIX_SIZE).unwrap(); + assert_eq!(ptr1, to_pointer(PREFIX_SIZE)); // when let ptr2 = heap.allocate(PAGE_SIZE / 2); // then // there is no room for another half page incl. its 8 byte prefix - assert_eq!(ptr2.is_err(), true); - if let Err(err) = ptr2 { - match err { - Error::AllocatorOutOfSpace => {}, - _ => panic!("Expected out of space error"), - } + match ptr2.unwrap_err() { + Error::AllocatorOutOfSpace => {}, + e => panic!("Expected allocator out of space error, got: {:?}", e), } } @@ -365,7 +418,7 @@ mod tests { let ptr = heap.allocate(MAX_POSSIBLE_ALLOCATION).unwrap(); // then - assert_eq!(ptr, to_pointer(8)); + assert_eq!(ptr, to_pointer(PREFIX_SIZE)); } #[test] @@ -378,12 +431,42 @@ mod tests { let ptr = heap.allocate(MAX_POSSIBLE_ALLOCATION + 1); // then - assert_eq!(ptr.is_err(), true); - if let Err(err) = ptr { - match err { - Error::RequestedAllocationTooLarge => {}, - e => panic!("Expected out of space error, got: {:?}", e), - } + match ptr.unwrap_err() { + Error::RequestedAllocationTooLarge => {}, + e => panic!("Expected allocation size too large error, got: {:?}", e), + } + } + + #[test] + fn should_return_error_when_bumper_greater_than_heap_size() { + // given + let mem = MemoryInstance::alloc(Pages(1), None).unwrap(); + let mut heap = FreeingBumpHeapAllocator::new(mem, 0); + heap.max_heap_size = 64; + + let ptr1 = heap.allocate(32).unwrap(); + assert_eq!(ptr1, to_pointer(PREFIX_SIZE)); + heap.deallocate(ptr1).expect("failed freeing ptr1"); + assert_eq!(heap.total_size, 0); + assert_eq!(heap.bumper, 40); + + let ptr2 = heap.allocate(16).unwrap(); + assert_eq!(ptr2, to_pointer(48)); + heap.deallocate(ptr2).expect("failed freeing ptr2"); + assert_eq!(heap.total_size, 0); + assert_eq!(heap.bumper, 64); + + // when + // the `bumper` value is equal to `max_heap_size` here and any + // further allocation which would increment the bumper must fail. + // we try to allocate 8 bytes here, which will increment the + // bumper since no 8 byte item has been allocated+freed before. + let ptr = heap.allocate(8); + + // then + match ptr.unwrap_err() { + Error::AllocatorOutOfSpace => {}, + e => panic!("Expected allocator out of space error, got: {:?}", e), } } @@ -398,7 +481,7 @@ mod tests { heap.allocate(9).unwrap(); // then - assert_eq!(heap.total_size, 8 + 16); + assert_eq!(heap.total_size, PREFIX_SIZE + 16); } #[test] @@ -409,7 +492,7 @@ mod tests { // when let ptr = heap.allocate(42).unwrap(); - assert_eq!(ptr, to_pointer(16 + 8)); + assert_eq!(ptr, to_pointer(16 + PREFIX_SIZE)); heap.deallocate(ptr).unwrap(); // then diff --git a/substrate/core/executor/src/error.rs b/substrate/core/executor/src/error.rs index d6bf6b8b84..c5148241ee 100644 --- a/substrate/core/executor/src/error.rs +++ b/substrate/core/executor/src/error.rs @@ -69,8 +69,8 @@ pub enum Error { /// Some error occurred in the allocator #[display(fmt="Error in allocator: {}", _0)] Allocator(&'static str), - /// The allocator run out of space. - #[display(fmt="Allocator run out of space")] + /// The allocator ran out of space. + #[display(fmt="Allocator ran out of space")] AllocatorOutOfSpace, /// Someone tried to allocate more memory than the allowed maximum per allocation. #[display(fmt="Requested allocation size is too large")] diff --git a/substrate/node/cli/src/factory_impl.rs b/substrate/node/cli/src/factory_impl.rs index c16a8af6f8..827b8e308c 100644 --- a/substrate/node/cli/src/factory_impl.rs +++ b/substrate/node/cli/src/factory_impl.rs @@ -23,7 +23,10 @@ use rand::rngs::StdRng; use codec::{Encode, Decode}; use keyring::sr25519::Keyring; -use node_runtime::{Call, CheckedExtrinsic, UncheckedExtrinsic, SignedExtra, BalancesCall, ExistentialDeposit}; +use node_runtime::{ + Call, CheckedExtrinsic, UncheckedExtrinsic, SignedExtra, BalancesCall, ExistentialDeposit, + MinimumPeriod, +}; use primitives::{sr25519, crypto::Pair}; use sr_primitives::{generic::Era, traits::{Block as BlockT, Header as HeaderT, SignedExtension}}; use transaction_factory::RuntimeAdapter; @@ -32,9 +35,6 @@ use inherents::InherentData; use timestamp; use finality_tracker; -// TODO get via api: ::MinimumPeriod::get(). See #2587. -const MINIMUM_PERIOD: u64 = 99; - pub struct FactoryState { block_no: N, @@ -152,7 +152,7 @@ impl RuntimeAdapter for FactoryState { } fn inherent_extrinsics(&self) -> InherentData { - let timestamp = self.block_no as u64 * MINIMUM_PERIOD; + let timestamp = (self.block_no as u64 + 1) * MinimumPeriod::get(); let mut inherent = InherentData::new(); inherent.put_data(timestamp::INHERENT_IDENTIFIER, ×tamp) @@ -163,7 +163,6 @@ impl RuntimeAdapter for FactoryState { } fn minimum_balance() -> Self::Balance { - // TODO get correct amount via api. See #2587. ExistentialDeposit::get() } diff --git a/substrate/test-utils/transaction-factory/src/lib.rs b/substrate/test-utils/transaction-factory/src/lib.rs index 5d63f906a7..067c75c3fc 100644 --- a/substrate/test-utils/transaction-factory/src/lib.rs +++ b/substrate/test-utils/transaction-factory/src/lib.rs @@ -116,7 +116,7 @@ where let best_header: Result<::Header, cli::error::Error> = select_chain.best_chain().map_err(|e| format!("{:?}", e).into()); let mut best_hash = best_header?.hash(); - let best_block_id = BlockId::::hash(best_hash); + let mut best_block_id = BlockId::::hash(best_hash); let version = client.runtime_version_at(&best_block_id)?.spec_version; let genesis_hash = client.block_hash(Zero::zero())? .expect("Genesis block always exists; qed").into(); @@ -140,6 +140,7 @@ where ), } { best_hash = block.header().hash(); + best_block_id = BlockId::::hash(best_hash); import_block(&client, block); info!("Imported block at {}", factory_state.block_no());