State cache and other performance optimizations (#1345)

* State caching

* Better code caching

* Execution optimizaton

* More optimizations

* Updated wasmi

* Caching test

* Style

* Style

* Reverted some minor changes

* Style and typos

* Style and typos

* Removed panics on missing memory
This commit is contained in:
Arkadiy Paronyan
2019-01-08 15:13:13 +03:00
committed by Gav Wood
parent e0639c435b
commit b104c02eb6
26 changed files with 796 additions and 266 deletions
+7 -1
View File
@@ -68,9 +68,11 @@ pub trait BlockImportOperation<Block, H> where
/// has been used to check justification of this block).
fn update_authorities(&mut self, authorities: Vec<AuthorityIdFor<Block>>);
/// Inject storage data into the database.
fn update_storage(&mut self, update: <Self::State as StateBackend<H>>::Transaction) -> error::Result<()>;
fn update_db_storage(&mut self, update: <Self::State as StateBackend<H>>::Transaction) -> error::Result<()>;
/// Inject storage data into the database replacing any existing data.
fn reset_storage(&mut self, top: StorageMap, children: ChildrenStorageMap) -> error::Result<H::Out>;
/// Set top level storage changes.
fn update_storage(&mut self, update: Vec<(Vec<u8>, Option<Vec<u8>>)>) -> error::Result<()>;
/// Inject changes trie data into the database.
fn update_changes_trie(&mut self, update: MemoryDB<H>) -> error::Result<()>;
/// Update auxiliary keys. Values are `None` if should be deleted.
@@ -127,6 +129,10 @@ pub trait Backend<Block, H>: AuxStore + Send + Sync where
fn changes_trie_storage(&self) -> Option<&Self::ChangesTrieStorage>;
/// Returns state backend with post-state of given block.
fn state_at(&self, block: BlockId<Block>) -> error::Result<Self::State>;
/// Destroy state and save any useful data, such as cache.
fn destroy_state(&self, _state: Self::State) -> error::Result<()> {
Ok(())
}
/// Attempts to revert the chain by `n` blocks. Returns the number of blocks that were
/// successfully reverted.
fn revert(&self, n: NumberFor<Block>) -> error::Result<NumberFor<Block>>;
+37 -30
View File
@@ -24,22 +24,11 @@ use state_machine::{self, OverlayedChanges, Ext,
use executor::{RuntimeVersion, RuntimeInfo, NativeVersion};
use hash_db::Hasher;
use trie::MemoryDB;
use codec::Decode;
use primitives::{H256, Blake2Hasher};
use primitives::storage::well_known_keys;
use backend;
use error;
/// Information regarding the result of a call.
#[derive(Debug, Clone)]
pub struct CallResult {
/// The data that was returned from the call.
pub return_data: Vec<u8>,
/// The changes made to the state by the call.
pub changes: OverlayedChanges,
}
/// Method call executor.
pub trait CallExecutor<B, H>
where
@@ -58,7 +47,7 @@ where
id: &BlockId<B>,
method: &str,
call_data: &[u8],
) -> Result<CallResult, error::Error>;
) -> Result<Vec<u8>, error::Error>;
/// Execute a contextual call on top of state in a block of a given hash.
///
@@ -163,16 +152,22 @@ where
id: &BlockId<Block>,
method: &str,
call_data: &[u8],
) -> error::Result<CallResult> {
) -> error::Result<Vec<u8>> {
let mut changes = OverlayedChanges::default();
let (return_data, _, _) = self.call_at_state(
&self.backend.state_at(*id)?,
let state = self.backend.state_at(*id)?;
let return_data = state_machine::execute_using_consensus_failure_handler(
&state,
self.backend.changes_trie_storage(),
&mut changes,
&self.executor,
method,
call_data,
native_when_possible(),
)?;
Ok(CallResult { return_data, changes })
false,
)
.map(|(result, _, _)| result)?;
self.backend.destroy_state(state)?;
Ok(return_data)
}
fn contextual_call<
@@ -192,28 +187,40 @@ where
//TODO: Find a better way to prevent double block initialization
if method != "Core_initialise_block" && initialised_block.map(|id| id != *at).unwrap_or(true) {
let header = prepare_environment_block()?;
self.call_at_state(&state, changes, "Core_initialise_block", &header.encode(), manager.clone())?;
state_machine::execute_using_consensus_failure_handler(
&state,
self.backend.changes_trie_storage(),
changes,
&self.executor,
"Core_initialise_block",
&header.encode(),
manager.clone(),
false,
)?;
*initialised_block = Some(*at);
}
self.call_at_state(&state, changes, method, call_data, manager).map(|cr| cr.0)
let result = state_machine::execute_using_consensus_failure_handler(
&state,
self.backend.changes_trie_storage(),
changes,
&self.executor,
method,
call_data,
manager,
false,
)
.map(|(result, _, _)| result)?;
self.backend.destroy_state(state)?;
Ok(result)
}
fn runtime_version(&self, id: &BlockId<Block>) -> error::Result<RuntimeVersion> {
let mut overlay = OverlayedChanges::default();
let state = self.backend.state_at(*id)?;
use state_machine::Backend;
let code = state.storage(well_known_keys::CODE)
.map_err(|e| error::ErrorKind::Execution(Box::new(e)))?
.ok_or(error::ErrorKind::VersionInvalid)?
.to_vec();
let heap_pages = state.storage(well_known_keys::HEAP_PAGES)
.map_err(|e| error::ErrorKind::Execution(Box::new(e)))?
.and_then(|v| u64::decode(&mut &v[..]))
.unwrap_or(1024) as usize;
let mut ext = Ext::new(&mut overlay, &state, self.backend.changes_trie_storage());
self.executor.runtime_version(&mut ext, heap_pages, &code)
self.executor.runtime_version(&mut ext)
.ok_or(error::ErrorKind::VersionInvalid.into())
}
+8 -6
View File
@@ -230,7 +230,6 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
let (genesis_storage, children_genesis_storage) = build_genesis_storage.build_storage()?;
let mut op = backend.begin_operation(BlockId::Hash(Default::default()))?;
let state_root = op.reset_storage(genesis_storage, children_genesis_storage)?;
let genesis_block = genesis::construct_genesis_block::<Block>(state_root.into());
info!("Initialising Genesis block/state (state: {}, header-hash: {})", genesis_block.header().state_root(), genesis_block.header().hash());
op.set_block_data(
@@ -284,8 +283,8 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
match self.backend.blockchain().cache().and_then(|cache| cache.authorities_at(*id)) {
Some(cached_value) => Ok(cached_value),
None => self.executor.call(id, "Core_authorities", &[])
.and_then(|r| Vec::<AuthorityIdFor<Block>>::decode(&mut &r.return_data[..])
.ok_or(error::ErrorKind::InvalidAuthoritiesSet.into()))
.and_then(|r| Vec::<AuthorityIdFor<Block>>::decode(&mut &r[..])
.ok_or_else(|| error::ErrorKind::InvalidAuthoritiesSet.into()))
}
}
@@ -602,7 +601,7 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
);
let (_, storage_update, changes_update) = r?;
overlay.commit_prospective();
(Some(storage_update), Some(changes_update), Some(overlay.into_committed()))
(Some(storage_update), Some(changes_update), Some(overlay.into_committed().collect()))
},
None => (None, None, None)
};
@@ -633,7 +632,10 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
transaction.update_authorities(authorities);
}
if let Some(storage_update) = storage_update {
transaction.update_storage(storage_update)?;
transaction.update_db_storage(storage_update)?;
}
if let Some(storage_changes) = storage_changes.clone() {
transaction.update_storage(storage_changes)?;
}
if let Some(Some(changes_update)) = changes_update {
transaction.update_changes_trie(changes_update)?;
@@ -646,7 +648,7 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
if let Some(storage_changes) = storage_changes {
// TODO [ToDr] How to handle re-orgs? Should we re-emit all storage changes?
self.storage_notifications.lock()
.trigger(&hash, storage_changes);
.trigger(&hash, storage_changes.into_iter());
}
if finalized {
+5 -1
View File
@@ -448,7 +448,7 @@ where
self.pending_authorities = Some(authorities);
}
fn update_storage(&mut self, update: <InMemory<H> as StateBackend<H>>::Transaction) -> error::Result<()> {
fn update_db_storage(&mut self, update: <InMemory<H> as StateBackend<H>>::Transaction) -> error::Result<()> {
self.new_state = Some(self.old_state.update(update));
Ok(())
}
@@ -491,6 +491,10 @@ where
self.aux = Some(ops.into_iter().collect());
Ok(())
}
fn update_storage(&mut self, _update: Vec<(Vec<u8>, Option<Vec<u8>>)>) -> error::Result<()> {
Ok(())
}
}
/// In-memory backend. Keeps all states and blocks in memory. Useful for testing.
+1 -1
View File
@@ -102,7 +102,7 @@ mod notifications;
#[cfg(feature = "std")]
pub use blockchain::Info as ChainInfo;
#[cfg(feature = "std")]
pub use call_executor::{CallResult, CallExecutor, LocalCallExecutor};
pub use call_executor::{CallExecutor, LocalCallExecutor};
#[cfg(feature = "std")]
pub use client::{
new_with_backend,
+6 -1
View File
@@ -188,7 +188,7 @@ where
self.authorities = Some(authorities);
}
fn update_storage(&mut self, _update: <Self::State as StateBackend<H>>::Transaction) -> ClientResult<()> {
fn update_db_storage(&mut self, _update: <Self::State as StateBackend<H>>::Transaction) -> ClientResult<()> {
// we're not storing anything locally => ignore changes
Ok(())
}
@@ -210,6 +210,11 @@ where
self.aux_ops = ops.into_iter().collect();
Ok(())
}
fn update_storage(&mut self, _update: Vec<(Vec<u8>, Option<Vec<u8>>)>) -> ClientResult<()> {
// we're not storing anything locally => ignore changes
Ok(())
}
}
impl<Block, S, F, H> StateBackend<H> for OnDemandState<Block, S, F>
@@ -31,7 +31,7 @@ use state_machine::{self, Backend as StateBackend, CodeExecutor, OverlayedChange
use hash_db::Hasher;
use blockchain::Backend as ChainBackend;
use call_executor::{CallExecutor, CallResult};
use call_executor::CallExecutor;
use error::{Error as ClientError, ErrorKind as ClientErrorKind, Result as ClientResult};
use light::fetcher::{Fetcher, RemoteCallRequest};
use executor::{RuntimeVersion, NativeVersion};
@@ -74,7 +74,7 @@ where
{
type Error = ClientError;
fn call(&self, id: &BlockId<Block>, method: &str, call_data: &[u8]) -> ClientResult<CallResult> {
fn call(&self, id: &BlockId<Block>, method: &str, call_data: &[u8]) -> ClientResult<Vec<u8>> {
let block_hash = self.blockchain.expect_block_hash_from_id(id)?;
let block_header = self.blockchain.expect_header(id.clone())?;
@@ -105,12 +105,12 @@ where
return Err(ClientErrorKind::NotAvailableOnLightClient.into());
}
self.call(at, method, call_data).map(|cr| cr.return_data)
self.call(at, method, call_data)
}
fn runtime_version(&self, id: &BlockId<Block>) -> ClientResult<RuntimeVersion> {
let call_result = self.call(id, "version", &[])?;
RuntimeVersion::decode(&mut call_result.return_data.as_slice())
RuntimeVersion::decode(&mut call_result.as_slice())
.ok_or_else(|| ClientErrorKind::VersionInvalid.into())
}
@@ -189,7 +189,7 @@ pub fn check_execution_proof<Header, E, H>(
executor: &E,
request: &RemoteCallRequest<Header>,
remote_proof: Vec<Vec<u8>>
) -> ClientResult<CallResult>
) -> ClientResult<Vec<u8>>
where
Header: HeaderT,
E: CodeExecutor<H>,
@@ -226,7 +226,7 @@ pub fn check_execution_proof<Header, E, H>(
&request.call_data,
)?;
Ok(CallResult { return_data: local_result, changes })
Ok(local_result)
}
#[cfg(test)]
@@ -273,7 +273,7 @@ mod tests {
retry_count: None,
}, remote_execution_proof).unwrap();
(remote_result, local_result.return_data)
(remote_result, local_result)
}
// prepare remote client
+5 -7
View File
@@ -28,7 +28,6 @@ use runtime_primitives::traits::{As, Block as BlockT, Header as HeaderT, NumberF
use state_machine::{CodeExecutor, ChangesTrieRootsStorage, ChangesTrieAnchorBlockId,
TrieBackend, read_proof_check, key_changes_proof_check, create_proof_check_backend_storage};
use call_executor::CallResult;
use cht;
use error::{Error as ClientError, ErrorKind as ClientErrorKind, Result as ClientResult};
use light::blockchain::{Blockchain, Storage as BlockchainStorage};
@@ -118,7 +117,7 @@ pub trait Fetcher<Block: BlockT>: Send + Sync {
/// Remote storage read future.
type RemoteReadResult: IntoFuture<Item=Option<Vec<u8>>, Error=ClientError>;
/// Remote call result future.
type RemoteCallResult: IntoFuture<Item=CallResult, Error=ClientError>;
type RemoteCallResult: IntoFuture<Item=Vec<u8>, Error=ClientError>;
/// Remote changes result future.
type RemoteChangesResult: IntoFuture<Item=Vec<(NumberFor<Block>, u32)>, Error=ClientError>;
@@ -156,7 +155,7 @@ pub trait FetchChecker<Block: BlockT>: Send + Sync {
&self,
request: &RemoteCallRequest<Block::Header>,
remote_proof: Vec<Vec<u8>>
) -> ClientResult<CallResult>;
) -> ClientResult<Vec<u8>>;
/// Check remote changes query proof.
fn check_changes_proof(
&self,
@@ -344,7 +343,7 @@ impl<E, Block, H, S, F> FetchChecker<Block> for LightDataChecker<E, H, Block, S,
&self,
request: &RemoteCallRequest<Block::Header>,
remote_proof: Vec<Vec<u8>>
) -> ClientResult<CallResult> {
) -> ClientResult<Vec<u8>> {
check_execution_proof::<_, _, H>(&self.executor, request, remote_proof)
}
@@ -392,7 +391,6 @@ pub mod tests {
use futures::future::{ok, err, FutureResult};
use parking_lot::Mutex;
use keyring::Keyring;
use call_executor::CallResult;
use client::tests::prepare_client_with_key_changes;
use executor::{self, NativeExecutionDispatch};
use error::Error as ClientError;
@@ -410,12 +408,12 @@ pub mod tests {
use state_machine::Backend;
use super::*;
pub type OkCallFetcher = Mutex<CallResult>;
pub type OkCallFetcher = Mutex<Vec<u8>>;
impl Fetcher<Block> for OkCallFetcher {
type RemoteHeaderResult = FutureResult<Header, ClientError>;
type RemoteReadResult = FutureResult<Option<Vec<u8>>, ClientError>;
type RemoteCallResult = FutureResult<CallResult, ClientError>;
type RemoteCallResult = FutureResult<Vec<u8>, ClientError>;
type RemoteChangesResult = FutureResult<Vec<(NumberFor<Block>, u32)>, ClientError>;
fn remote_header(&self, _request: RemoteHeaderRequest<Header>) -> Self::RemoteHeaderResult {