2016-02-05 13:40:41 +01:00
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
2016-02-02 15:29:53 +01:00
//! Blockchain database client.
2016-03-04 11:56:04 +01:00
use std ::marker ::PhantomData ;
2016-04-12 03:42:50 +02:00
use std ::path ::PathBuf ;
2016-01-09 18:50:45 +01:00
use util ::* ;
2016-02-10 12:50:27 +01:00
use util ::panics ::* ;
2016-02-10 15:06:13 +01:00
use views ::BlockView ;
2016-05-18 11:34:15 +02:00
use error ::{ Error , ImportError , ExecutionError , BlockError , ImportResult } ;
2016-03-22 16:07:42 +01:00
use header ::{ BlockNumber , Header } ;
2016-01-26 15:00:22 +01:00
use state ::State ;
2016-01-11 12:28:59 +01:00
use spec ::Spec ;
2016-01-11 11:51:31 +01:00
use engine ::Engine ;
2016-01-26 15:00:22 +01:00
use views ::HeaderView ;
2016-02-06 23:15:53 +01:00
use service ::{ NetSyncMessage , SyncMessage } ;
2016-01-14 01:28:37 +01:00
use env_info ::LastHashes ;
use verification ::* ;
use block ::* ;
2016-04-01 11:26:14 +02:00
use transaction ::{ LocalizedTransaction , SignedTransaction , Action } ;
2016-05-26 18:24:51 +02:00
use blockchain ::extras ::TransactionAddress ;
2016-02-17 12:35:37 +01:00
use filter ::Filter ;
use log_entry ::LocalizedLogEntry ;
2016-03-10 11:32:10 +01:00
use block_queue ::{ BlockQueue , BlockQueueInfo } ;
2016-03-13 15:29:55 +01:00
use blockchain ::{ BlockChain , BlockProvider , TreeRoute , ImportRoute } ;
2016-06-02 12:28:09 +02:00
use client ::{ BlockID , TransactionID , UncleID , TraceId , ClientConfig , BlockChainClient , MiningBlockChainClient , TraceFilter , CallAnalytics } ;
2016-05-18 11:34:15 +02:00
use client ::Error as ClientError ;
2016-03-19 21:37:11 +01:00
use env_info ::EnvInfo ;
2016-04-06 13:05:58 +02:00
use executive ::{ Executive , Executed , TransactOptions , contract_address } ;
2016-03-20 18:44:57 +01:00
use receipt ::LocalizedReceipt ;
2016-03-11 10:30:13 +01:00
pub use blockchain ::CacheSize as BlockChainCacheSize ;
2016-05-04 15:22:22 +02:00
use trace ::{ TraceDB , ImportRequest as TraceImportRequest , LocalizedTrace , Database as TraceDatabase } ;
2016-05-02 12:17:30 +02:00
use trace ;
2016-05-16 18:33:32 +02:00
pub use types ::blockchain_info ::BlockChainInfo ;
pub use types ::block_status ::BlockStatus ;
2016-05-19 00:44:49 +02:00
use evm ::Factory as EvmFactory ;
2016-05-31 19:52:53 +02:00
use miner ::{ Miner , MinerService , TransactionImportResult , AccountDetails } ;
2016-01-07 21:35:06 +01:00
2016-01-18 19:23:28 +01:00
impl fmt ::Display for BlockChainInfo {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
write! ( f , " #{}.{} " , self . best_block_number , self . best_block_hash )
}
}
2016-02-02 23:43:29 +01:00
/// Report on the status of a client.
2016-03-10 11:32:10 +01:00
#[ derive(Default, Clone, Debug, Eq, PartialEq) ]
2016-01-18 23:23:32 +01:00
pub struct ClientReport {
2016-02-02 23:43:29 +01:00
/// How many blocks have been imported so far.
2016-01-18 23:23:32 +01:00
pub blocks_imported : usize ,
2016-02-02 23:43:29 +01:00
/// How many transactions have been applied so far.
2016-01-18 23:23:32 +01:00
pub transactions_applied : usize ,
2016-02-02 23:43:29 +01:00
/// How much gas has been processed so far.
2016-01-18 23:23:32 +01:00
pub gas_processed : U256 ,
2016-03-06 22:39:04 +01:00
/// Memory used by state DB
pub state_db_mem : usize ,
2016-01-18 23:23:32 +01:00
}
impl ClientReport {
2016-02-02 23:43:29 +01:00
/// Alter internal reporting to reflect the additional `block` has been processed.
2016-03-01 00:02:48 +01:00
pub fn accrue_block ( & mut self , block : & PreverifiedBlock ) {
2016-01-18 23:23:32 +01:00
self . blocks_imported + = 1 ;
self . transactions_applied + = block . transactions . len ( ) ;
2016-02-19 00:06:06 +01:00
self . gas_processed = self . gas_processed + block . header . gas_used ;
2016-01-18 23:23:32 +01:00
}
}
2016-01-07 21:35:06 +01:00
/// Blockchain database client backed by a persistent database. Owns and manages a blockchain and a block queue.
2016-01-25 18:56:36 +01:00
/// Call `import_block()` to import a block asynchronously; `flush_queue()` flushes the queue.
2016-03-04 11:56:04 +01:00
pub struct Client < V = CanonVerifier > where V : Verifier {
2016-02-22 00:36:59 +01:00
chain : Arc < BlockChain > ,
2016-04-30 17:41:24 +02:00
tracedb : Arc < TraceDB < BlockChain > > ,
2016-01-14 01:28:37 +01:00
engine : Arc < Box < Engine > > ,
2016-03-11 13:21:53 +01:00
state_db : Mutex < Box < JournalDB > > ,
2016-02-21 19:46:29 +01:00
block_queue : BlockQueue ,
2016-01-21 23:33:52 +01:00
report : RwLock < ClientReport > ,
2016-02-10 12:50:27 +01:00
import_lock : Mutex < ( ) > ,
2016-02-10 15:28:43 +01:00
panic_handler : Arc < PanicHandler > ,
2016-03-04 18:09:21 +01:00
verifier : PhantomData < V > ,
2016-05-19 00:44:49 +02:00
vm_factory : Arc < EvmFactory > ,
2016-05-31 19:52:53 +02:00
miner : Arc < Miner > ,
2016-01-07 21:35:06 +01:00
}
2016-03-11 20:37:47 +01:00
const HISTORY : u64 = 1200 ;
2016-03-14 10:48:32 +01:00
// DO NOT TOUCH THIS ANY MORE UNLESS YOU REALLY KNOW WHAT YOU'RE DOING.
// Altering it will force a blanket DB update for *all* JournalDB-derived
// databases.
// Instead, add/upgrade the version string of the individual JournalDB-derived database
// of which you actually want force an upgrade.
2016-03-12 18:09:45 +01:00
const CLIENT_DB_VER_STR : & 'static str = " 5.3 " ;
2016-01-18 13:54:46 +01:00
2016-03-04 11:56:04 +01:00
impl Client < CanonVerifier > {
2016-01-15 01:03:29 +01:00
/// Create a new client with given spec and DB path.
2016-05-31 19:52:53 +02:00
pub fn new ( config : ClientConfig , spec : Spec , path : & Path , miner : Arc < Miner > , message_channel : IoChannel < NetSyncMessage > ) -> Result < Arc < Client > , ClientError > {
Client ::< CanonVerifier > ::new_with_verifier ( config , spec , path , miner , message_channel )
2016-03-04 11:56:04 +01:00
}
}
2016-04-13 20:26:41 +02:00
/// Get the path for the databases given the root path and information on the databases.
2016-04-12 03:42:50 +02:00
pub fn get_db_path ( path : & Path , pruning : journaldb ::Algorithm , genesis_hash : H256 ) -> PathBuf {
let mut dir = path . to_path_buf ( ) ;
dir . push ( H64 ::from ( genesis_hash ) . hex ( ) ) ;
//TODO: sec/fat: pruned/full versioning
// version here is a bit useless now, since it's controlled only be the pruning algo.
dir . push ( format! ( " v {} -sec- {} " , CLIENT_DB_VER_STR , pruning ) ) ;
dir
}
2016-04-13 20:26:41 +02:00
/// Append a path element to the given path and return the string.
2016-04-12 03:42:50 +02:00
pub fn append_path ( path : & Path , item : & str ) -> String {
let mut p = path . to_path_buf ( ) ;
p . push ( item ) ;
p . to_str ( ) . unwrap ( ) . to_owned ( )
}
2016-03-04 11:56:04 +01:00
impl < V > Client < V > where V : Verifier {
/// Create a new client with given spec and DB path and custom verifier.
2016-05-31 19:01:37 +02:00
pub fn new_with_verifier (
config : ClientConfig ,
spec : Spec ,
path : & Path ,
2016-05-31 19:52:53 +02:00
miner : Arc < Miner > ,
2016-05-31 19:01:37 +02:00
message_channel : IoChannel < NetSyncMessage > )
-> Result < Arc < Client < V > > , ClientError >
{
2016-04-12 03:42:50 +02:00
let path = get_db_path ( path , config . pruning , spec . genesis_header ( ) . hash ( ) ) ;
2016-01-26 15:00:22 +01:00
let gb = spec . genesis_block ( ) ;
2016-04-12 03:42:50 +02:00
let chain = Arc ::new ( BlockChain ::new ( config . blockchain , & gb , & path ) ) ;
2016-05-18 11:34:15 +02:00
let tracedb = Arc ::new ( try ! ( TraceDB ::new ( config . tracing , & path , chain . clone ( ) ) ) ) ;
2016-02-10 12:50:27 +01:00
2016-04-12 03:42:50 +02:00
let mut state_db = journaldb ::new ( & append_path ( & path , " state " ) , config . pruning ) ;
2016-03-11 13:50:39 +01:00
2016-04-09 19:20:35 +02:00
if state_db . is_empty ( ) & & spec . ensure_db_good ( state_db . as_hashdb_mut ( ) ) {
state_db . commit ( 0 , & spec . genesis_header ( ) . hash ( ) , None ) . expect ( " Error commiting genesis state to state DB " ) ;
2016-01-18 13:54:46 +01:00
}
2016-02-10 12:50:27 +01:00
2016-04-09 19:20:35 +02:00
let engine = Arc ::new ( spec . engine ) ;
2016-02-25 14:09:39 +01:00
let block_queue = BlockQueue ::new ( config . queue , engine . clone ( ) , message_channel ) ;
2016-02-10 16:35:52 +01:00
let panic_handler = PanicHandler ::new_in_arc ( ) ;
panic_handler . forward_from ( & block_queue ) ;
2016-02-10 12:50:27 +01:00
2016-05-18 11:34:15 +02:00
let client = Client {
2016-01-16 11:52:28 +01:00
chain : chain ,
2016-04-30 17:41:24 +02:00
tracedb : tracedb ,
2016-02-10 12:50:27 +01:00
engine : engine ,
2016-02-07 23:07:36 +01:00
state_db : Mutex ::new ( state_db ) ,
2016-02-21 19:46:29 +01:00
block_queue : block_queue ,
2016-01-21 23:33:52 +01:00
report : RwLock ::new ( Default ::default ( ) ) ,
import_lock : Mutex ::new ( ( ) ) ,
2016-02-29 14:57:41 +01:00
panic_handler : panic_handler ,
2016-03-04 18:09:21 +01:00
verifier : PhantomData ,
2016-05-19 00:44:49 +02:00
vm_factory : Arc ::new ( EvmFactory ::new ( config . vm_type ) ) ,
2016-05-31 19:52:53 +02:00
miner : miner ,
2016-05-18 11:34:15 +02:00
} ;
Ok ( Arc ::new ( client ) )
2016-01-07 21:35:06 +01:00
}
2016-01-13 23:15:44 +01:00
2016-01-25 18:56:36 +01:00
/// Flush the block import queue.
pub fn flush_queue ( & self ) {
2016-02-21 19:46:29 +01:00
self . block_queue . flush ( ) ;
2016-01-25 18:56:36 +01:00
}
2016-02-29 14:57:41 +01:00
fn build_last_hashes ( & self , parent_hash : H256 ) -> LastHashes {
2016-02-23 18:44:13 +01:00
let mut last_hashes = LastHashes ::new ( ) ;
last_hashes . resize ( 256 , H256 ::new ( ) ) ;
2016-02-29 14:57:41 +01:00
last_hashes [ 0 ] = parent_hash ;
2016-02-23 18:44:13 +01:00
for i in 0 .. 255 {
2016-02-29 18:11:59 +01:00
match self . chain . block_details ( & last_hashes [ i ] ) {
2016-02-23 18:44:13 +01:00
Some ( details ) = > {
last_hashes [ i + 1 ] = details . parent . clone ( ) ;
} ,
None = > break ,
}
}
last_hashes
}
2016-03-27 20:33:23 +02:00
fn check_and_close_block ( & self , block : & PreverifiedBlock ) -> Result < LockedBlock , ( ) > {
2016-02-24 10:55:34 +01:00
let engine = self . engine . deref ( ) . deref ( ) ;
let header = & block . header ;
2016-03-02 12:57:34 +01:00
// Check the block isn't so old we won't be able to enact it.
2016-03-07 15:10:15 +01:00
let best_block_number = self . chain . best_block_number ( ) ;
2016-03-02 13:28:51 +01:00
if best_block_number > = HISTORY & & header . number ( ) < = best_block_number - HISTORY {
2016-03-02 12:57:34 +01:00
warn! ( target : " client " , " Block import failed for #{} ({}) \n Block is ancient (current best block: #{}). " , header . number ( ) , header . hash ( ) , best_block_number ) ;
return Err ( ( ) ) ;
}
2016-02-24 10:55:34 +01:00
// Verify Block Family
2016-03-07 15:10:15 +01:00
let verify_family_result = V ::verify_block_family ( & header , & block . bytes , engine , self . chain . deref ( ) ) ;
2016-02-24 10:55:34 +01:00
if let Err ( e ) = verify_family_result {
warn! ( target : " client " , " Stage 3 block verification failed for #{} ({}) \n Error: {:?} " , header . number ( ) , header . hash ( ) , e ) ;
return Err ( ( ) ) ;
} ;
// Check if Parent is in chain
2016-02-29 18:11:59 +01:00
let chain_has_parent = self . chain . block_header ( & header . parent_hash ) ;
2016-02-24 10:55:34 +01:00
if let None = chain_has_parent {
warn! ( target : " client " , " Block import failed for #{} ({}): Parent not found ({}) " , header . number ( ) , header . hash ( ) , header . parent_hash ) ;
return Err ( ( ) ) ;
} ;
// Enact Verified Block
let parent = chain_has_parent . unwrap ( ) ;
2016-02-29 14:57:41 +01:00
let last_hashes = self . build_last_hashes ( header . parent_hash . clone ( ) ) ;
2016-03-28 09:42:50 +02:00
let db = self . state_db . lock ( ) . unwrap ( ) . boxed_clone ( ) ;
2016-02-24 10:55:34 +01:00
2016-05-19 00:44:49 +02:00
let enact_result = enact_verified ( & block , engine , self . tracedb . tracing_enabled ( ) , db , & parent , last_hashes , & self . vm_factory ) ;
2016-02-24 10:55:34 +01:00
if let Err ( e ) = enact_result {
warn! ( target : " client " , " Block import failed for #{} ({}) \n Error: {:?} " , header . number ( ) , header . hash ( ) , e ) ;
return Err ( ( ) ) ;
} ;
// Final Verification
2016-03-27 20:33:23 +02:00
let locked_block = enact_result . unwrap ( ) ;
if let Err ( e ) = V ::verify_block_final ( & header , locked_block . block ( ) . header ( ) ) {
2016-02-24 10:55:34 +01:00
warn! ( target : " client " , " Stage 4 block verification failed for #{} ({}) \n Error: {:?} " , header . number ( ) , header . hash ( ) , e ) ;
return Err ( ( ) ) ;
}
2016-03-27 20:33:23 +02:00
Ok ( locked_block )
2016-02-24 10:55:34 +01:00
}
2016-03-13 15:29:55 +01:00
fn calculate_enacted_retracted ( & self , import_results : Vec < ImportRoute > ) -> ( Vec < H256 > , Vec < H256 > ) {
fn map_to_vec ( map : Vec < ( H256 , bool ) > ) -> Vec < H256 > {
map . into_iter ( ) . map ( | ( k , _v ) | k ) . collect ( )
}
// In ImportRoute we get all the blocks that have been enacted and retracted by single insert.
// Because we are doing multiple inserts some of the blocks that were enacted in import `k`
// could be retracted in import `k+1`. This is why to understand if after all inserts
// the block is enacted or retracted we iterate over all routes and at the end final state
// will be in the hashmap
let map = import_results . into_iter ( ) . fold ( HashMap ::new ( ) , | mut map , route | {
for hash in route . enacted {
map . insert ( hash , true ) ;
}
for hash in route . retracted {
map . insert ( hash , false ) ;
}
map
} ) ;
// Split to enacted retracted (using hashmap value)
let ( enacted , retracted ) = map . into_iter ( ) . partition ( | & ( _k , v ) | v ) ;
// And convert tuples to keys
( map_to_vec ( enacted ) , map_to_vec ( retracted ) )
}
2016-03-14 17:36:51 +01:00
2016-01-15 01:03:29 +01:00
/// This is triggered by a message coming from a block queue when the block is ready for insertion
2016-02-06 23:15:53 +01:00
pub fn import_verified_blocks ( & self , io : & IoChannel < NetSyncMessage > ) -> usize {
2016-05-27 16:49:29 +02:00
let max_blocks_to_import = 64 ;
2016-02-23 18:44:13 +01:00
2016-03-13 15:29:55 +01:00
let mut imported_blocks = Vec ::with_capacity ( max_blocks_to_import ) ;
let mut invalid_blocks = HashSet ::new ( ) ;
let mut import_results = Vec ::with_capacity ( max_blocks_to_import ) ;
2016-02-23 18:44:13 +01:00
2016-01-21 23:33:52 +01:00
let _import_lock = self . import_lock . lock ( ) ;
2016-02-29 18:11:59 +01:00
let blocks = self . block_queue . drain ( max_blocks_to_import ) ;
2016-02-23 18:44:13 +01:00
2016-02-29 14:57:41 +01:00
let original_best = self . chain_info ( ) . best_block_hash ;
2016-01-25 23:24:51 +01:00
for block in blocks {
2016-02-24 11:17:25 +01:00
let header = & block . header ;
2016-02-23 18:44:13 +01:00
2016-03-13 15:29:55 +01:00
if invalid_blocks . contains ( & header . parent_hash ) {
invalid_blocks . insert ( header . hash ( ) ) ;
2016-01-17 23:07:58 +01:00
continue ;
}
2016-02-24 10:55:34 +01:00
let closed_block = self . check_and_close_block ( & block ) ;
if let Err ( _ ) = closed_block {
2016-03-13 15:29:55 +01:00
invalid_blocks . insert ( header . hash ( ) ) ;
2016-01-27 13:28:15 +01:00
break ;
2016-01-14 01:28:37 +01:00
}
2016-03-13 15:29:55 +01:00
imported_blocks . push ( header . hash ( ) ) ;
2016-02-23 18:44:13 +01:00
2016-03-05 16:46:04 +01:00
// Are we committing an era?
2016-02-23 18:44:13 +01:00
let ancient = if header . number ( ) > = HISTORY {
let n = header . number ( ) - HISTORY ;
2016-02-29 18:11:59 +01:00
Some ( ( n , self . chain . block_hash ( n ) . unwrap ( ) ) )
2016-02-23 18:44:13 +01:00
} else {
None
2016-01-17 23:07:58 +01:00
} ;
2016-01-14 01:28:37 +01:00
2016-02-23 18:44:13 +01:00
// Commit results
2016-03-05 16:46:04 +01:00
let closed_block = closed_block . unwrap ( ) ;
let receipts = closed_block . block ( ) . receipts ( ) . clone ( ) ;
2016-05-02 12:17:30 +02:00
let traces = From ::from ( closed_block . block ( ) . traces ( ) . clone ( ) . unwrap_or_else ( Vec ::new ) ) ;
2016-02-24 10:55:34 +01:00
closed_block . drain ( )
. commit ( header . number ( ) , & header . hash ( ) , ancient )
. expect ( " State DB commit failed. " ) ;
2016-02-02 12:12:32 +01:00
2016-03-10 11:16:54 +01:00
// And update the chain after commit to prevent race conditions
// (when something is in chain but you are not able to fetch details)
2016-03-13 15:29:55 +01:00
let route = self . chain . insert_block ( & block . bytes , receipts ) ;
2016-05-02 12:17:30 +02:00
self . tracedb . import ( TraceImportRequest {
traces : traces ,
block_hash : header . hash ( ) ,
block_number : header . number ( ) ,
enacted : route . enacted . clone ( ) ,
retracted : route . retracted . len ( )
} ) ;
2016-03-13 15:29:55 +01:00
import_results . push ( route ) ;
2016-03-05 16:46:04 +01:00
2016-01-21 23:33:52 +01:00
self . report . write ( ) . unwrap ( ) . accrue_block ( & block ) ;
2016-01-18 23:23:32 +01:00
trace! ( target : " client " , " Imported #{} ({}) " , header . number ( ) , header . hash ( ) ) ;
2016-01-14 01:28:37 +01:00
}
2016-02-23 18:44:13 +01:00
2016-03-13 15:29:55 +01:00
let imported = imported_blocks . len ( ) ;
let invalid_blocks = invalid_blocks . into_iter ( ) . collect ::< Vec < H256 > > ( ) ;
2016-02-24 10:55:34 +01:00
{
2016-03-13 15:29:55 +01:00
if ! invalid_blocks . is_empty ( ) {
self . block_queue . mark_as_bad ( & invalid_blocks ) ;
2016-03-10 00:21:07 +01:00
}
2016-03-13 15:29:55 +01:00
if ! imported_blocks . is_empty ( ) {
self . block_queue . mark_as_good ( & imported_blocks ) ;
2016-03-10 00:21:07 +01:00
}
2016-02-24 10:55:34 +01:00
}
{
2016-03-13 15:29:55 +01:00
if ! imported_blocks . is_empty ( ) & & self . block_queue . queue_info ( ) . is_empty ( ) {
let ( enacted , retracted ) = self . calculate_enacted_retracted ( import_results ) ;
2016-05-31 21:17:46 +02:00
if self . queue_info ( ) . is_empty ( ) {
self . miner . chain_new_blocks ( self , & imported_blocks , & invalid_blocks , & enacted , & retracted ) ;
}
2016-02-24 10:55:34 +01:00
io . send ( NetworkIoMessage ::User ( SyncMessage ::NewChainBlocks {
2016-03-13 15:36:03 +01:00
imported : imported_blocks ,
2016-03-13 15:29:55 +01:00
invalid : invalid_blocks ,
enacted : enacted ,
retracted : retracted ,
2016-02-24 10:55:34 +01:00
} ) ) . unwrap ( ) ;
}
2016-02-11 22:14:06 +01:00
}
2016-02-24 10:55:34 +01:00
2016-03-09 12:54:07 +01:00
{
if self . chain_info ( ) . best_block_hash ! = original_best {
2016-05-31 19:52:53 +02:00
self . miner . update_sealing ( self ) ;
2016-03-09 12:54:07 +01:00
}
2016-02-29 14:57:41 +01:00
}
2016-02-23 18:44:13 +01:00
imported
2016-01-13 23:15:44 +01:00
}
2016-01-18 19:23:28 +01:00
2016-05-25 17:35:15 +02:00
/// Attempt to get a copy of a specific block's state.
///
2016-05-26 12:40:29 +02:00
/// This will not fail if given BlockID::Latest.
/// Otherwise, this can fail (but may not) if the DB prunes state.
2016-05-26 11:46:45 +02:00
pub fn state_at ( & self , id : BlockID ) -> Option < State > {
// fast path for latest state.
if let BlockID ::Latest = id . clone ( ) {
return Some ( self . state ( ) )
}
2016-06-02 21:01:47 +02:00
let block_number = match self . block_number ( id . clone ( ) ) {
Some ( num ) = > num ,
None = > return None ,
} ;
2016-06-02 20:34:38 +02:00
2016-06-02 20:52:21 +02:00
self . block_header ( id ) . and_then ( | header | {
2016-05-25 17:35:15 +02:00
let db = self . state_db . lock ( ) . unwrap ( ) . boxed_clone ( ) ;
2016-06-02 21:01:47 +02:00
2016-06-05 22:14:25 +02:00
// early exit for pruned blocks
if db . is_pruned ( ) & & self . chain . best_block_number ( ) > = block_number + HISTORY {
return None ;
}
2016-06-05 23:50:27 +02:00
let root = HeaderView ::new ( & header ) . state_root ( ) ;
2016-06-07 20:44:09 +02:00
State ::from_existing ( db , root , self . engine . account_start_nonce ( ) ) . ok ( )
2016-05-25 17:35:15 +02:00
} )
}
2016-01-26 15:00:22 +01:00
/// Get a copy of the best block's state.
pub fn state ( & self ) -> State {
2016-03-28 09:42:50 +02:00
State ::from_existing ( self . state_db . lock ( ) . unwrap ( ) . boxed_clone ( ) , HeaderView ::new ( & self . best_block_header ( ) ) . state_root ( ) , self . engine . account_start_nonce ( ) )
2016-06-07 20:44:09 +02:00
. expect ( " State root of best block header always valid. " )
2016-01-26 15:00:22 +01:00
}
2016-01-18 19:23:28 +01:00
/// Get info on the cache.
2016-02-25 14:09:39 +01:00
pub fn blockchain_cache_info ( & self ) -> BlockChainCacheSize {
2016-02-22 00:36:59 +01:00
self . chain . cache_size ( )
2016-01-18 19:23:28 +01:00
}
2016-01-18 23:23:32 +01:00
/// Get the report.
pub fn report ( & self ) -> ClientReport {
2016-03-06 22:39:04 +01:00
let mut report = self . report . read ( ) . unwrap ( ) . clone ( ) ;
report . state_db_mem = self . state_db . lock ( ) . unwrap ( ) . mem_used ( ) ;
report
2016-01-18 23:23:32 +01:00
}
2016-01-18 19:23:28 +01:00
/// Tick the client.
pub fn tick ( & self ) {
2016-02-22 00:36:59 +01:00
self . chain . collect_garbage ( ) ;
2016-02-29 18:11:59 +01:00
self . block_queue . collect_garbage ( ) ;
2016-02-02 01:59:14 +01:00
}
/// Set up the cache behaviour.
pub fn configure_cache ( & self , pref_cache_size : usize , max_cache_size : usize ) {
2016-02-22 00:36:59 +01:00
self . chain . configure_cache ( pref_cache_size , max_cache_size ) ;
2016-01-18 19:23:28 +01:00
}
2016-02-10 19:29:27 +01:00
2016-05-23 09:51:36 +02:00
/// Look up the block number for the given block ID.
pub fn block_number ( & self , id : BlockID ) -> Option < BlockNumber > {
match id {
BlockID ::Number ( number ) = > Some ( number ) ,
BlockID ::Hash ( ref hash ) = > self . chain . block_number ( hash ) ,
BlockID ::Earliest = > Some ( 0 ) ,
BlockID ::Latest = > Some ( self . chain . best_block_number ( ) )
}
}
2016-05-19 11:00:32 +02:00
fn block_hash ( chain : & BlockChain , id : BlockID ) -> Option < H256 > {
2016-02-10 19:29:27 +01:00
match id {
2016-05-19 11:00:32 +02:00
BlockID ::Hash ( hash ) = > Some ( hash ) ,
BlockID ::Number ( number ) = > chain . block_hash ( number ) ,
BlockID ::Earliest = > chain . block_hash ( 0 ) ,
BlockID ::Latest = > Some ( chain . best_block_hash ( ) )
2016-02-10 19:29:27 +01:00
}
}
2016-02-13 13:05:28 +01:00
2016-05-19 11:00:32 +02:00
fn transaction_address ( & self , id : TransactionID ) -> Option < TransactionAddress > {
2016-03-20 17:29:39 +01:00
match id {
2016-05-19 11:00:32 +02:00
TransactionID ::Hash ( ref hash ) = > self . chain . transaction_address ( hash ) ,
TransactionID ::Location ( id , index ) = > Self ::block_hash ( & self . chain , id ) . map ( | hash | TransactionAddress {
2016-03-20 17:29:39 +01:00
block_hash : hash ,
2016-05-31 21:03:44 +02:00
index : index ,
2016-03-20 17:29:39 +01:00
} )
}
}
2016-03-08 15:46:44 +01:00
}
2016-02-29 14:57:41 +01:00
2016-03-08 15:46:44 +01:00
impl < V > BlockChainClient for Client < V > where V : Verifier {
2016-05-31 21:03:44 +02:00
fn call ( & self , t : & SignedTransaction , analytics : CallAnalytics ) -> Result < Executed , ExecutionError > {
2016-05-19 11:00:32 +02:00
let header = self . block_header ( BlockID ::Latest ) . unwrap ( ) ;
2016-03-19 21:37:11 +01:00
let view = HeaderView ::new ( & header ) ;
let last_hashes = self . build_last_hashes ( view . hash ( ) ) ;
let env_info = EnvInfo {
number : view . number ( ) ,
author : view . author ( ) ,
timestamp : view . timestamp ( ) ,
difficulty : view . difficulty ( ) ,
last_hashes : last_hashes ,
gas_used : U256 ::zero ( ) ,
gas_limit : U256 ::max_value ( ) ,
} ;
// that's just a copy of the state.
let mut state = self . state ( ) ;
2016-05-14 14:28:44 +02:00
let sender = try ! ( t . sender ( ) . map_err ( | e | {
let message = format! ( " Transaction malformed: {:?} " , e ) ;
ExecutionError ::TransactionMalformed ( message )
} ) ) ;
2016-03-19 21:37:11 +01:00
let balance = state . balance ( & sender ) ;
2016-05-31 21:03:44 +02:00
let needed_balance = t . value + t . gas * t . gas_price ;
if balance < needed_balance {
// give the sender a sufficient balance
state . add_balance ( & sender , & ( needed_balance - balance ) ) ;
}
2016-06-02 13:50:50 +02:00
let options = TransactOptions { tracing : analytics . transaction_tracing , vm_tracing : analytics . vm_tracing , check_nonce : false } ;
2016-05-31 21:03:44 +02:00
let mut ret = Executive ::new ( & mut state , & env_info , self . engine . deref ( ) . deref ( ) , & self . vm_factory ) . transact ( t , options ) ;
2016-06-06 14:33:12 +02:00
2016-05-31 21:03:44 +02:00
// TODO gav move this into Executive.
2016-06-02 12:39:25 +02:00
if analytics . state_diffing {
2016-05-31 21:03:44 +02:00
if let Ok ( ref mut x ) = ret {
2016-06-02 12:39:25 +02:00
x . state_diff = Some ( state . diff_from ( self . state ( ) ) ) ;
2016-05-31 21:03:44 +02:00
}
}
ret
2016-03-19 21:37:11 +01:00
}
2016-05-19 00:44:49 +02:00
fn vm_factory ( & self ) -> & EvmFactory {
& self . vm_factory
}
2016-05-19 11:00:32 +02:00
fn block_header ( & self , id : BlockID ) -> Option < Bytes > {
2016-02-22 00:36:59 +01:00
Self ::block_hash ( & self . chain , id ) . and_then ( | hash | self . chain . block ( & hash ) . map ( | bytes | BlockView ::new ( & bytes ) . rlp ( ) . at ( 0 ) . as_raw ( ) . to_vec ( ) ) )
2016-01-07 21:35:06 +01:00
}
2016-05-19 11:00:32 +02:00
fn block_body ( & self , id : BlockID ) -> Option < Bytes > {
2016-02-22 00:36:59 +01:00
Self ::block_hash ( & self . chain , id ) . and_then ( | hash | {
self . chain . block ( & hash ) . map ( | bytes | {
2016-02-10 19:29:27 +01:00
let rlp = Rlp ::new ( & bytes ) ;
2016-02-11 10:20:15 +01:00
let mut body = RlpStream ::new_list ( 2 ) ;
2016-02-10 19:29:27 +01:00
body . append_raw ( rlp . at ( 1 ) . as_raw ( ) , 1 ) ;
body . append_raw ( rlp . at ( 2 ) . as_raw ( ) , 1 ) ;
body . out ( )
} )
2016-01-07 21:35:06 +01:00
} )
}
2016-05-19 11:00:32 +02:00
fn block ( & self , id : BlockID ) -> Option < Bytes > {
2016-02-22 00:36:59 +01:00
Self ::block_hash ( & self . chain , id ) . and_then ( | hash | {
self . chain . block ( & hash )
2016-02-10 19:29:27 +01:00
} )
2016-01-07 21:35:06 +01:00
}
2016-05-19 11:00:32 +02:00
fn block_status ( & self , id : BlockID ) -> BlockStatus {
2016-02-22 00:36:59 +01:00
match Self ::block_hash ( & self . chain , id ) {
Some ( ref hash ) if self . chain . is_known ( hash ) = > BlockStatus ::InChain ,
2016-02-21 19:46:29 +01:00
Some ( hash ) = > self . block_queue . block_status ( & hash ) ,
2016-02-10 19:29:27 +01:00
None = > BlockStatus ::Unknown
2016-02-02 12:12:32 +01:00
}
2016-01-07 21:35:06 +01:00
}
2016-02-23 18:44:13 +01:00
2016-05-19 11:00:32 +02:00
fn block_total_difficulty ( & self , id : BlockID ) -> Option < U256 > {
2016-02-22 00:36:59 +01:00
Self ::block_hash ( & self . chain , id ) . and_then ( | hash | self . chain . block_details ( & hash ) ) . map ( | d | d . total_difficulty )
2016-01-27 12:31:54 +01:00
}
2016-01-07 21:35:06 +01:00
2016-05-26 12:40:29 +02:00
fn nonce ( & self , address : & Address , id : BlockID ) -> Option < U256 > {
self . state_at ( id ) . map ( | s | s . nonce ( address ) )
2016-03-05 16:46:04 +01:00
}
2016-05-19 11:00:32 +02:00
fn block_hash ( & self , id : BlockID ) -> Option < H256 > {
2016-03-07 15:10:15 +01:00
Self ::block_hash ( & self . chain , id )
2016-02-24 14:16:05 +01:00
}
2016-02-08 10:58:08 +01:00
fn code ( & self , address : & Address ) -> Option < Bytes > {
self . state ( ) . code ( address )
}
2016-05-26 11:46:45 +02:00
fn balance ( & self , address : & Address , id : BlockID ) -> Option < U256 > {
self . state_at ( id ) . map ( | s | s . balance ( address ) )
2016-03-13 12:09:30 +01:00
}
2016-05-26 11:46:45 +02:00
fn storage_at ( & self , address : & Address , position : & H256 , id : BlockID ) -> Option < H256 > {
self . state_at ( id ) . map ( | s | s . storage_at ( address , position ) )
2016-05-25 17:54:20 +02:00
}
2016-05-19 11:00:32 +02:00
fn transaction ( & self , id : TransactionID ) -> Option < LocalizedTransaction > {
2016-03-20 17:29:39 +01:00
self . transaction_address ( id ) . and_then ( | address | self . chain . transaction ( & address ) )
}
2016-05-19 11:00:32 +02:00
fn uncle ( & self , id : UncleID ) -> Option < Header > {
2016-03-22 16:07:42 +01:00
let index = id . 1 ;
self . block ( id . 0 ) . and_then ( | block | BlockView ::new ( & block ) . uncle_at ( index ) )
}
2016-05-19 11:00:32 +02:00
fn transaction_receipt ( & self , id : TransactionID ) -> Option < LocalizedReceipt > {
2016-03-20 18:44:57 +01:00
self . transaction_address ( id ) . and_then ( | address | {
let t = self . chain . block ( & address . block_hash )
. and_then ( | block | BlockView ::new ( & block ) . localized_transaction_at ( address . index ) ) ;
match ( t , self . chain . transaction_receipt ( & address ) ) {
( Some ( tx ) , Some ( receipt ) ) = > {
let block_hash = tx . block_hash . clone ( ) ;
let block_number = tx . block_number . clone ( ) ;
let transaction_hash = tx . hash ( ) ;
let transaction_index = tx . transaction_index ;
2016-05-21 14:49:21 +02:00
let prior_gas_used = match tx . transaction_index {
0 = > U256 ::zero ( ) ,
i = > {
let prior_address = TransactionAddress { block_hash : address . block_hash , index : i - 1 } ;
let prior_receipt = self . chain . transaction_receipt ( & prior_address ) . expect ( " Transaction receipt at `address` exists; `prior_address` has lower index in same block; qed " ) ;
prior_receipt . gas_used
}
} ;
2016-03-20 18:44:57 +01:00
Some ( LocalizedReceipt {
transaction_hash : tx . hash ( ) ,
transaction_index : tx . transaction_index ,
block_hash : tx . block_hash ,
block_number : tx . block_number ,
cumulative_gas_used : receipt . gas_used ,
2016-05-21 14:49:21 +02:00
gas_used : receipt . gas_used - prior_gas_used ,
2016-04-01 11:26:14 +02:00
contract_address : match tx . action {
Action ::Call ( _ ) = > None ,
Action ::Create = > Some ( contract_address ( & tx . sender ( ) . unwrap ( ) , & tx . nonce ) )
} ,
2016-03-20 18:44:57 +01:00
logs : receipt . logs . into_iter ( ) . enumerate ( ) . map ( | ( i , log ) | LocalizedLogEntry {
entry : log ,
block_hash : block_hash . clone ( ) ,
block_number : block_number ,
transaction_hash : transaction_hash . clone ( ) ,
transaction_index : transaction_index ,
log_index : i
} ) . collect ( )
} )
} ,
_ = > None
}
} )
2016-02-09 13:17:44 +01:00
}
2016-01-10 23:37:09 +01:00
fn tree_route ( & self , from : & H256 , to : & H256 ) -> Option < TreeRoute > {
2016-02-29 18:11:59 +01:00
match self . chain . is_known ( from ) & & self . chain . is_known ( to ) {
true = > Some ( self . chain . tree_route ( from . clone ( ) , to . clone ( ) ) ) ,
2016-02-27 01:37:12 +01:00
false = > None
}
2016-01-07 21:35:06 +01:00
}
2016-05-24 21:56:17 +02:00
fn find_uncles ( & self , hash : & H256 ) -> Option < Vec < H256 > > {
self . chain . find_uncle_hashes ( hash , self . engine . maximum_uncle_age ( ) )
}
2016-03-11 20:09:14 +01:00
fn state_data ( & self , hash : & H256 ) -> Option < Bytes > {
self . state_db . lock ( ) . unwrap ( ) . state ( hash )
2016-01-07 21:35:06 +01:00
}
2016-03-11 23:33:01 +01:00
fn block_receipts ( & self , hash : & H256 ) -> Option < Bytes > {
2016-03-13 15:59:25 +01:00
self . chain . block_receipts ( hash ) . map ( | receipts | rlp ::encode ( & receipts ) . to_vec ( ) )
2016-01-07 21:35:06 +01:00
}
2016-01-21 23:33:52 +01:00
fn import_block ( & self , bytes : Bytes ) -> ImportResult {
2016-03-01 00:02:48 +01:00
{
let header = BlockView ::new ( & bytes ) . header_view ( ) ;
2016-03-07 15:10:15 +01:00
if self . chain . is_known ( & header . sha3 ( ) ) {
2016-05-31 16:59:01 +02:00
return Err ( ImportError ::AlreadyInChain . into ( ) ) ;
2016-03-01 00:02:48 +01:00
}
2016-05-19 11:00:32 +02:00
if self . block_status ( BlockID ::Hash ( header . parent_hash ( ) ) ) = = BlockStatus ::Unknown {
2016-05-31 16:59:01 +02:00
return Err ( BlockError ::UnknownParent ( header . parent_hash ( ) ) . into ( ) ) ;
2016-03-01 00:02:48 +01:00
}
2016-02-02 12:12:32 +01:00
}
2016-02-21 19:46:29 +01:00
self . block_queue . import_block ( bytes )
2016-01-07 21:35:06 +01:00
}
2016-01-22 04:54:38 +01:00
fn queue_info ( & self ) -> BlockQueueInfo {
2016-02-21 19:46:29 +01:00
self . block_queue . queue_info ( )
2016-01-07 21:35:06 +01:00
}
2016-01-21 23:33:52 +01:00
fn clear_queue ( & self ) {
2016-02-21 19:46:29 +01:00
self . block_queue . clear ( ) ;
2016-01-07 21:35:06 +01:00
}
fn chain_info ( & self ) -> BlockChainInfo {
BlockChainInfo {
2016-02-22 00:36:59 +01:00
total_difficulty : self . chain . best_block_total_difficulty ( ) ,
pending_total_difficulty : self . chain . best_block_total_difficulty ( ) ,
genesis_hash : self . chain . genesis_hash ( ) ,
best_block_hash : self . chain . best_block_hash ( ) ,
best_block_number : From ::from ( self . chain . best_block_number ( ) )
2016-01-07 21:35:06 +01:00
}
}
2016-02-13 13:05:28 +01:00
2016-05-19 11:00:32 +02:00
fn blocks_with_bloom ( & self , bloom : & H2048 , from_block : BlockID , to_block : BlockID ) -> Option < Vec < BlockNumber > > {
2016-02-13 13:05:28 +01:00
match ( self . block_number ( from_block ) , self . block_number ( to_block ) ) {
2016-02-26 13:38:06 +01:00
( Some ( from ) , Some ( to ) ) = > Some ( self . chain . blocks_with_bloom ( bloom , from , to ) ) ,
2016-02-13 13:05:28 +01:00
_ = > None
}
}
2016-02-17 12:35:37 +01:00
fn logs ( & self , filter : Filter ) -> Vec < LocalizedLogEntry > {
2016-03-10 11:32:10 +01:00
// TODO: lock blockchain only once
2016-02-17 12:35:37 +01:00
let mut blocks = filter . bloom_possibilities ( ) . iter ( )
2016-02-24 10:23:25 +01:00
. filter_map ( | bloom | self . blocks_with_bloom ( bloom , filter . from_block . clone ( ) , filter . to_block . clone ( ) ) )
2016-02-17 12:35:37 +01:00
. flat_map ( | m | m )
// remove duplicate elements
. collect ::< HashSet < u64 > > ( )
. into_iter ( )
. collect ::< Vec < u64 > > ( ) ;
blocks . sort ( ) ;
blocks . into_iter ( )
2016-02-26 13:38:06 +01:00
. filter_map ( | number | self . chain . block_hash ( number ) . map ( | hash | ( number , hash ) ) )
. filter_map ( | ( number , hash ) | self . chain . block_receipts ( & hash ) . map ( | r | ( number , hash , r . receipts ) ) )
. filter_map ( | ( number , hash , receipts ) | self . chain . block ( & hash ) . map ( | ref b | ( number , hash , receipts , BlockView ::new ( b ) . transaction_hashes ( ) ) ) )
2016-02-24 10:23:25 +01:00
. flat_map ( | ( number , hash , receipts , hashes ) | {
2016-02-17 12:35:37 +01:00
let mut log_index = 0 ;
receipts . into_iter ( )
. enumerate ( )
2016-02-24 10:23:25 +01:00
. flat_map ( | ( index , receipt ) | {
2016-02-17 12:35:37 +01:00
log_index + = receipt . logs . len ( ) ;
receipt . logs . into_iter ( )
. enumerate ( )
. filter ( | tuple | filter . matches ( & tuple . 1 ) )
. map ( | ( i , log ) | LocalizedLogEntry {
entry : log ,
block_hash : hash . clone ( ) ,
2016-03-20 18:44:57 +01:00
block_number : number ,
2016-02-17 14:57:54 +01:00
transaction_hash : hashes . get ( index ) . cloned ( ) . unwrap_or_else ( H256 ::new ) ,
2016-02-17 12:35:37 +01:00
transaction_index : index ,
2016-02-17 14:13:51 +01:00
log_index : log_index + i
2016-02-17 12:35:37 +01:00
} )
. collect ::< Vec < LocalizedLogEntry > > ( )
} )
. collect ::< Vec < LocalizedLogEntry > > ( )
2016-02-26 19:43:06 +01:00
2016-02-17 12:35:37 +01:00
} )
. collect ( )
}
2016-04-28 21:47:44 +02:00
2016-05-02 12:17:30 +02:00
fn filter_traces ( & self , filter : TraceFilter ) -> Option < Vec < LocalizedTrace > > {
let start = self . block_number ( filter . range . start ) ;
let end = self . block_number ( filter . range . end ) ;
if start . is_some ( ) & & end . is_some ( ) {
let filter = trace ::Filter {
range : start . unwrap ( ) as usize .. end . unwrap ( ) as usize ,
from_address : From ::from ( filter . from_address ) ,
to_address : From ::from ( filter . to_address ) ,
} ;
let traces = self . tracedb . filter ( & filter ) ;
Some ( traces )
} else {
None
}
}
fn trace ( & self , trace : TraceId ) -> Option < LocalizedTrace > {
let trace_address = trace . address ;
self . transaction_address ( trace . transaction )
. and_then ( | tx_address | {
2016-05-19 11:00:32 +02:00
self . block_number ( BlockID ::Hash ( tx_address . block_hash ) )
2016-05-02 12:17:30 +02:00
. and_then ( | number | self . tracedb . trace ( number , tx_address . index , trace_address ) )
} )
}
2016-05-19 11:00:32 +02:00
fn transaction_traces ( & self , transaction : TransactionID ) -> Option < Vec < LocalizedTrace > > {
2016-05-02 12:17:30 +02:00
self . transaction_address ( transaction )
. and_then ( | tx_address | {
2016-05-19 11:00:32 +02:00
self . block_number ( BlockID ::Hash ( tx_address . block_hash ) )
2016-05-02 12:17:30 +02:00
. and_then ( | number | self . tracedb . transaction_traces ( number , tx_address . index ) )
} )
}
2016-05-19 11:00:32 +02:00
fn block_traces ( & self , block : BlockID ) -> Option < Vec < LocalizedTrace > > {
2016-05-02 12:17:30 +02:00
self . block_number ( block )
. and_then ( | number | self . tracedb . block_traces ( number ) )
}
2016-04-28 21:47:44 +02:00
fn last_hashes ( & self ) -> LastHashes {
self . build_last_hashes ( self . chain . best_block_hash ( ) )
}
2016-05-31 19:52:53 +02:00
fn import_transactions ( & self , transactions : Vec < SignedTransaction > ) -> Vec < Result < TransactionImportResult , Error > > {
let fetch_account = | a : & Address | AccountDetails {
nonce : self . latest_nonce ( a ) ,
balance : self . latest_balance ( a ) ,
} ;
self . miner . import_transactions ( transactions , fetch_account )
}
fn all_transactions ( & self ) -> Vec < SignedTransaction > {
self . miner . all_transactions ( )
}
2016-01-07 21:35:06 +01:00
}
2016-02-10 12:50:27 +01:00
2016-05-31 20:33:26 +02:00
impl < V > MiningBlockChainClient for Client < V > where V : Verifier {
2016-06-06 14:33:12 +02:00
fn prepare_open_block ( & self , author : Address , gas_floor_target : U256 , extra_data : Bytes ) -> OpenBlock {
2016-05-31 16:41:15 +02:00
let engine = self . engine . deref ( ) . deref ( ) ;
let h = self . chain . best_block_hash ( ) ;
2016-06-06 14:33:12 +02:00
let mut open_block = OpenBlock ::new (
2016-05-31 16:41:15 +02:00
engine ,
& self . vm_factory ,
false , // TODO: this will need to be parameterised once we want to do immediate mining insertion.
self . state_db . lock ( ) . unwrap ( ) . boxed_clone ( ) ,
2016-06-06 14:33:12 +02:00
& self . chain . block_header ( & h ) . expect ( " h is best block hash: so it's header must exist: qed " ) ,
2016-05-31 16:41:15 +02:00
self . build_last_hashes ( h . clone ( ) ) ,
author ,
gas_floor_target ,
extra_data ,
2016-06-16 12:44:08 +02:00
) . expect ( " OpenBlock::new only fails if parent state root invalid; state root of best block's header is never invalid; qed " ) ;
2016-05-31 16:41:15 +02:00
// Add uncles
self . chain
. find_uncle_headers ( & h , engine . maximum_uncle_age ( ) )
. unwrap ( )
. into_iter ( )
. take ( engine . maximum_uncle_count ( ) )
. foreach ( | h | {
2016-06-06 14:33:12 +02:00
open_block . push_uncle ( h ) . unwrap ( ) ;
2016-05-31 16:41:15 +02:00
} ) ;
2016-06-06 14:33:12 +02:00
open_block
2016-05-31 16:41:15 +02:00
}
2016-01-07 21:35:06 +01:00
}
2016-02-10 12:50:27 +01:00
2016-02-10 15:28:43 +01:00
impl MayPanic for Client {
fn on_panic < F > ( & self , closure : F ) where F : OnPanicListener {
2016-02-10 12:50:27 +01:00
self . panic_handler . on_panic ( closure ) ;
}
}