Merge branch 'master' into block_header_rpc
This commit is contained in:
commit
e094043b80
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -784,8 +784,7 @@ name = "ethstore"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"docopt 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"docopt 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"ethcore-devtools 1.7.0",
|
"ethcore-bigint 0.1.2",
|
||||||
"ethcore-util 1.7.0",
|
|
||||||
"ethcrypto 0.1.0",
|
"ethcrypto 0.1.0",
|
||||||
"ethkey 0.2.0",
|
"ethkey 0.2.0",
|
||||||
"itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
"itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -800,6 +799,7 @@ dependencies = [
|
|||||||
"serde_derive 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_derive 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_json 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_json 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"smallvec 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"smallvec 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
@ -1761,7 +1761,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "parity-ui-precompiled"
|
name = "parity-ui-precompiled"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
source = "git+https://github.com/paritytech/js-precompiled.git#b4c41885c6e02c64fb773546b2f135f56ea7022f"
|
source = "git+https://github.com/paritytech/js-precompiled.git#f4fa3048dcb0e202c53a61b9b9e7dd446fa1c088"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
@ -61,6 +61,7 @@ impl Default for CacheSizes {
|
|||||||
///
|
///
|
||||||
/// Note that almost all getter methods take `&mut self` due to the necessity to update
|
/// Note that almost all getter methods take `&mut self` due to the necessity to update
|
||||||
/// the underlying LRU-caches on read.
|
/// the underlying LRU-caches on read.
|
||||||
|
/// [LRU-cache](https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_Recently_Used_.28LRU.29)
|
||||||
pub struct Cache {
|
pub struct Cache {
|
||||||
headers: MemoryLruCache<H256, encoded::Header>,
|
headers: MemoryLruCache<H256, encoded::Header>,
|
||||||
canon_hashes: MemoryLruCache<BlockNumber, H256>,
|
canon_hashes: MemoryLruCache<BlockNumber, H256>,
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
//!
|
//!
|
||||||
//! Each CHT is a trie mapping block numbers to canonical hashes and total difficulty.
|
//! Each CHT is a trie mapping block numbers to canonical hashes and total difficulty.
|
||||||
//! One is generated for every `SIZE` blocks, allowing us to discard those blocks in
|
//! One is generated for every `SIZE` blocks, allowing us to discard those blocks in
|
||||||
//! favor the the trie root. When the "ancient" blocks need to be accessed, we simply
|
//! favor of the trie root. When the "ancient" blocks need to be accessed, we simply
|
||||||
//! request an inclusion proof of a specific block number against the trie with the
|
//! request an inclusion proof of a specific block number against the trie with the
|
||||||
//! root has. A correct proof implies that the claimed block is identical to the one
|
//! root has. A correct proof implies that the claimed block is identical to the one
|
||||||
//! we discarded.
|
//! we discarded.
|
||||||
|
@ -39,6 +39,9 @@ use rlp::{Encodable, Decodable, DecoderError, RlpStream, Rlp, UntrustedRlp};
|
|||||||
use util::{H256, U256, HeapSizeOf, RwLock};
|
use util::{H256, U256, HeapSizeOf, RwLock};
|
||||||
use util::kvdb::{DBTransaction, KeyValueDB};
|
use util::kvdb::{DBTransaction, KeyValueDB};
|
||||||
|
|
||||||
|
use cache::Cache;
|
||||||
|
use util::Mutex;
|
||||||
|
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
/// Store at least this many candidate headers at all times.
|
/// Store at least this many candidate headers at all times.
|
||||||
@ -138,11 +141,12 @@ pub struct HeaderChain {
|
|||||||
best_block: RwLock<BlockDescriptor>,
|
best_block: RwLock<BlockDescriptor>,
|
||||||
db: Arc<KeyValueDB>,
|
db: Arc<KeyValueDB>,
|
||||||
col: Option<u32>,
|
col: Option<u32>,
|
||||||
|
cache: Arc<Mutex<Cache>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HeaderChain {
|
impl HeaderChain {
|
||||||
/// Create a new header chain given this genesis block and database to read from.
|
/// Create a new header chain given this genesis block and database to read from.
|
||||||
pub fn new(db: Arc<KeyValueDB>, col: Option<u32>, genesis: &[u8]) -> Result<Self, String> {
|
pub fn new(db: Arc<KeyValueDB>, col: Option<u32>, genesis: &[u8], cache: Arc<Mutex<Cache>>) -> Result<Self, String> {
|
||||||
use ethcore::views::HeaderView;
|
use ethcore::views::HeaderView;
|
||||||
|
|
||||||
let chain = if let Some(current) = db.get(col, CURRENT_KEY)? {
|
let chain = if let Some(current) = db.get(col, CURRENT_KEY)? {
|
||||||
@ -186,6 +190,7 @@ impl HeaderChain {
|
|||||||
candidates: RwLock::new(candidates),
|
candidates: RwLock::new(candidates),
|
||||||
db: db,
|
db: db,
|
||||||
col: col,
|
col: col,
|
||||||
|
cache: cache,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let g_view = HeaderView::new(genesis);
|
let g_view = HeaderView::new(genesis);
|
||||||
@ -199,6 +204,7 @@ impl HeaderChain {
|
|||||||
candidates: RwLock::new(BTreeMap::new()),
|
candidates: RwLock::new(BTreeMap::new()),
|
||||||
db: db,
|
db: db,
|
||||||
col: col,
|
col: col,
|
||||||
|
cache: cache,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -355,15 +361,44 @@ impl HeaderChain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a block's hash by ID. In the case of query by number, only canonical results
|
||||||
|
/// will be returned.
|
||||||
|
pub fn block_hash(&self, id: BlockId) -> Option<H256> {
|
||||||
|
match id {
|
||||||
|
BlockId::Earliest => Some(self.genesis_hash()),
|
||||||
|
BlockId::Hash(hash) => Some(hash),
|
||||||
|
BlockId::Number(num) => {
|
||||||
|
if self.best_block.read().number < num { return None }
|
||||||
|
self.candidates.read().get(&num).map(|entry| entry.canonical_hash)
|
||||||
|
}
|
||||||
|
BlockId::Latest | BlockId::Pending => {
|
||||||
|
Some(self.best_block.read().hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get a block header. In the case of query by number, only canonical blocks
|
/// Get a block header. In the case of query by number, only canonical blocks
|
||||||
/// will be returned.
|
/// will be returned.
|
||||||
pub fn block_header(&self, id: BlockId) -> Option<encoded::Header> {
|
pub fn block_header(&self, id: BlockId) -> Option<encoded::Header> {
|
||||||
let load_from_db = |hash: H256| {
|
let load_from_db = |hash: H256| {
|
||||||
match self.db.get(self.col, &hash) {
|
let mut cache = self.cache.lock();
|
||||||
Ok(val) => val.map(|x| x.to_vec()).map(encoded::Header::new),
|
|
||||||
Err(e) => {
|
match cache.block_header(&hash) {
|
||||||
warn!(target: "chain", "Failed to read from database: {}", e);
|
Some(header) => Some(header),
|
||||||
None
|
None => {
|
||||||
|
match self.db.get(self.col, &hash) {
|
||||||
|
Ok(db_value) => {
|
||||||
|
db_value.map(|x| x.to_vec()).map(encoded::Header::new)
|
||||||
|
.and_then(|header| {
|
||||||
|
cache.insert_block_header(hash.clone(), header.clone());
|
||||||
|
Some(header)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
warn!(target: "chain", "Failed to read from database: {}", e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -395,6 +430,28 @@ impl HeaderChain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a block's chain score.
|
||||||
|
/// Returns nothing for non-canonical blocks.
|
||||||
|
pub fn score(&self, id: BlockId) -> Option<U256> {
|
||||||
|
let genesis_hash = self.genesis_hash();
|
||||||
|
match id {
|
||||||
|
BlockId::Earliest | BlockId::Number(0) => Some(self.genesis_header.difficulty()),
|
||||||
|
BlockId::Hash(hash) if hash == genesis_hash => Some(self.genesis_header.difficulty()),
|
||||||
|
BlockId::Hash(hash) => match self.block_header(BlockId::Hash(hash)) {
|
||||||
|
Some(header) => self.candidates.read().get(&header.number())
|
||||||
|
.and_then(|era| era.candidates.iter().find(|e| e.hash == hash))
|
||||||
|
.map(|c| c.total_difficulty),
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
BlockId::Number(num) => {
|
||||||
|
let candidates = self.candidates.read();
|
||||||
|
if self.best_block.read().number < num { return None }
|
||||||
|
candidates.get(&num).map(|era| era.candidates[0].total_difficulty)
|
||||||
|
}
|
||||||
|
BlockId::Latest | BlockId::Pending => Some(self.best_block.read().total_difficulty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the best block's header.
|
/// Get the best block's header.
|
||||||
pub fn best_header(&self) -> encoded::Header {
|
pub fn best_header(&self) -> encoded::Header {
|
||||||
self.block_header(BlockId::Latest).expect("Header for best block always stored; qed")
|
self.block_header(BlockId::Latest).expect("Header for best block always stored; qed")
|
||||||
@ -493,6 +550,10 @@ mod tests {
|
|||||||
use ethcore::ids::BlockId;
|
use ethcore::ids::BlockId;
|
||||||
use ethcore::header::Header;
|
use ethcore::header::Header;
|
||||||
use ethcore::spec::Spec;
|
use ethcore::spec::Spec;
|
||||||
|
use cache::Cache;
|
||||||
|
|
||||||
|
use time::Duration;
|
||||||
|
use util::Mutex;
|
||||||
|
|
||||||
fn make_db() -> Arc<::util::KeyValueDB> {
|
fn make_db() -> Arc<::util::KeyValueDB> {
|
||||||
Arc::new(::util::kvdb::in_memory(0))
|
Arc::new(::util::kvdb::in_memory(0))
|
||||||
@ -504,7 +565,9 @@ mod tests {
|
|||||||
let genesis_header = spec.genesis_header();
|
let genesis_header = spec.genesis_header();
|
||||||
let db = make_db();
|
let db = make_db();
|
||||||
|
|
||||||
let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header)).unwrap();
|
let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::hours(6))));
|
||||||
|
|
||||||
|
let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header), cache).unwrap();
|
||||||
|
|
||||||
let mut parent_hash = genesis_header.hash();
|
let mut parent_hash = genesis_header.hash();
|
||||||
let mut rolling_timestamp = genesis_header.timestamp();
|
let mut rolling_timestamp = genesis_header.timestamp();
|
||||||
@ -534,9 +597,10 @@ mod tests {
|
|||||||
fn reorganize() {
|
fn reorganize() {
|
||||||
let spec = Spec::new_test();
|
let spec = Spec::new_test();
|
||||||
let genesis_header = spec.genesis_header();
|
let genesis_header = spec.genesis_header();
|
||||||
|
|
||||||
let db = make_db();
|
let db = make_db();
|
||||||
let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header)).unwrap();
|
let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::hours(6))));
|
||||||
|
|
||||||
|
let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header), cache).unwrap();
|
||||||
|
|
||||||
let mut parent_hash = genesis_header.hash();
|
let mut parent_hash = genesis_header.hash();
|
||||||
let mut rolling_timestamp = genesis_header.timestamp();
|
let mut rolling_timestamp = genesis_header.timestamp();
|
||||||
@ -617,8 +681,10 @@ mod tests {
|
|||||||
let spec = Spec::new_test();
|
let spec = Spec::new_test();
|
||||||
let genesis_header = spec.genesis_header();
|
let genesis_header = spec.genesis_header();
|
||||||
let db = make_db();
|
let db = make_db();
|
||||||
|
let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::hours(6))));
|
||||||
|
|
||||||
|
let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header), cache).unwrap();
|
||||||
|
|
||||||
let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header)).unwrap();
|
|
||||||
|
|
||||||
assert!(chain.block_header(BlockId::Earliest).is_some());
|
assert!(chain.block_header(BlockId::Earliest).is_some());
|
||||||
assert!(chain.block_header(BlockId::Latest).is_some());
|
assert!(chain.block_header(BlockId::Latest).is_some());
|
||||||
@ -630,9 +696,10 @@ mod tests {
|
|||||||
let spec = Spec::new_test();
|
let spec = Spec::new_test();
|
||||||
let genesis_header = spec.genesis_header();
|
let genesis_header = spec.genesis_header();
|
||||||
let db = make_db();
|
let db = make_db();
|
||||||
|
let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::hours(6))));
|
||||||
|
|
||||||
{
|
{
|
||||||
let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header)).unwrap();
|
let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header), cache.clone()).unwrap();
|
||||||
let mut parent_hash = genesis_header.hash();
|
let mut parent_hash = genesis_header.hash();
|
||||||
let mut rolling_timestamp = genesis_header.timestamp();
|
let mut rolling_timestamp = genesis_header.timestamp();
|
||||||
for i in 1..10000 {
|
for i in 1..10000 {
|
||||||
@ -652,7 +719,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header)).unwrap();
|
let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header), cache.clone()).unwrap();
|
||||||
assert!(chain.block_header(BlockId::Number(10)).is_none());
|
assert!(chain.block_header(BlockId::Number(10)).is_none());
|
||||||
assert!(chain.block_header(BlockId::Number(9000)).is_some());
|
assert!(chain.block_header(BlockId::Number(9000)).is_some());
|
||||||
assert!(chain.cht_root(2).is_some());
|
assert!(chain.cht_root(2).is_some());
|
||||||
@ -665,9 +732,10 @@ mod tests {
|
|||||||
let spec = Spec::new_test();
|
let spec = Spec::new_test();
|
||||||
let genesis_header = spec.genesis_header();
|
let genesis_header = spec.genesis_header();
|
||||||
let db = make_db();
|
let db = make_db();
|
||||||
|
let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::hours(6))));
|
||||||
|
|
||||||
{
|
{
|
||||||
let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header)).unwrap();
|
let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header), cache.clone()).unwrap();
|
||||||
let mut parent_hash = genesis_header.hash();
|
let mut parent_hash = genesis_header.hash();
|
||||||
let mut rolling_timestamp = genesis_header.timestamp();
|
let mut rolling_timestamp = genesis_header.timestamp();
|
||||||
|
|
||||||
@ -709,7 +777,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// after restoration, non-canonical eras should still be loaded.
|
// after restoration, non-canonical eras should still be loaded.
|
||||||
let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header)).unwrap();
|
let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header), cache.clone()).unwrap();
|
||||||
assert_eq!(chain.block_header(BlockId::Latest).unwrap().number(), 10);
|
assert_eq!(chain.block_header(BlockId::Latest).unwrap().number(), 10);
|
||||||
assert!(chain.candidates.read().get(&100).is_some())
|
assert!(chain.candidates.read().get(&100).is_some())
|
||||||
}
|
}
|
||||||
|
@ -31,11 +31,13 @@ use ethcore::service::ClientIoMessage;
|
|||||||
use ethcore::encoded;
|
use ethcore::encoded;
|
||||||
use io::IoChannel;
|
use io::IoChannel;
|
||||||
|
|
||||||
use util::{H256, Mutex, RwLock};
|
use util::{H256, U256, Mutex, RwLock};
|
||||||
use util::kvdb::{KeyValueDB, CompactionProfile};
|
use util::kvdb::{KeyValueDB, CompactionProfile};
|
||||||
|
|
||||||
use self::header_chain::{AncestryIter, HeaderChain};
|
use self::header_chain::{AncestryIter, HeaderChain};
|
||||||
|
|
||||||
|
use cache::Cache;
|
||||||
|
|
||||||
pub use self::service::Service;
|
pub use self::service::Service;
|
||||||
|
|
||||||
mod header_chain;
|
mod header_chain;
|
||||||
@ -65,12 +67,18 @@ pub trait LightChainClient: Send + Sync {
|
|||||||
/// parent queued prior.
|
/// parent queued prior.
|
||||||
fn queue_header(&self, header: Header) -> Result<H256, BlockImportError>;
|
fn queue_header(&self, header: Header) -> Result<H256, BlockImportError>;
|
||||||
|
|
||||||
|
/// Attempt to get a block hash by block id.
|
||||||
|
fn block_hash(&self, id: BlockId) -> Option<H256>;
|
||||||
|
|
||||||
/// Attempt to get block header by block id.
|
/// Attempt to get block header by block id.
|
||||||
fn block_header(&self, id: BlockId) -> Option<encoded::Header>;
|
fn block_header(&self, id: BlockId) -> Option<encoded::Header>;
|
||||||
|
|
||||||
/// Get the best block header.
|
/// Get the best block header.
|
||||||
fn best_block_header(&self) -> encoded::Header;
|
fn best_block_header(&self) -> encoded::Header;
|
||||||
|
|
||||||
|
/// Get a block's chain score by ID.
|
||||||
|
fn score(&self, id: BlockId) -> Option<U256>;
|
||||||
|
|
||||||
/// Get an iterator over a block and its ancestry.
|
/// Get an iterator over a block and its ancestry.
|
||||||
fn ancestry_iter<'a>(&'a self, start: BlockId) -> Box<Iterator<Item=encoded::Header> + 'a>;
|
fn ancestry_iter<'a>(&'a self, start: BlockId) -> Box<Iterator<Item=encoded::Header> + 'a>;
|
||||||
|
|
||||||
@ -127,13 +135,13 @@ pub struct Client {
|
|||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
/// Create a new `Client`.
|
/// Create a new `Client`.
|
||||||
pub fn new(config: Config, db: Arc<KeyValueDB>, chain_col: Option<u32>, spec: &Spec, io_channel: IoChannel<ClientIoMessage>) -> Result<Self, String> {
|
pub fn new(config: Config, db: Arc<KeyValueDB>, chain_col: Option<u32>, spec: &Spec, io_channel: IoChannel<ClientIoMessage>, cache: Arc<Mutex<Cache>>) -> Result<Self, String> {
|
||||||
let gh = ::rlp::encode(&spec.genesis_header());
|
let gh = ::rlp::encode(&spec.genesis_header());
|
||||||
|
|
||||||
Ok(Client {
|
Ok(Client {
|
||||||
queue: HeaderQueue::new(config.queue, spec.engine.clone(), io_channel, true),
|
queue: HeaderQueue::new(config.queue, spec.engine.clone(), io_channel, true),
|
||||||
engine: spec.engine.clone(),
|
engine: spec.engine.clone(),
|
||||||
chain: HeaderChain::new(db.clone(), chain_col, &gh)?,
|
chain: HeaderChain::new(db.clone(), chain_col, &gh, cache)?,
|
||||||
report: RwLock::new(ClientReport::default()),
|
report: RwLock::new(ClientReport::default()),
|
||||||
import_lock: Mutex::new(()),
|
import_lock: Mutex::new(()),
|
||||||
db: db,
|
db: db,
|
||||||
@ -142,10 +150,10 @@ impl Client {
|
|||||||
|
|
||||||
/// Create a new `Client` backed purely in-memory.
|
/// Create a new `Client` backed purely in-memory.
|
||||||
/// This will ignore all database options in the configuration.
|
/// This will ignore all database options in the configuration.
|
||||||
pub fn in_memory(config: Config, spec: &Spec, io_channel: IoChannel<ClientIoMessage>) -> Self {
|
pub fn in_memory(config: Config, spec: &Spec, io_channel: IoChannel<ClientIoMessage>, cache: Arc<Mutex<Cache>>) -> Self {
|
||||||
let db = ::util::kvdb::in_memory(0);
|
let db = ::util::kvdb::in_memory(0);
|
||||||
|
|
||||||
Client::new(config, Arc::new(db), None, spec, io_channel).expect("New DB creation infallible; qed")
|
Client::new(config, Arc::new(db), None, spec, io_channel, cache).expect("New DB creation infallible; qed")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Import a header to the queue for additional verification.
|
/// Import a header to the queue for additional verification.
|
||||||
@ -188,6 +196,11 @@ impl Client {
|
|||||||
self.queue.queue_info()
|
self.queue.queue_info()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attempt to get a block hash by block id.
|
||||||
|
pub fn block_hash(&self, id: BlockId) -> Option<H256> {
|
||||||
|
self.chain.block_hash(id)
|
||||||
|
}
|
||||||
|
|
||||||
/// Get a block header by Id.
|
/// Get a block header by Id.
|
||||||
pub fn block_header(&self, id: BlockId) -> Option<encoded::Header> {
|
pub fn block_header(&self, id: BlockId) -> Option<encoded::Header> {
|
||||||
self.chain.block_header(id)
|
self.chain.block_header(id)
|
||||||
@ -198,6 +211,11 @@ impl Client {
|
|||||||
self.chain.best_header()
|
self.chain.best_header()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a block's chain score.
|
||||||
|
pub fn score(&self, id: BlockId) -> Option<U256> {
|
||||||
|
self.chain.score(id)
|
||||||
|
}
|
||||||
|
|
||||||
/// Get an iterator over a block and its ancestry.
|
/// Get an iterator over a block and its ancestry.
|
||||||
pub fn ancestry_iter(&self, start: BlockId) -> AncestryIter {
|
pub fn ancestry_iter(&self, start: BlockId) -> AncestryIter {
|
||||||
self.chain.ancestry_iter(start)
|
self.chain.ancestry_iter(start)
|
||||||
@ -315,6 +333,10 @@ impl LightChainClient for Client {
|
|||||||
self.import_header(header)
|
self.import_header(header)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn block_hash(&self, id: BlockId) -> Option<H256> {
|
||||||
|
Client::block_hash(self, id)
|
||||||
|
}
|
||||||
|
|
||||||
fn block_header(&self, id: BlockId) -> Option<encoded::Header> {
|
fn block_header(&self, id: BlockId) -> Option<encoded::Header> {
|
||||||
Client::block_header(self, id)
|
Client::block_header(self, id)
|
||||||
}
|
}
|
||||||
@ -323,6 +345,10 @@ impl LightChainClient for Client {
|
|||||||
Client::best_block_header(self)
|
Client::best_block_header(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn score(&self, id: BlockId) -> Option<U256> {
|
||||||
|
Client::score(self, id)
|
||||||
|
}
|
||||||
|
|
||||||
fn ancestry_iter<'a>(&'a self, start: BlockId) -> Box<Iterator<Item=encoded::Header> + 'a> {
|
fn ancestry_iter<'a>(&'a self, start: BlockId) -> Box<Iterator<Item=encoded::Header> + 'a> {
|
||||||
Box::new(Client::ancestry_iter(self, start))
|
Box::new(Client::ancestry_iter(self, start))
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,9 @@ use ethcore::spec::Spec;
|
|||||||
use io::{IoContext, IoError, IoHandler, IoService};
|
use io::{IoContext, IoError, IoHandler, IoService};
|
||||||
use util::kvdb::{Database, DatabaseConfig};
|
use util::kvdb::{Database, DatabaseConfig};
|
||||||
|
|
||||||
|
use cache::Cache;
|
||||||
|
use util::Mutex;
|
||||||
|
|
||||||
use super::{Client, Config as ClientConfig};
|
use super::{Client, Config as ClientConfig};
|
||||||
|
|
||||||
/// Errors on service initialization.
|
/// Errors on service initialization.
|
||||||
@ -55,7 +58,8 @@ pub struct Service {
|
|||||||
|
|
||||||
impl Service {
|
impl Service {
|
||||||
/// Start the service: initialize I/O workers and client itself.
|
/// Start the service: initialize I/O workers and client itself.
|
||||||
pub fn start(config: ClientConfig, spec: &Spec, path: &Path) -> Result<Self, Error> {
|
pub fn start(config: ClientConfig, spec: &Spec, path: &Path, cache: Arc<Mutex<Cache>>) -> Result<Self, Error> {
|
||||||
|
|
||||||
// initialize database.
|
// initialize database.
|
||||||
let mut db_config = DatabaseConfig::with_columns(db::NUM_COLUMNS);
|
let mut db_config = DatabaseConfig::with_columns(db::NUM_COLUMNS);
|
||||||
|
|
||||||
@ -78,6 +82,7 @@ impl Service {
|
|||||||
db::COL_LIGHT_CHAIN,
|
db::COL_LIGHT_CHAIN,
|
||||||
spec,
|
spec,
|
||||||
io_service.channel(),
|
io_service.channel(),
|
||||||
|
cache,
|
||||||
).map_err(Error::Database)?);
|
).map_err(Error::Database)?);
|
||||||
io_service.register_handler(Arc::new(ImportBlocks(client.clone()))).map_err(Error::Io)?;
|
io_service.register_handler(Arc::new(ImportBlocks(client.clone()))).map_err(Error::Io)?;
|
||||||
Ok(Service {
|
Ok(Service {
|
||||||
@ -112,11 +117,18 @@ mod tests {
|
|||||||
use super::Service;
|
use super::Service;
|
||||||
use devtools::RandomTempPath;
|
use devtools::RandomTempPath;
|
||||||
use ethcore::spec::Spec;
|
use ethcore::spec::Spec;
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
use cache::Cache;
|
||||||
|
use time::Duration;
|
||||||
|
use util::Mutex;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_works() {
|
fn it_works() {
|
||||||
let spec = Spec::new_test();
|
let spec = Spec::new_test();
|
||||||
let temp_path = RandomTempPath::new();
|
let temp_path = RandomTempPath::new();
|
||||||
Service::start(Default::default(), &spec, temp_path.as_path()).unwrap();
|
let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::hours(6))));
|
||||||
|
|
||||||
|
Service::start(Default::default(), &spec, temp_path.as_path(), cache).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
//! PIP Protocol Version 1 implementation.
|
//! PLP Protocol Version 1 implementation.
|
||||||
//!
|
//!
|
||||||
//! This uses a "Provider" to answer requests.
|
//! This uses a "Provider" to answer requests.
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ use futures::sync::oneshot::{self, Sender, Receiver};
|
|||||||
use network::PeerId;
|
use network::PeerId;
|
||||||
use rlp::RlpStream;
|
use rlp::RlpStream;
|
||||||
use util::{Bytes, RwLock, Mutex, U256, H256};
|
use util::{Bytes, RwLock, Mutex, U256, H256};
|
||||||
use util::sha3::{SHA3_NULL_RLP, SHA3_EMPTY_LIST_RLP};
|
use util::sha3::{SHA3_NULL_RLP, SHA3_EMPTY, SHA3_EMPTY_LIST_RLP};
|
||||||
|
|
||||||
use net::{self, Handler, Status, Capabilities, Announcement, EventContext, BasicContext, ReqId};
|
use net::{self, Handler, Status, Capabilities, Announcement, EventContext, BasicContext, ReqId};
|
||||||
use cache::Cache;
|
use cache::Cache;
|
||||||
@ -83,7 +83,7 @@ enum Pending {
|
|||||||
HeaderByHash(request::HeaderByHash, Sender<encoded::Header>),
|
HeaderByHash(request::HeaderByHash, Sender<encoded::Header>),
|
||||||
Block(request::Body, Sender<encoded::Block>),
|
Block(request::Body, Sender<encoded::Block>),
|
||||||
BlockReceipts(request::BlockReceipts, Sender<Vec<Receipt>>),
|
BlockReceipts(request::BlockReceipts, Sender<Vec<Receipt>>),
|
||||||
Account(request::Account, Sender<Option<BasicAccount>>),
|
Account(request::Account, Sender<BasicAccount>),
|
||||||
Code(request::Code, Sender<Bytes>),
|
Code(request::Code, Sender<Bytes>),
|
||||||
TxProof(request::TransactionProof, Sender<Result<Executed, ExecutionError>>),
|
TxProof(request::TransactionProof, Sender<Result<Executed, ExecutionError>>),
|
||||||
}
|
}
|
||||||
@ -136,18 +136,20 @@ pub struct OnDemand {
|
|||||||
pending_requests: RwLock<HashMap<ReqId, Pending>>,
|
pending_requests: RwLock<HashMap<ReqId, Pending>>,
|
||||||
cache: Arc<Mutex<Cache>>,
|
cache: Arc<Mutex<Cache>>,
|
||||||
orphaned_requests: RwLock<Vec<Pending>>,
|
orphaned_requests: RwLock<Vec<Pending>>,
|
||||||
|
start_nonce: U256,
|
||||||
}
|
}
|
||||||
|
|
||||||
const RECEIVER_IN_SCOPE: &'static str = "Receiver is still in scope, so it's not dropped; qed";
|
const RECEIVER_IN_SCOPE: &'static str = "Receiver is still in scope, so it's not dropped; qed";
|
||||||
|
|
||||||
impl OnDemand {
|
impl OnDemand {
|
||||||
/// Create a new `OnDemand` service with the given cache.
|
/// Create a new `OnDemand` service with the given cache.
|
||||||
pub fn new(cache: Arc<Mutex<Cache>>) -> Self {
|
pub fn new(cache: Arc<Mutex<Cache>>, account_start_nonce: U256) -> Self {
|
||||||
OnDemand {
|
OnDemand {
|
||||||
peers: RwLock::new(HashMap::new()),
|
peers: RwLock::new(HashMap::new()),
|
||||||
pending_requests: RwLock::new(HashMap::new()),
|
pending_requests: RwLock::new(HashMap::new()),
|
||||||
cache: cache,
|
cache: cache,
|
||||||
orphaned_requests: RwLock::new(Vec::new()),
|
orphaned_requests: RwLock::new(Vec::new()),
|
||||||
|
start_nonce: account_start_nonce,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,7 +270,7 @@ impl OnDemand {
|
|||||||
|
|
||||||
/// Request an account by address and block header -- which gives a hash to query and a state root
|
/// Request an account by address and block header -- which gives a hash to query and a state root
|
||||||
/// to verify against.
|
/// to verify against.
|
||||||
pub fn account(&self, ctx: &BasicContext, req: request::Account) -> Receiver<Option<BasicAccount>> {
|
pub fn account(&self, ctx: &BasicContext, req: request::Account) -> Receiver<BasicAccount> {
|
||||||
let (sender, receiver) = oneshot::channel();
|
let (sender, receiver) = oneshot::channel();
|
||||||
self.dispatch(ctx, Pending::Account(req, sender));
|
self.dispatch(ctx, Pending::Account(req, sender));
|
||||||
receiver
|
receiver
|
||||||
@ -279,7 +281,7 @@ impl OnDemand {
|
|||||||
let (sender, receiver) = oneshot::channel();
|
let (sender, receiver) = oneshot::channel();
|
||||||
|
|
||||||
// fast path for no code.
|
// fast path for no code.
|
||||||
if req.code_hash == ::util::sha3::SHA3_EMPTY {
|
if req.code_hash == SHA3_EMPTY {
|
||||||
sender.send(Vec::new()).expect(RECEIVER_IN_SCOPE)
|
sender.send(Vec::new()).expect(RECEIVER_IN_SCOPE)
|
||||||
} else {
|
} else {
|
||||||
self.dispatch(ctx, Pending::Code(req, sender));
|
self.dispatch(ctx, Pending::Code(req, sender));
|
||||||
@ -497,10 +499,19 @@ impl Handler for OnDemand {
|
|||||||
Pending::Account(req, sender) => {
|
Pending::Account(req, sender) => {
|
||||||
if let NetworkResponse::Account(ref response) = *response {
|
if let NetworkResponse::Account(ref response) = *response {
|
||||||
match req.check_response(&response.proof) {
|
match req.check_response(&response.proof) {
|
||||||
Ok(maybe_account) => {
|
Ok(account) => {
|
||||||
|
let account = account.unwrap_or_else(|| {
|
||||||
|
BasicAccount {
|
||||||
|
balance: 0.into(),
|
||||||
|
nonce: self.start_nonce,
|
||||||
|
code_hash: SHA3_EMPTY,
|
||||||
|
storage_root: SHA3_NULL_RLP
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// TODO: validate against request outputs.
|
// TODO: validate against request outputs.
|
||||||
// needs engine + env info as part of request.
|
// needs engine + env info as part of request.
|
||||||
let _ = sender.send(maybe_account);
|
let _ = sender.send(account);
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Err(e) => warn!(target: "on_demand", "Error handling response for state request: {:?}", e),
|
Err(e) => warn!(target: "on_demand", "Error handling response for state request: {:?}", e),
|
||||||
@ -572,7 +583,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn detects_hangup() {
|
fn detects_hangup() {
|
||||||
let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::hours(6))));
|
let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::hours(6))));
|
||||||
let on_demand = OnDemand::new(cache);
|
let on_demand = OnDemand::new(cache, 0.into());
|
||||||
let result = on_demand.header_by_hash(&FakeContext, request::HeaderByHash(H256::default()));
|
let result = on_demand.header_by_hash(&FakeContext, request::HeaderByHash(H256::default()));
|
||||||
|
|
||||||
assert!(on_demand.orphaned_requests.read().len() == 1);
|
assert!(on_demand.orphaned_requests.read().len() == 1);
|
||||||
|
@ -61,6 +61,11 @@ pub fn consensus_view(header: &Header) -> Result<View, ::rlp::DecoderError> {
|
|||||||
UntrustedRlp::new(view_rlp.as_slice()).as_val()
|
UntrustedRlp::new(view_rlp.as_slice()).as_val()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Proposal signature.
|
||||||
|
pub fn proposal_signature(header: &Header) -> Result<H520, ::rlp::DecoderError> {
|
||||||
|
UntrustedRlp::new(header.seal().get(1).expect("seal passed basic verification; seal has 3 fields; qed").as_slice()).as_val()
|
||||||
|
}
|
||||||
|
|
||||||
impl Message for ConsensusMessage {
|
impl Message for ConsensusMessage {
|
||||||
type Round = VoteStep;
|
type Round = VoteStep;
|
||||||
|
|
||||||
@ -84,34 +89,18 @@ impl ConsensusMessage {
|
|||||||
|
|
||||||
pub fn new_proposal(header: &Header) -> Result<Self, ::rlp::DecoderError> {
|
pub fn new_proposal(header: &Header) -> Result<Self, ::rlp::DecoderError> {
|
||||||
Ok(ConsensusMessage {
|
Ok(ConsensusMessage {
|
||||||
|
signature: proposal_signature(header)?,
|
||||||
vote_step: VoteStep::new(header.number() as Height, consensus_view(header)?, Step::Propose),
|
vote_step: VoteStep::new(header.number() as Height, consensus_view(header)?, Step::Propose),
|
||||||
signature: UntrustedRlp::new(header.seal().get(1).expect("seal passed basic verification; seal has 3 fields; qed").as_slice()).as_val()?,
|
|
||||||
block_hash: Some(header.bare_hash()),
|
block_hash: Some(header.bare_hash()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_commit(proposal: &ConsensusMessage, signature: H520) -> Self {
|
|
||||||
let mut vote_step = proposal.vote_step.clone();
|
|
||||||
vote_step.step = Step::Precommit;
|
|
||||||
ConsensusMessage {
|
|
||||||
vote_step: vote_step,
|
|
||||||
block_hash: proposal.block_hash,
|
|
||||||
signature: signature,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verify(&self) -> Result<Address, Error> {
|
pub fn verify(&self) -> Result<Address, Error> {
|
||||||
let full_rlp = ::rlp::encode(self);
|
let full_rlp = ::rlp::encode(self);
|
||||||
let block_info = Rlp::new(&full_rlp).at(1);
|
let block_info = Rlp::new(&full_rlp).at(1);
|
||||||
let public_key = recover(&self.signature.into(), &block_info.as_raw().sha3())?;
|
let public_key = recover(&self.signature.into(), &block_info.as_raw().sha3())?;
|
||||||
Ok(public_to_address(&public_key))
|
Ok(public_to_address(&public_key))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn precommit_hash(&self) -> H256 {
|
|
||||||
let mut vote_step = self.vote_step.clone();
|
|
||||||
vote_step.step = Step::Precommit;
|
|
||||||
message_info_rlp(&vote_step, self.block_hash).sha3()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for VoteStep {
|
impl Default for VoteStep {
|
||||||
@ -203,6 +192,10 @@ pub fn message_full_rlp(signature: &H520, vote_info: &Bytes) -> Bytes {
|
|||||||
s.out()
|
s.out()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn message_hash(vote_step: VoteStep, block_hash: H256) -> H256 {
|
||||||
|
message_info_rlp(&vote_step, Some(block_hash)).sha3()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use util::*;
|
use util::*;
|
||||||
@ -294,19 +287,6 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn message_info_from_header() {
|
|
||||||
let header = Header::default();
|
|
||||||
let pro = ConsensusMessage {
|
|
||||||
signature: Default::default(),
|
|
||||||
vote_step: VoteStep::new(0, 0, Step::Propose),
|
|
||||||
block_hash: Some(header.bare_hash())
|
|
||||||
};
|
|
||||||
let pre = message_info_rlp(&VoteStep::new(0, 0, Step::Precommit), Some(header.bare_hash()));
|
|
||||||
|
|
||||||
assert_eq!(pro.precommit_hash(), pre.sha3());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn step_ordering() {
|
fn step_ordering() {
|
||||||
assert!(VoteStep::new(10, 123, Step::Precommit) < VoteStep::new(11, 123, Step::Precommit));
|
assert!(VoteStep::new(10, 123, Step::Precommit) < VoteStep::new(11, 123, Step::Precommit));
|
||||||
|
@ -97,6 +97,8 @@ pub struct Tendermint {
|
|||||||
proposal: RwLock<Option<H256>>,
|
proposal: RwLock<Option<H256>>,
|
||||||
/// Hash of the proposal parent block.
|
/// Hash of the proposal parent block.
|
||||||
proposal_parent: RwLock<H256>,
|
proposal_parent: RwLock<H256>,
|
||||||
|
/// Last block proposed by this validator.
|
||||||
|
last_proposed: RwLock<H256>,
|
||||||
/// Set used to determine the current validators.
|
/// Set used to determine the current validators.
|
||||||
validators: Box<ValidatorSet>,
|
validators: Box<ValidatorSet>,
|
||||||
}
|
}
|
||||||
@ -122,6 +124,7 @@ impl Tendermint {
|
|||||||
last_lock: AtomicUsize::new(0),
|
last_lock: AtomicUsize::new(0),
|
||||||
proposal: RwLock::new(None),
|
proposal: RwLock::new(None),
|
||||||
proposal_parent: Default::default(),
|
proposal_parent: Default::default(),
|
||||||
|
last_proposed: Default::default(),
|
||||||
validators: new_validator_set(our_params.validators),
|
validators: new_validator_set(our_params.validators),
|
||||||
});
|
});
|
||||||
let handler = TransitionHandler::new(Arc::downgrade(&engine) as Weak<Engine>, Box::new(our_params.timeouts));
|
let handler = TransitionHandler::new(Arc::downgrade(&engine) as Weak<Engine>, Box::new(our_params.timeouts));
|
||||||
@ -196,6 +199,7 @@ impl Tendermint {
|
|||||||
self.height.store(new_height, AtomicOrdering::SeqCst);
|
self.height.store(new_height, AtomicOrdering::SeqCst);
|
||||||
self.view.store(0, AtomicOrdering::SeqCst);
|
self.view.store(0, AtomicOrdering::SeqCst);
|
||||||
*self.lock_change.write() = None;
|
*self.lock_change.write() = None;
|
||||||
|
*self.proposal.write() = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Use via step_service to transition steps.
|
/// Use via step_service to transition steps.
|
||||||
@ -206,7 +210,6 @@ impl Tendermint {
|
|||||||
*self.step.write() = step;
|
*self.step.write() = step;
|
||||||
match step {
|
match step {
|
||||||
Step::Propose => {
|
Step::Propose => {
|
||||||
*self.proposal.write() = None;
|
|
||||||
self.update_sealing()
|
self.update_sealing()
|
||||||
},
|
},
|
||||||
Step::Prevote => {
|
Step::Prevote => {
|
||||||
@ -230,28 +233,6 @@ impl Tendermint {
|
|||||||
},
|
},
|
||||||
Step::Commit => {
|
Step::Commit => {
|
||||||
trace!(target: "engine", "to_step: Commit.");
|
trace!(target: "engine", "to_step: Commit.");
|
||||||
// Commit the block using a complete signature set.
|
|
||||||
let view = self.view.load(AtomicOrdering::SeqCst);
|
|
||||||
let height = self.height.load(AtomicOrdering::SeqCst);
|
|
||||||
if let Some(block_hash) = *self.proposal.read() {
|
|
||||||
// Generate seal and remove old votes.
|
|
||||||
if self.is_signer_proposer(&*self.proposal_parent.read()) {
|
|
||||||
let proposal_step = VoteStep::new(height, view, Step::Propose);
|
|
||||||
let precommit_step = VoteStep::new(proposal_step.height, proposal_step.view, Step::Precommit);
|
|
||||||
if let Some(seal) = self.votes.seal_signatures(proposal_step, precommit_step, &block_hash) {
|
|
||||||
trace!(target: "engine", "Collected seal: {:?}", seal);
|
|
||||||
let seal = vec![
|
|
||||||
::rlp::encode(&view).to_vec(),
|
|
||||||
::rlp::encode(&seal.proposal).to_vec(),
|
|
||||||
::rlp::encode_list(&seal.votes).to_vec()
|
|
||||||
];
|
|
||||||
self.submit_seal(block_hash, seal);
|
|
||||||
self.to_next_height(height);
|
|
||||||
} else {
|
|
||||||
warn!(target: "engine", "Not enough votes found!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -260,8 +241,17 @@ impl Tendermint {
|
|||||||
self.validators.contains(&*self.proposal_parent.read(), address)
|
self.validators.contains(&*self.proposal_parent.read(), address)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_above_threshold(&self, n: usize) -> bool {
|
fn check_above_threshold(&self, n: usize) -> Result<(), EngineError> {
|
||||||
n > self.validators.count(&*self.proposal_parent.read()) * 2/3
|
let threshold = self.validators.count(&*self.proposal_parent.read()) * 2/3;
|
||||||
|
if n > threshold {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(EngineError::BadSealFieldSize(OutOfBounds {
|
||||||
|
min: Some(threshold),
|
||||||
|
max: None,
|
||||||
|
found: n
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the designated for the given view.
|
/// Find the designated for the given view.
|
||||||
@ -272,7 +262,7 @@ impl Tendermint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Check if address is a proposer for given view.
|
/// Check if address is a proposer for given view.
|
||||||
fn is_view_proposer(&self, bh: &H256, height: Height, view: View, address: &Address) -> Result<(), EngineError> {
|
fn check_view_proposer(&self, bh: &H256, height: Height, view: View, address: &Address) -> Result<(), EngineError> {
|
||||||
let proposer = self.view_proposer(bh, height, view);
|
let proposer = self.view_proposer(bh, height, view);
|
||||||
if proposer == *address {
|
if proposer == *address {
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -308,13 +298,13 @@ impl Tendermint {
|
|||||||
|
|
||||||
fn has_enough_any_votes(&self) -> bool {
|
fn has_enough_any_votes(&self) -> bool {
|
||||||
let step_votes = self.votes.count_round_votes(&VoteStep::new(self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst), *self.step.read()));
|
let step_votes = self.votes.count_round_votes(&VoteStep::new(self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst), *self.step.read()));
|
||||||
self.is_above_threshold(step_votes)
|
self.check_above_threshold(step_votes).is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_enough_future_step_votes(&self, vote_step: &VoteStep) -> bool {
|
fn has_enough_future_step_votes(&self, vote_step: &VoteStep) -> bool {
|
||||||
if vote_step.view > self.view.load(AtomicOrdering::SeqCst) {
|
if vote_step.view > self.view.load(AtomicOrdering::SeqCst) {
|
||||||
let step_votes = self.votes.count_round_votes(vote_step);
|
let step_votes = self.votes.count_round_votes(vote_step);
|
||||||
self.is_above_threshold(step_votes)
|
self.check_above_threshold(step_votes).is_ok()
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@ -322,7 +312,7 @@ impl Tendermint {
|
|||||||
|
|
||||||
fn has_enough_aligned_votes(&self, message: &ConsensusMessage) -> bool {
|
fn has_enough_aligned_votes(&self, message: &ConsensusMessage) -> bool {
|
||||||
let aligned_count = self.votes.count_aligned_votes(&message);
|
let aligned_count = self.votes.count_aligned_votes(&message);
|
||||||
self.is_above_threshold(aligned_count)
|
self.check_above_threshold(aligned_count).is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_valid_message(&self, message: &ConsensusMessage) {
|
fn handle_valid_message(&self, message: &ConsensusMessage) {
|
||||||
@ -337,18 +327,32 @@ impl Tendermint {
|
|||||||
&& self.has_enough_aligned_votes(message);
|
&& self.has_enough_aligned_votes(message);
|
||||||
if lock_change {
|
if lock_change {
|
||||||
trace!(target: "engine", "handle_valid_message: Lock change.");
|
trace!(target: "engine", "handle_valid_message: Lock change.");
|
||||||
*self.lock_change.write() = Some(message.clone());
|
*self.lock_change.write() = Some(message.clone());
|
||||||
}
|
}
|
||||||
// Check if it can affect the step transition.
|
// Check if it can affect the step transition.
|
||||||
if self.is_height(message) {
|
if self.is_height(message) {
|
||||||
let next_step = match *self.step.read() {
|
let next_step = match *self.step.read() {
|
||||||
|
Step::Precommit if message.block_hash.is_none() && self.has_enough_aligned_votes(message) => {
|
||||||
|
self.increment_view(1);
|
||||||
|
Some(Step::Propose)
|
||||||
|
},
|
||||||
Step::Precommit if self.has_enough_aligned_votes(message) => {
|
Step::Precommit if self.has_enough_aligned_votes(message) => {
|
||||||
if message.block_hash.is_none() {
|
let bh = message.block_hash.expect("previous guard ensures is_some; qed");
|
||||||
self.increment_view(1);
|
if *self.last_proposed.read() == bh {
|
||||||
Some(Step::Propose)
|
// Commit the block using a complete signature set.
|
||||||
} else {
|
// Generate seal and remove old votes.
|
||||||
Some(Step::Commit)
|
let precommits = self.votes.round_signatures(vote_step, &bh);
|
||||||
|
trace!(target: "engine", "Collected seal: {:?}", precommits);
|
||||||
|
let seal = vec![
|
||||||
|
::rlp::encode(&vote_step.view).to_vec(),
|
||||||
|
::rlp::NULL_RLP.to_vec(),
|
||||||
|
::rlp::encode_list(&precommits).to_vec()
|
||||||
|
];
|
||||||
|
self.submit_seal(bh, seal);
|
||||||
|
self.votes.throw_out_old(&vote_step);
|
||||||
}
|
}
|
||||||
|
self.to_next_height(self.height.load(AtomicOrdering::SeqCst));
|
||||||
|
Some(Step::Commit)
|
||||||
},
|
},
|
||||||
Step::Precommit if self.has_enough_future_step_votes(&vote_step) => {
|
Step::Precommit if self.has_enough_future_step_votes(&vote_step) => {
|
||||||
self.increment_view(vote_step.view - self.view.load(AtomicOrdering::SeqCst));
|
self.increment_view(vote_step.view - self.view.load(AtomicOrdering::SeqCst));
|
||||||
@ -442,6 +446,8 @@ impl Engine for Tendermint {
|
|||||||
// Insert Propose vote.
|
// Insert Propose vote.
|
||||||
debug!(target: "engine", "Submitting proposal {} at height {} view {}.", header.bare_hash(), height, view);
|
debug!(target: "engine", "Submitting proposal {} at height {} view {}.", header.bare_hash(), height, view);
|
||||||
self.votes.vote(ConsensusMessage::new(signature, height, view, Step::Propose, bh), author);
|
self.votes.vote(ConsensusMessage::new(signature, height, view, Step::Propose, bh), author);
|
||||||
|
// Remember the owned block.
|
||||||
|
*self.last_proposed.write() = header.bare_hash();
|
||||||
// Remember proposal for later seal submission.
|
// Remember proposal for later seal submission.
|
||||||
*self.proposal.write() = bh;
|
*self.proposal.write() = bh;
|
||||||
*self.proposal_parent.write() = header.parent_hash().clone();
|
*self.proposal_parent.write() = header.parent_hash().clone();
|
||||||
@ -462,12 +468,12 @@ impl Engine for Tendermint {
|
|||||||
if !self.votes.is_old_or_known(&message) {
|
if !self.votes.is_old_or_known(&message) {
|
||||||
let sender = public_to_address(&recover(&message.signature.into(), &rlp.at(1)?.as_raw().sha3())?);
|
let sender = public_to_address(&recover(&message.signature.into(), &rlp.at(1)?.as_raw().sha3())?);
|
||||||
if !self.is_authority(&sender) {
|
if !self.is_authority(&sender) {
|
||||||
Err(EngineError::NotAuthorized(sender))?;
|
return Err(EngineError::NotAuthorized(sender).into());
|
||||||
}
|
}
|
||||||
self.broadcast_message(rlp.as_raw().to_vec());
|
self.broadcast_message(rlp.as_raw().to_vec());
|
||||||
if self.votes.vote(message.clone(), &sender).is_some() {
|
if self.votes.vote(message.clone(), &sender).is_some() {
|
||||||
self.validators.report_malicious(&sender);
|
self.validators.report_malicious(&sender);
|
||||||
Err(EngineError::DoubleVote(sender))?
|
return Err(EngineError::DoubleVote(sender).into());
|
||||||
}
|
}
|
||||||
trace!(target: "engine", "Handling a valid {:?} from {}.", message, sender);
|
trace!(target: "engine", "Handling a valid {:?} from {}.", message, sender);
|
||||||
self.handle_valid_message(&message);
|
self.handle_valid_message(&message);
|
||||||
@ -491,22 +497,19 @@ impl Engine for Tendermint {
|
|||||||
fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
||||||
let seal_length = header.seal().len();
|
let seal_length = header.seal().len();
|
||||||
if seal_length == self.seal_fields() {
|
if seal_length == self.seal_fields() {
|
||||||
let signatures_len = header.seal()[2].len();
|
// Either proposal or commit.
|
||||||
if signatures_len >= 1 {
|
if (header.seal()[1] == ::rlp::NULL_RLP.to_vec())
|
||||||
|
!= (header.seal()[2] == ::rlp::EMPTY_LIST_RLP.to_vec()) {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(From::from(EngineError::BadSealFieldSize(OutOfBounds {
|
warn!(target: "engine", "verify_block_basic: Block is neither a Commit nor Proposal.");
|
||||||
min: Some(1),
|
Err(BlockError::InvalidSeal.into())
|
||||||
max: None,
|
|
||||||
found: signatures_len
|
|
||||||
})))
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(From::from(BlockError::InvalidSealArity(
|
Err(BlockError::InvalidSealArity(
|
||||||
Mismatch { expected: self.seal_fields(), found: seal_length }
|
Mismatch { expected: self.seal_fields(), found: seal_length }
|
||||||
)))
|
).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
||||||
@ -515,50 +518,42 @@ impl Engine for Tendermint {
|
|||||||
|
|
||||||
/// Verify validators and gas limit.
|
/// Verify validators and gas limit.
|
||||||
fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
||||||
let proposal = ConsensusMessage::new_proposal(header)?;
|
|
||||||
let proposer = proposal.verify()?;
|
|
||||||
if !self.is_authority(&proposer) {
|
|
||||||
Err(EngineError::NotAuthorized(proposer))?
|
|
||||||
}
|
|
||||||
|
|
||||||
let precommit_hash = proposal.precommit_hash();
|
|
||||||
let ref signatures_field = header.seal()[2];
|
|
||||||
let mut signature_count = 0;
|
|
||||||
let mut origins = HashSet::new();
|
|
||||||
for rlp in UntrustedRlp::new(signatures_field).iter() {
|
|
||||||
let precommit: ConsensusMessage = ConsensusMessage::new_commit(&proposal, rlp.as_val()?);
|
|
||||||
let address = match self.votes.get(&precommit) {
|
|
||||||
Some(a) => a,
|
|
||||||
None => public_to_address(&recover(&precommit.signature.into(), &precommit_hash)?),
|
|
||||||
};
|
|
||||||
if !self.validators.contains(header.parent_hash(), &address) {
|
|
||||||
Err(EngineError::NotAuthorized(address.to_owned()))?
|
|
||||||
}
|
|
||||||
|
|
||||||
if origins.insert(address) {
|
|
||||||
signature_count += 1;
|
|
||||||
} else {
|
|
||||||
warn!(target: "engine", "verify_block_unordered: Duplicate signature from {} on the seal.", address);
|
|
||||||
Err(BlockError::InvalidSeal)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if its a proposal if there is not enough precommits.
|
|
||||||
if !self.is_above_threshold(signature_count) {
|
|
||||||
let signatures_len = signatures_field.len();
|
|
||||||
// Proposal has to have an empty signature list.
|
|
||||||
if signatures_len != 1 {
|
|
||||||
Err(EngineError::BadSealFieldSize(OutOfBounds {
|
|
||||||
min: Some(1),
|
|
||||||
max: Some(1),
|
|
||||||
found: signatures_len
|
|
||||||
}))?;
|
|
||||||
}
|
|
||||||
self.is_view_proposer(header.parent_hash(), proposal.vote_step.height, proposal.vote_step.view, &proposer)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if header.number() == 0 {
|
if header.number() == 0 {
|
||||||
Err(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))?;
|
return Err(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(proposal) = ConsensusMessage::new_proposal(header) {
|
||||||
|
let proposer = proposal.verify()?;
|
||||||
|
if !self.is_authority(&proposer) {
|
||||||
|
return Err(EngineError::NotAuthorized(proposer).into());
|
||||||
|
}
|
||||||
|
self.check_view_proposer(header.parent_hash(), proposal.vote_step.height, proposal.vote_step.view, &proposer)?;
|
||||||
|
} else {
|
||||||
|
let vote_step = VoteStep::new(header.number() as usize, consensus_view(header)?, Step::Precommit);
|
||||||
|
let precommit_hash = message_hash(vote_step.clone(), header.bare_hash());
|
||||||
|
let ref signatures_field = header.seal().get(2).expect("block went through verify_block_basic; block has .seal_fields() fields; qed");
|
||||||
|
let mut origins = HashSet::new();
|
||||||
|
for rlp in UntrustedRlp::new(signatures_field).iter() {
|
||||||
|
let precommit = ConsensusMessage {
|
||||||
|
signature: rlp.as_val()?,
|
||||||
|
block_hash: Some(header.bare_hash()),
|
||||||
|
vote_step: vote_step.clone(),
|
||||||
|
};
|
||||||
|
let address = match self.votes.get(&precommit) {
|
||||||
|
Some(a) => a,
|
||||||
|
None => public_to_address(&recover(&precommit.signature.into(), &precommit_hash)?),
|
||||||
|
};
|
||||||
|
if !self.validators.contains(header.parent_hash(), &address) {
|
||||||
|
return Err(EngineError::NotAuthorized(address.to_owned()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !origins.insert(address) {
|
||||||
|
warn!(target: "engine", "verify_block_unordered: Duplicate signature from {} on the seal.", address);
|
||||||
|
return Err(BlockError::InvalidSeal.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.check_above_threshold(origins.len())?
|
||||||
}
|
}
|
||||||
|
|
||||||
let gas_limit_divisor = self.gas_limit_bound_divisor;
|
let gas_limit_divisor = self.gas_limit_bound_divisor;
|
||||||
@ -566,7 +561,7 @@ impl Engine for Tendermint {
|
|||||||
let max_gas = parent.gas_limit().clone() + parent.gas_limit().clone() / gas_limit_divisor;
|
let max_gas = parent.gas_limit().clone() + parent.gas_limit().clone() / gas_limit_divisor;
|
||||||
if header.gas_limit() <= &min_gas || header.gas_limit() >= &max_gas {
|
if header.gas_limit() <= &min_gas || header.gas_limit() >= &max_gas {
|
||||||
self.validators.report_malicious(header.author());
|
self.validators.report_malicious(header.author());
|
||||||
Err(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas), max: Some(max_gas), found: header.gas_limit().clone() }))?;
|
return Err(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas), max: Some(max_gas), found: header.gas_limit().clone() }).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -590,13 +585,14 @@ impl Engine for Tendermint {
|
|||||||
fn is_proposal(&self, header: &Header) -> bool {
|
fn is_proposal(&self, header: &Header) -> bool {
|
||||||
let signatures_len = header.seal()[2].len();
|
let signatures_len = header.seal()[2].len();
|
||||||
// Signatures have to be an empty list rlp.
|
// Signatures have to be an empty list rlp.
|
||||||
let proposal = ConsensusMessage::new_proposal(header).expect("block went through full verification; this Engine verifies new_proposal creation; qed");
|
|
||||||
if signatures_len != 1 {
|
if signatures_len != 1 {
|
||||||
// New Commit received, skip to next height.
|
// New Commit received, skip to next height.
|
||||||
trace!(target: "engine", "Received a commit: {:?}.", proposal.vote_step);
|
trace!(target: "engine", "Received a commit: {:?}.", header.number());
|
||||||
self.to_next_height(proposal.vote_step.height);
|
self.to_next_height(header.number() as usize);
|
||||||
|
self.to_step(Step::Commit);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
let proposal = ConsensusMessage::new_proposal(header).expect("block went through full verification; this Engine verifies new_proposal creation; qed");
|
||||||
let proposer = proposal.verify().expect("block went through full verification; this Engine tries verify; qed");
|
let proposer = proposal.verify().expect("block went through full verification; this Engine tries verify; qed");
|
||||||
debug!(target: "engine", "Received a new proposal {:?} from {}.", proposal.vote_step, proposer);
|
debug!(target: "engine", "Received a new proposal {:?} from {}.", proposal.vote_step, proposer);
|
||||||
if self.is_view(&proposal) {
|
if self.is_view(&proposal) {
|
||||||
@ -647,6 +643,10 @@ impl Engine for Tendermint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn register_client(&self, client: Weak<Client>) {
|
fn register_client(&self, client: Weak<Client>) {
|
||||||
|
use client::BlockChainClient;
|
||||||
|
if let Some(c) = client.upgrade() {
|
||||||
|
self.height.store(c.chain_info().best_block_number as usize + 1, AtomicOrdering::SeqCst);
|
||||||
|
}
|
||||||
*self.client.write() = Some(client.clone());
|
*self.client.write() = Some(client.clone());
|
||||||
self.validators.register_contract(client);
|
self.validators.register_contract(client);
|
||||||
}
|
}
|
||||||
@ -825,6 +825,7 @@ mod tests {
|
|||||||
let vote_info = message_info_rlp(&VoteStep::new(2, 0, Step::Precommit), Some(header.bare_hash()));
|
let vote_info = message_info_rlp(&VoteStep::new(2, 0, Step::Precommit), Some(header.bare_hash()));
|
||||||
let signature1 = tap.sign(proposer, None, vote_info.sha3()).unwrap();
|
let signature1 = tap.sign(proposer, None, vote_info.sha3()).unwrap();
|
||||||
|
|
||||||
|
seal[1] = ::rlp::NULL_RLP.to_vec();
|
||||||
seal[2] = ::rlp::encode_list(&vec![H520::from(signature1.clone())]).to_vec();
|
seal[2] = ::rlp::encode_list(&vec![H520::from(signature1.clone())]).to_vec();
|
||||||
header.set_seal(seal.clone());
|
header.set_seal(seal.clone());
|
||||||
|
|
||||||
|
@ -136,30 +136,14 @@ impl <M: Message + Default + Encodable + Debug> VoteCollector<M> {
|
|||||||
*guard = new_collector;
|
*guard = new_collector;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collects the signatures used to seal a block.
|
/// Collects the signatures for a given round and hash.
|
||||||
pub fn seal_signatures(&self, proposal_round: M::Round, commit_round: M::Round, block_hash: &H256) -> Option<SealSignatures> {
|
pub fn round_signatures(&self, round: &M::Round, block_hash: &H256) -> Vec<H520> {
|
||||||
let ref bh = Some(*block_hash);
|
let guard = self.votes.read();
|
||||||
let maybe_seal = {
|
guard
|
||||||
let guard = self.votes.read();
|
.get(round)
|
||||||
guard
|
.and_then(|c| c.block_votes.get(&Some(*block_hash)))
|
||||||
.get(&proposal_round)
|
.map(|votes| votes.keys().cloned().collect())
|
||||||
.and_then(|c| c.block_votes.get(bh))
|
.unwrap_or_else(Vec::new)
|
||||||
.and_then(|proposals| proposals.keys().next())
|
|
||||||
.map(|proposal| SealSignatures {
|
|
||||||
proposal: proposal.clone(),
|
|
||||||
votes: guard
|
|
||||||
.get(&commit_round)
|
|
||||||
.and_then(|c| c.block_votes.get(bh))
|
|
||||||
.map(|precommits| precommits.keys().cloned().collect())
|
|
||||||
.unwrap_or_else(Vec::new),
|
|
||||||
})
|
|
||||||
.and_then(|seal| if seal.votes.is_empty() { None } else { Some(seal) })
|
|
||||||
};
|
|
||||||
if maybe_seal.is_some() {
|
|
||||||
// Remove messages that are no longer relevant.
|
|
||||||
self.throw_out_old(&commit_round);
|
|
||||||
}
|
|
||||||
maybe_seal
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Count votes which agree with the given message.
|
/// Count votes which agree with the given message.
|
||||||
@ -275,11 +259,9 @@ mod tests {
|
|||||||
random_vote(&collector, signatures[1].clone(), commit_round.clone(), bh.clone());
|
random_vote(&collector, signatures[1].clone(), commit_round.clone(), bh.clone());
|
||||||
// Wrong round, same signature.
|
// Wrong round, same signature.
|
||||||
random_vote(&collector, signatures[1].clone(), 7, bh.clone());
|
random_vote(&collector, signatures[1].clone(), 7, bh.clone());
|
||||||
let seal = SealSignatures {
|
|
||||||
proposal: signatures[0],
|
assert_eq!(signatures[0..1].to_vec(), collector.round_signatures(&propose_round, &bh.unwrap()));
|
||||||
votes: signatures[1..3].to_vec()
|
assert_eq!(signatures[1..3].iter().collect::<HashSet<_>>(), collector.round_signatures(&commit_round, &bh.unwrap()).iter().collect::<HashSet<_>>());
|
||||||
};
|
|
||||||
assert_eq!(seal, collector.seal_signatures(propose_round, commit_round, &bh.unwrap()).unwrap());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -199,6 +199,12 @@ impl Block {
|
|||||||
/// Decode to a full block.
|
/// Decode to a full block.
|
||||||
pub fn decode(&self) -> FullBlock { ::rlp::decode(&self.0) }
|
pub fn decode(&self) -> FullBlock { ::rlp::decode(&self.0) }
|
||||||
|
|
||||||
|
/// Decode the header.
|
||||||
|
pub fn decode_header(&self) -> FullHeader { self.rlp().val_at(0) }
|
||||||
|
|
||||||
|
/// Clone the encoded header.
|
||||||
|
pub fn header(&self) -> Header { Header(self.rlp().at(0).as_raw().to_vec()) }
|
||||||
|
|
||||||
/// Get the rlp of this block.
|
/// Get the rlp of this block.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn rlp(&self) -> Rlp {
|
pub fn rlp(&self) -> Rlp {
|
||||||
|
@ -94,11 +94,11 @@ pub trait Keccak256<T> {
|
|||||||
fn keccak256(&self) -> T where T: Sized;
|
fn keccak256(&self) -> T where T: Sized;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Keccak256<[u8; 32]> for [u8] {
|
impl<T> Keccak256<[u8; 32]> for T where T: AsRef<[u8]> {
|
||||||
fn keccak256(&self) -> [u8; 32] {
|
fn keccak256(&self) -> [u8; 32] {
|
||||||
let mut keccak = Keccak::new_keccak256();
|
let mut keccak = Keccak::new_keccak256();
|
||||||
let mut result = [0u8; 32];
|
let mut result = [0u8; 32];
|
||||||
keccak.update(self);
|
keccak.update(self.as_ref());
|
||||||
keccak.finalize(&mut result);
|
keccak.finalize(&mut result);
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
@ -19,10 +19,10 @@ time = "0.1.34"
|
|||||||
itertools = "0.5"
|
itertools = "0.5"
|
||||||
parking_lot = "0.4"
|
parking_lot = "0.4"
|
||||||
ethcrypto = { path = "../ethcrypto" }
|
ethcrypto = { path = "../ethcrypto" }
|
||||||
ethcore-util = { path = "../util" }
|
ethcore-bigint = { path = "../util/bigint" }
|
||||||
smallvec = "0.3.1"
|
smallvec = "0.3.1"
|
||||||
ethcore-devtools = { path = "../devtools" }
|
|
||||||
parity-wordlist = "1.0"
|
parity-wordlist = "1.0"
|
||||||
|
tempdir = "0.3"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
cli = ["docopt"]
|
cli = ["docopt"]
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::iter::repeat;
|
use std::iter::repeat;
|
||||||
|
use std::str;
|
||||||
use ethkey::Secret;
|
use ethkey::Secret;
|
||||||
use {json, Error, crypto};
|
use {json, Error, crypto};
|
||||||
use crypto::Keccak256;
|
use crypto::Keccak256;
|
||||||
@ -46,17 +47,31 @@ impl From<json::Crypto> for Crypto {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<json::Crypto> for Crypto {
|
impl From<Crypto> for json::Crypto {
|
||||||
fn into(self) -> json::Crypto {
|
fn from(c: Crypto) -> Self {
|
||||||
json::Crypto {
|
json::Crypto {
|
||||||
cipher: self.cipher.into(),
|
cipher: c.cipher.into(),
|
||||||
ciphertext: self.ciphertext.into(),
|
ciphertext: c.ciphertext.into(),
|
||||||
kdf: self.kdf.into(),
|
kdf: c.kdf.into(),
|
||||||
mac: self.mac.into(),
|
mac: c.mac.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl str::FromStr for Crypto {
|
||||||
|
type Err = <json::Crypto as str::FromStr>::Err;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
s.parse::<json::Crypto>().map(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Crypto> for String {
|
||||||
|
fn from(c: Crypto) -> Self {
|
||||||
|
json::Crypto::from(c).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Crypto {
|
impl Crypto {
|
||||||
pub fn with_secret(secret: &Secret, password: &str, iterations: u32) -> Self {
|
pub fn with_secret(secret: &Secret, password: &str, iterations: u32) -> Self {
|
||||||
Crypto::with_plain(&*secret, password, iterations)
|
Crypto::with_plain(&*secret, password, iterations)
|
||||||
|
@ -225,8 +225,8 @@ impl<T> KeyDirectory for DiskDirectory<T> where T: KeyFileManager {
|
|||||||
Some(self)
|
Some(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unique_repr(&self) -> Result<u64, Error> {
|
fn unique_repr(&self) -> Result<u64, Error> {
|
||||||
self.files_hash()
|
self.files_hash()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,12 +280,14 @@ impl KeyFileManager for DiskKeyFileManager {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
extern crate tempdir;
|
||||||
|
|
||||||
use std::{env, fs};
|
use std::{env, fs};
|
||||||
use super::RootDiskDirectory;
|
use super::RootDiskDirectory;
|
||||||
use dir::{KeyDirectory, VaultKey};
|
use dir::{KeyDirectory, VaultKey};
|
||||||
use account::SafeAccount;
|
use account::SafeAccount;
|
||||||
use ethkey::{Random, Generator};
|
use ethkey::{Random, Generator};
|
||||||
use devtools::RandomTempPath;
|
use self::tempdir::TempDir;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_create_new_account() {
|
fn should_create_new_account() {
|
||||||
@ -344,7 +346,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn should_list_vaults() {
|
fn should_list_vaults() {
|
||||||
// given
|
// given
|
||||||
let temp_path = RandomTempPath::new();
|
let temp_path = TempDir::new("").unwrap();
|
||||||
let directory = RootDiskDirectory::create(&temp_path).unwrap();
|
let directory = RootDiskDirectory::create(&temp_path).unwrap();
|
||||||
let vault_provider = directory.as_vault_provider().unwrap();
|
let vault_provider = directory.as_vault_provider().unwrap();
|
||||||
vault_provider.create("vault1", VaultKey::new("password1", 1)).unwrap();
|
vault_provider.create("vault1", VaultKey::new("password1", 1)).unwrap();
|
||||||
@ -359,11 +361,11 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hash_of_files() {
|
fn hash_of_files() {
|
||||||
let temp_path = RandomTempPath::new();
|
let temp_path = TempDir::new("").unwrap();
|
||||||
let directory = RootDiskDirectory::create(&temp_path).unwrap();
|
let directory = RootDiskDirectory::create(&temp_path).unwrap();
|
||||||
|
|
||||||
let hash = directory.files_hash().expect("Files hash should be calculated ok");
|
let hash = directory.files_hash().expect("Files hash should be calculated ok");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
hash,
|
hash,
|
||||||
15130871412783076140
|
15130871412783076140
|
||||||
);
|
);
|
||||||
@ -373,7 +375,7 @@ mod test {
|
|||||||
let account = SafeAccount::create(&keypair, [0u8; 16], password, 1024, "Test".to_owned(), "{}".to_owned());
|
let account = SafeAccount::create(&keypair, [0u8; 16], password, 1024, "Test".to_owned(), "{}".to_owned());
|
||||||
directory.insert(account).expect("Account should be inserted ok");
|
directory.insert(account).expect("Account should be inserted ok");
|
||||||
|
|
||||||
let new_hash = directory.files_hash().expect("New files hash should be calculated ok");
|
let new_hash = directory.files_hash().expect("New files hash should be calculated ok");
|
||||||
|
|
||||||
assert!(new_hash != hash, "hash of the file list should change once directory content changed");
|
assert!(new_hash != hash, "hash of the file list should change once directory content changed");
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ use std::{fs, io};
|
|||||||
use std::path::{PathBuf, Path};
|
use std::path::{PathBuf, Path};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use {json, SafeAccount, Error};
|
use {json, SafeAccount, Error};
|
||||||
use util::sha3::Hashable;
|
use crypto::Keccak256;
|
||||||
use super::super::account::Crypto;
|
use super::super::account::Crypto;
|
||||||
use super::{KeyDirectory, VaultKeyDirectory, VaultKey, SetKeyError};
|
use super::{KeyDirectory, VaultKeyDirectory, VaultKey, SetKeyError};
|
||||||
use super::disk::{DiskDirectory, KeyFileManager};
|
use super::disk::{DiskDirectory, KeyFileManager};
|
||||||
@ -234,7 +234,7 @@ fn check_vault_name(name: &str) -> bool {
|
|||||||
|
|
||||||
/// Vault can be empty, but still must be pluggable => we store vault password in separate file
|
/// Vault can be empty, but still must be pluggable => we store vault password in separate file
|
||||||
fn create_vault_file<P>(vault_dir_path: P, key: &VaultKey, meta: &str) -> Result<(), Error> where P: AsRef<Path> {
|
fn create_vault_file<P>(vault_dir_path: P, key: &VaultKey, meta: &str) -> Result<(), Error> where P: AsRef<Path> {
|
||||||
let password_hash = key.password.sha3();
|
let password_hash = key.password.keccak256();
|
||||||
let crypto = Crypto::with_plain(&password_hash, &key.password, key.iterations);
|
let crypto = Crypto::with_plain(&password_hash, &key.password, key.iterations);
|
||||||
|
|
||||||
let mut vault_file_path: PathBuf = vault_dir_path.as_ref().into();
|
let mut vault_file_path: PathBuf = vault_dir_path.as_ref().into();
|
||||||
@ -268,8 +268,8 @@ fn read_vault_file<P>(vault_dir_path: P, key: Option<&VaultKey>) -> Result<Strin
|
|||||||
|
|
||||||
if let Some(key) = key {
|
if let Some(key) = key {
|
||||||
let password_bytes = vault_file_crypto.decrypt(&key.password)?;
|
let password_bytes = vault_file_crypto.decrypt(&key.password)?;
|
||||||
let password_hash = key.password.sha3();
|
let password_hash = key.password.keccak256();
|
||||||
if &*password_hash != password_bytes.as_slice() {
|
if password_hash != password_bytes.as_slice() {
|
||||||
return Err(Error::InvalidPassword);
|
return Err(Error::InvalidPassword);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -279,12 +279,14 @@ fn read_vault_file<P>(vault_dir_path: P, key: Option<&VaultKey>) -> Result<Strin
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
extern crate tempdir;
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use dir::VaultKey;
|
use dir::VaultKey;
|
||||||
use super::{VAULT_FILE_NAME, check_vault_name, make_vault_dir_path, create_vault_file, read_vault_file, VaultDiskDirectory};
|
use super::{VAULT_FILE_NAME, check_vault_name, make_vault_dir_path, create_vault_file, read_vault_file, VaultDiskDirectory};
|
||||||
use devtools::RandomTempPath;
|
use self::tempdir::TempDir;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_vault_name_succeeds() {
|
fn check_vault_name_succeeds() {
|
||||||
@ -320,9 +322,9 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn create_vault_file_succeeds() {
|
fn create_vault_file_succeeds() {
|
||||||
// given
|
// given
|
||||||
let temp_path = RandomTempPath::new();
|
let temp_path = TempDir::new("").unwrap();
|
||||||
let key = VaultKey::new("password", 1024);
|
let key = VaultKey::new("password", 1024);
|
||||||
let mut vault_dir: PathBuf = temp_path.as_path().into();
|
let mut vault_dir: PathBuf = temp_path.path().into();
|
||||||
vault_dir.push("vault");
|
vault_dir.push("vault");
|
||||||
fs::create_dir_all(&vault_dir).unwrap();
|
fs::create_dir_all(&vault_dir).unwrap();
|
||||||
|
|
||||||
@ -339,10 +341,10 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn read_vault_file_succeeds() {
|
fn read_vault_file_succeeds() {
|
||||||
// given
|
// given
|
||||||
let temp_path = RandomTempPath::create_dir();
|
let temp_path = TempDir::new("").unwrap();
|
||||||
let key = VaultKey::new("password", 1024);
|
let key = VaultKey::new("password", 1024);
|
||||||
let vault_file_contents = r#"{"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"758696c8dc6378ab9b25bb42790da2f5"},"ciphertext":"54eb50683717d41caaeb12ea969f2c159daada5907383f26f327606a37dc7168","kdf":"pbkdf2","kdfparams":{"c":1024,"dklen":32,"prf":"hmac-sha256","salt":"3c320fa566a1a7963ac8df68a19548d27c8f40bf92ef87c84594dcd5bbc402b6"},"mac":"9e5c2314c2a0781962db85611417c614bd6756666b6b1e93840f5b6ed895f003"}}"#;
|
let vault_file_contents = r#"{"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"758696c8dc6378ab9b25bb42790da2f5"},"ciphertext":"54eb50683717d41caaeb12ea969f2c159daada5907383f26f327606a37dc7168","kdf":"pbkdf2","kdfparams":{"c":1024,"dklen":32,"prf":"hmac-sha256","salt":"3c320fa566a1a7963ac8df68a19548d27c8f40bf92ef87c84594dcd5bbc402b6"},"mac":"9e5c2314c2a0781962db85611417c614bd6756666b6b1e93840f5b6ed895f003"}}"#;
|
||||||
let dir: PathBuf = temp_path.as_path().into();
|
let dir: PathBuf = temp_path.path().into();
|
||||||
let mut vault_file_path: PathBuf = dir.clone();
|
let mut vault_file_path: PathBuf = dir.clone();
|
||||||
vault_file_path.push(VAULT_FILE_NAME);
|
vault_file_path.push(VAULT_FILE_NAME);
|
||||||
{
|
{
|
||||||
@ -360,9 +362,9 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn read_vault_file_fails() {
|
fn read_vault_file_fails() {
|
||||||
// given
|
// given
|
||||||
let temp_path = RandomTempPath::create_dir();
|
let temp_path = TempDir::new("").unwrap();
|
||||||
let key = VaultKey::new("password1", 1024);
|
let key = VaultKey::new("password1", 1024);
|
||||||
let dir: PathBuf = temp_path.as_path().into();
|
let dir: PathBuf = temp_path.path().into();
|
||||||
let mut vault_file_path: PathBuf = dir.clone();
|
let mut vault_file_path: PathBuf = dir.clone();
|
||||||
vault_file_path.push(VAULT_FILE_NAME);
|
vault_file_path.push(VAULT_FILE_NAME);
|
||||||
|
|
||||||
@ -389,9 +391,9 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn vault_directory_can_be_created() {
|
fn vault_directory_can_be_created() {
|
||||||
// given
|
// given
|
||||||
let temp_path = RandomTempPath::new();
|
let temp_path = TempDir::new("").unwrap();
|
||||||
let key = VaultKey::new("password", 1024);
|
let key = VaultKey::new("password", 1024);
|
||||||
let dir: PathBuf = temp_path.as_path().into();
|
let dir: PathBuf = temp_path.path().into();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let vault = VaultDiskDirectory::create(&dir, "vault", key.clone());
|
let vault = VaultDiskDirectory::create(&dir, "vault", key.clone());
|
||||||
@ -409,9 +411,9 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn vault_directory_cannot_be_created_if_already_exists() {
|
fn vault_directory_cannot_be_created_if_already_exists() {
|
||||||
// given
|
// given
|
||||||
let temp_path = RandomTempPath::new();
|
let temp_path = TempDir::new("").unwrap();
|
||||||
let key = VaultKey::new("password", 1024);
|
let key = VaultKey::new("password", 1024);
|
||||||
let dir: PathBuf = temp_path.as_path().into();
|
let dir: PathBuf = temp_path.path().into();
|
||||||
let mut vault_dir = dir.clone();
|
let mut vault_dir = dir.clone();
|
||||||
vault_dir.push("vault");
|
vault_dir.push("vault");
|
||||||
fs::create_dir_all(&vault_dir).unwrap();
|
fs::create_dir_all(&vault_dir).unwrap();
|
||||||
@ -426,9 +428,9 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn vault_directory_cannot_be_opened_if_not_exists() {
|
fn vault_directory_cannot_be_opened_if_not_exists() {
|
||||||
// given
|
// given
|
||||||
let temp_path = RandomTempPath::create_dir();
|
let temp_path = TempDir::new("").unwrap();
|
||||||
let key = VaultKey::new("password", 1024);
|
let key = VaultKey::new("password", 1024);
|
||||||
let dir: PathBuf = temp_path.as_path().into();
|
let dir: PathBuf = temp_path.path().into();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let vault = VaultDiskDirectory::at(&dir, "vault", key);
|
let vault = VaultDiskDirectory::at(&dir, "vault", key);
|
||||||
|
@ -620,13 +620,14 @@ impl SimpleSecretStore for EthMultiStore {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
extern crate tempdir;
|
||||||
|
|
||||||
use dir::{KeyDirectory, MemoryDirectory, RootDiskDirectory};
|
use dir::{KeyDirectory, MemoryDirectory, RootDiskDirectory};
|
||||||
use ethkey::{Random, Generator, KeyPair};
|
use ethkey::{Random, Generator, KeyPair};
|
||||||
use secret_store::{SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef, Derivation};
|
use secret_store::{SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef, Derivation};
|
||||||
use super::{EthStore, EthMultiStore};
|
use super::{EthStore, EthMultiStore};
|
||||||
use devtools::RandomTempPath;
|
use self::tempdir::TempDir;
|
||||||
use util::H256;
|
use bigint::hash::H256;
|
||||||
|
|
||||||
fn keypair() -> KeyPair {
|
fn keypair() -> KeyPair {
|
||||||
Random.generate().unwrap()
|
Random.generate().unwrap()
|
||||||
@ -642,13 +643,13 @@ mod tests {
|
|||||||
|
|
||||||
struct RootDiskDirectoryGuard {
|
struct RootDiskDirectoryGuard {
|
||||||
pub key_dir: Option<Box<KeyDirectory>>,
|
pub key_dir: Option<Box<KeyDirectory>>,
|
||||||
_path: RandomTempPath,
|
_path: TempDir,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RootDiskDirectoryGuard {
|
impl RootDiskDirectoryGuard {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let temp_path = RandomTempPath::new();
|
let temp_path = TempDir::new("").unwrap();
|
||||||
let disk_dir = Box::new(RootDiskDirectory::create(temp_path.as_path()).unwrap());
|
let disk_dir = Box::new(RootDiskDirectory::create(temp_path.path()).unwrap());
|
||||||
|
|
||||||
RootDiskDirectoryGuard {
|
RootDiskDirectoryGuard {
|
||||||
key_dir: Some(disk_dir),
|
key_dir: Some(disk_dir),
|
||||||
|
@ -14,10 +14,11 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::fmt;
|
use std::{fmt, str};
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use serde::ser::SerializeStruct;
|
use serde::ser::SerializeStruct;
|
||||||
use serde::de::{Visitor, MapVisitor, Error};
|
use serde::de::{Visitor, MapVisitor, Error};
|
||||||
|
use serde_json;
|
||||||
use super::{Cipher, CipherSer, CipherSerParams, Kdf, KdfSer, KdfSerParams, H256, Bytes};
|
use super::{Cipher, CipherSer, CipherSerParams, Kdf, KdfSer, KdfSerParams, H256, Bytes};
|
||||||
|
|
||||||
pub type CipherText = Bytes;
|
pub type CipherText = Bytes;
|
||||||
@ -30,6 +31,20 @@ pub struct Crypto {
|
|||||||
pub mac: H256,
|
pub mac: H256,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl str::FromStr for Crypto {
|
||||||
|
type Err = serde_json::error::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
serde_json::from_str(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Crypto> for String {
|
||||||
|
fn from(c: Crypto) -> Self {
|
||||||
|
serde_json::to_string(&c).expect("serialization cannot fail, cause all crypto keys are strings")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum CryptoField {
|
enum CryptoField {
|
||||||
Cipher,
|
Cipher,
|
||||||
CipherParams,
|
CipherParams,
|
||||||
|
@ -29,9 +29,9 @@ extern crate serde_json;
|
|||||||
extern crate smallvec;
|
extern crate smallvec;
|
||||||
extern crate time;
|
extern crate time;
|
||||||
extern crate tiny_keccak;
|
extern crate tiny_keccak;
|
||||||
|
extern crate tempdir;
|
||||||
|
|
||||||
extern crate ethcore_devtools as devtools;
|
extern crate ethcore_bigint as bigint;
|
||||||
extern crate ethcore_util as util;
|
|
||||||
extern crate ethcrypto as crypto;
|
extern crate ethcrypto as crypto;
|
||||||
extern crate ethkey as _ethkey;
|
extern crate ethkey as _ethkey;
|
||||||
extern crate parity_wordlist;
|
extern crate parity_wordlist;
|
||||||
@ -54,7 +54,7 @@ mod presale;
|
|||||||
mod random;
|
mod random;
|
||||||
mod secret_store;
|
mod secret_store;
|
||||||
|
|
||||||
pub use self::account::SafeAccount;
|
pub use self::account::{SafeAccount, Crypto};
|
||||||
pub use self::error::Error;
|
pub use self::error::Error;
|
||||||
pub use self::ethstore::{EthStore, EthMultiStore};
|
pub use self::ethstore::{EthStore, EthMultiStore};
|
||||||
pub use self::import::{import_accounts, read_geth_accounts};
|
pub use self::import::{import_accounts, read_geth_accounts};
|
||||||
|
@ -19,7 +19,7 @@ use std::path::PathBuf;
|
|||||||
use ethkey::{Address, Message, Signature, Secret, Public};
|
use ethkey::{Address, Message, Signature, Secret, Public};
|
||||||
use Error;
|
use Error;
|
||||||
use json::{Uuid, OpaqueKeyFile};
|
use json::{Uuid, OpaqueKeyFile};
|
||||||
use util::H256;
|
use bigint::hash::H256;
|
||||||
|
|
||||||
/// Key directory reference
|
/// Key directory reference
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "parity.js",
|
"name": "parity.js",
|
||||||
"version": "1.7.49",
|
"version": "1.7.50",
|
||||||
"main": "release/index.js",
|
"main": "release/index.js",
|
||||||
"jsnext:main": "src/index.js",
|
"jsnext:main": "src/index.js",
|
||||||
"author": "Parity Team <admin@parity.io>",
|
"author": "Parity Team <admin@parity.io>",
|
||||||
|
@ -522,6 +522,11 @@ export default class Parity {
|
|||||||
.then(outNumber);
|
.then(outNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signMessage (address, password, messageHash) {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_signMessage', inAddress(address), password, inHex(messageHash));
|
||||||
|
}
|
||||||
|
|
||||||
testPassword (account, password) {
|
testPassword (account, password) {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_testPassword', inAddress(account), password);
|
.execute('parity_testPassword', inAddress(account), password);
|
||||||
|
@ -37,7 +37,7 @@ export default {
|
|||||||
params: `An error occurred with the following description`
|
params: `An error occurred with the following description`
|
||||||
},
|
},
|
||||||
input: {
|
input: {
|
||||||
abi: `ABI Interface`,
|
abi: `ABI Definition`,
|
||||||
code: `Bytecode`,
|
code: `Bytecode`,
|
||||||
metadata: `Metadata`,
|
metadata: `Metadata`,
|
||||||
swarm: `Swarm Metadata Hash`
|
swarm: `Swarm Metadata Hash`
|
||||||
|
@ -1881,5 +1881,31 @@ export default {
|
|||||||
desc: 'Decrypted message.',
|
desc: 'Decrypted message.',
|
||||||
example: withComment('0x68656c6c6f20776f726c64', 'hello world')
|
example: withComment('0x68656c6c6f20776f726c64', 'hello world')
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
signMessage: {
|
||||||
|
desc: 'Sign the hashed message bytes with the given account.',
|
||||||
|
params: [
|
||||||
|
{
|
||||||
|
type: Address,
|
||||||
|
desc: 'Account which signs the message.',
|
||||||
|
example: '0xc171033d5cbff7175f29dfd3a63dda3d6f8f385e'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: String,
|
||||||
|
desc: 'Passphrase to unlock the account.',
|
||||||
|
example: 'password1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: Data,
|
||||||
|
desc: 'Hashed message.',
|
||||||
|
example: '0xbc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: Data,
|
||||||
|
desc: 'Message signature.',
|
||||||
|
example: '0x1d9e33a8cf8bfc089a172bca01da462f9e359c6cb1b0f29398bc884e4d18df4f78588aee4fb5cc067ca62d2abab995e0bba29527be6ac98105b0320020a2efaf00'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -96,6 +96,7 @@ export default class Store {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@computed get qrAddressValid () {
|
@computed get qrAddressValid () {
|
||||||
|
console.log('qrValid', this.qrAddress, this._api.util.isAddressValid(this.qrAddress));
|
||||||
return this._api.util.isAddressValid(this.qrAddress);
|
return this._api.util.isAddressValid(this.qrAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,7 +156,10 @@ export default class Store {
|
|||||||
qrAddress = `0x${qrAddress}`;
|
qrAddress = `0x${qrAddress}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.qrAddress = qrAddress;
|
// FIXME: Current native signer encoding is not 100% for EIP-55, lowercase for now
|
||||||
|
this.qrAddress = this._api.util
|
||||||
|
? this._api.util.toChecksumAddress(qrAddress.toLowerCase())
|
||||||
|
: qrAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action setVaultName = (vaultName) => {
|
@action setVaultName = (vaultName) => {
|
||||||
|
@ -608,7 +608,7 @@ class WriteContract extends Component {
|
|||||||
label={
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='writeContract.input.abi'
|
id='writeContract.input.abi'
|
||||||
defaultMessage='ABI Interface'
|
defaultMessage='ABI Definition'
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
readOnly
|
readOnly
|
||||||
|
@ -67,7 +67,6 @@ impl IoHandler<ClientIoMessage> for QueueCull {
|
|||||||
|
|
||||||
let (sync, on_demand, txq) = (self.sync.clone(), self.on_demand.clone(), self.txq.clone());
|
let (sync, on_demand, txq) = (self.sync.clone(), self.on_demand.clone(), self.txq.clone());
|
||||||
let best_header = self.client.best_block_header();
|
let best_header = self.client.best_block_header();
|
||||||
let start_nonce = self.client.engine().account_start_nonce();
|
|
||||||
|
|
||||||
info!(target: "cull", "Attempting to cull queued transactions from {} senders.", senders.len());
|
info!(target: "cull", "Attempting to cull queued transactions from {} senders.", senders.len());
|
||||||
self.remote.spawn_with_timeout(move || {
|
self.remote.spawn_with_timeout(move || {
|
||||||
@ -75,8 +74,7 @@ impl IoHandler<ClientIoMessage> for QueueCull {
|
|||||||
// fetch the nonce of each sender in the queue.
|
// fetch the nonce of each sender in the queue.
|
||||||
let nonce_futures = senders.iter()
|
let nonce_futures = senders.iter()
|
||||||
.map(|&address| request::Account { header: best_header.clone(), address: address })
|
.map(|&address| request::Account { header: best_header.clone(), address: address })
|
||||||
.map(|request| on_demand.account(ctx, request))
|
.map(|request| on_demand.account(ctx, request).map(|acc| acc.nonce))
|
||||||
.map(move |fut| fut.map(move |x| x.map(|acc| acc.nonce).unwrap_or(start_nonce)))
|
|
||||||
.zip(senders.iter())
|
.zip(senders.iter())
|
||||||
.map(|(fut, &addr)| fut.map(move |nonce| (addr, nonce)));
|
.map(|(fut, &addr)| fut.map(move |nonce| (addr, nonce)));
|
||||||
|
|
||||||
|
@ -244,7 +244,7 @@ impl Dependencies for FullDependencies {
|
|||||||
);
|
);
|
||||||
handler.extend_with(client.to_delegate());
|
handler.extend_with(client.to_delegate());
|
||||||
|
|
||||||
let filter_client = EthFilterClient::new(&self.client, &self.miner);
|
let filter_client = EthFilterClient::new(self.client.clone(), self.miner.clone());
|
||||||
handler.extend_with(filter_client.to_delegate());
|
handler.extend_with(filter_client.to_delegate());
|
||||||
|
|
||||||
add_signing_methods!(EthSigning, handler, self);
|
add_signing_methods!(EthSigning, handler, self);
|
||||||
@ -377,9 +377,8 @@ impl Dependencies for LightDependencies {
|
|||||||
self.secret_store.clone(),
|
self.secret_store.clone(),
|
||||||
self.cache.clone(),
|
self.cache.clone(),
|
||||||
);
|
);
|
||||||
handler.extend_with(client.to_delegate());
|
handler.extend_with(Eth::to_delegate(client.clone()));
|
||||||
|
handler.extend_with(EthFilter::to_delegate(client));
|
||||||
// TODO: filters.
|
|
||||||
add_signing_methods!(EthSigning, handler, self);
|
add_signing_methods!(EthSigning, handler, self);
|
||||||
},
|
},
|
||||||
Api::Personal => {
|
Api::Personal => {
|
||||||
|
@ -192,6 +192,10 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) ->
|
|||||||
info!("Starting {}", Colour::White.bold().paint(version()));
|
info!("Starting {}", Colour::White.bold().paint(version()));
|
||||||
info!("Running in experimental {} mode.", Colour::Blue.bold().paint("Light Client"));
|
info!("Running in experimental {} mode.", Colour::Blue.bold().paint("Light Client"));
|
||||||
|
|
||||||
|
// TODO: configurable cache size.
|
||||||
|
let cache = LightDataCache::new(Default::default(), ::time::Duration::minutes(GAS_CORPUS_EXPIRATION_MINUTES));
|
||||||
|
let cache = Arc::new(::util::Mutex::new(cache));
|
||||||
|
|
||||||
// start client and create transaction queue.
|
// start client and create transaction queue.
|
||||||
let mut config = light_client::Config {
|
let mut config = light_client::Config {
|
||||||
queue: Default::default(),
|
queue: Default::default(),
|
||||||
@ -204,7 +208,7 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) ->
|
|||||||
config.queue.max_mem_use = cmd.cache_config.queue() as usize * 1024 * 1024;
|
config.queue.max_mem_use = cmd.cache_config.queue() as usize * 1024 * 1024;
|
||||||
config.queue.verifier_settings = cmd.verifier_settings;
|
config.queue.verifier_settings = cmd.verifier_settings;
|
||||||
|
|
||||||
let service = light_client::Service::start(config, &spec, &db_dirs.client_path(algorithm))
|
let service = light_client::Service::start(config, &spec, &db_dirs.client_path(algorithm), cache.clone())
|
||||||
.map_err(|e| format!("Error starting light client: {}", e))?;
|
.map_err(|e| format!("Error starting light client: {}", e))?;
|
||||||
let txq = Arc::new(RwLock::new(::light::transaction_queue::TransactionQueue::default()));
|
let txq = Arc::new(RwLock::new(::light::transaction_queue::TransactionQueue::default()));
|
||||||
let provider = ::light::provider::LightProvider::new(service.client().clone(), txq.clone());
|
let provider = ::light::provider::LightProvider::new(service.client().clone(), txq.clone());
|
||||||
@ -216,12 +220,9 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) ->
|
|||||||
net_conf.boot_nodes = spec.nodes.clone();
|
net_conf.boot_nodes = spec.nodes.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: configurable cache size.
|
|
||||||
let cache = LightDataCache::new(Default::default(), ::time::Duration::minutes(GAS_CORPUS_EXPIRATION_MINUTES));
|
|
||||||
let cache = Arc::new(::util::Mutex::new(cache));
|
|
||||||
|
|
||||||
// start on_demand service.
|
// start on_demand service.
|
||||||
let on_demand = Arc::new(::light::on_demand::OnDemand::new(cache.clone()));
|
let account_start_nonce = service.client().engine().account_start_nonce();
|
||||||
|
let on_demand = Arc::new(::light::on_demand::OnDemand::new(cache.clone(), account_start_nonce));
|
||||||
|
|
||||||
// set network path.
|
// set network path.
|
||||||
net_conf.net_config_path = Some(db_dirs.network_path().to_string_lossy().into_owned());
|
net_conf.net_config_path = Some(db_dirs.network_path().to_string_lossy().into_owned());
|
||||||
|
@ -268,7 +268,7 @@ impl LightDispatcher {
|
|||||||
|
|
||||||
match nonce_future {
|
match nonce_future {
|
||||||
Some(x) =>
|
Some(x) =>
|
||||||
x.map(|acc| acc.map_or_else(Default::default, |acc| acc.nonce))
|
x.map(|acc| acc.nonce)
|
||||||
.map_err(|_| errors::no_light_peers())
|
.map_err(|_| errors::no_light_peers())
|
||||||
.boxed(),
|
.boxed(),
|
||||||
None => future::err(errors::network_disabled()).boxed()
|
None => future::err(errors::network_disabled()).boxed()
|
||||||
@ -474,7 +474,7 @@ pub fn execute<D: Dispatcher + 'static>(
|
|||||||
.map(ConfirmationResponse::SignTransaction)
|
.map(ConfirmationResponse::SignTransaction)
|
||||||
).boxed()
|
).boxed()
|
||||||
},
|
},
|
||||||
ConfirmationPayload::Signature(address, mut data) => {
|
ConfirmationPayload::EthSignMessage(address, mut data) => {
|
||||||
let mut message_data =
|
let mut message_data =
|
||||||
format!("\x19Ethereum Signed Message:\n{}", data.len())
|
format!("\x19Ethereum Signed Message:\n{}", data.len())
|
||||||
.into_bytes();
|
.into_bytes();
|
||||||
@ -574,8 +574,8 @@ pub fn from_rpc<D>(payload: RpcConfirmationPayload, default_account: Address, di
|
|||||||
RpcConfirmationPayload::Decrypt(RpcDecryptRequest { address, msg }) => {
|
RpcConfirmationPayload::Decrypt(RpcDecryptRequest { address, msg }) => {
|
||||||
future::ok(ConfirmationPayload::Decrypt(address.into(), msg.into())).boxed()
|
future::ok(ConfirmationPayload::Decrypt(address.into(), msg.into())).boxed()
|
||||||
},
|
},
|
||||||
RpcConfirmationPayload::Signature(RpcSignRequest { address, data }) => {
|
RpcConfirmationPayload::EthSignMessage(RpcSignRequest { address, data }) => {
|
||||||
future::ok(ConfirmationPayload::Signature(address.into(), data.into())).boxed()
|
future::ok(ConfirmationPayload::EthSignMessage(address.into(), data.into())).boxed()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,7 @@ impl LightFetch {
|
|||||||
sync.with_context(|ctx| on_demand.account(ctx, request::Account {
|
sync.with_context(|ctx| on_demand.account(ctx, request::Account {
|
||||||
header: header,
|
header: header,
|
||||||
address: address,
|
address: address,
|
||||||
}))
|
}).map(Some))
|
||||||
.map(|x| x.map_err(errors::on_demand_cancel).boxed())
|
.map(|x| x.map_err(errors::on_demand_cancel).boxed())
|
||||||
.unwrap_or_else(|| future::err(errors::network_disabled()).boxed())
|
.unwrap_or_else(|| future::err(errors::network_disabled()).boxed())
|
||||||
}).boxed()
|
}).boxed()
|
||||||
@ -197,4 +197,21 @@ impl LightFetch {
|
|||||||
}
|
}
|
||||||
}).boxed()
|
}).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a block.
|
||||||
|
pub fn block(&self, id: BlockId) -> BoxFuture<Option<encoded::Block>, Error> {
|
||||||
|
let (on_demand, sync) = (self.on_demand.clone(), self.sync.clone());
|
||||||
|
|
||||||
|
self.header(id).and_then(move |hdr| {
|
||||||
|
let req = match hdr {
|
||||||
|
Some(hdr) => request::Body::new(hdr),
|
||||||
|
None => return future::ok(None).boxed(),
|
||||||
|
};
|
||||||
|
|
||||||
|
match sync.with_context(move |ctx| on_demand.block(ctx, req)) {
|
||||||
|
Some(fut) => fut.map_err(errors::on_demand_cancel).map(Some).boxed(),
|
||||||
|
None => future::err(errors::network_disabled()).boxed(),
|
||||||
|
}
|
||||||
|
}).boxed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,8 +113,8 @@ pub enum ConfirmationPayload {
|
|||||||
SendTransaction(FilledTransactionRequest),
|
SendTransaction(FilledTransactionRequest),
|
||||||
/// Sign Transaction
|
/// Sign Transaction
|
||||||
SignTransaction(FilledTransactionRequest),
|
SignTransaction(FilledTransactionRequest),
|
||||||
/// Sign request
|
/// Sign a message with an Ethereum specific security prefix.
|
||||||
Signature(Address, Bytes),
|
EthSignMessage(Address, Bytes),
|
||||||
/// Decrypt request
|
/// Decrypt request
|
||||||
Decrypt(Address, Bytes),
|
Decrypt(Address, Bytes),
|
||||||
}
|
}
|
||||||
@ -124,7 +124,7 @@ impl ConfirmationPayload {
|
|||||||
match *self {
|
match *self {
|
||||||
ConfirmationPayload::SendTransaction(ref request) => request.from,
|
ConfirmationPayload::SendTransaction(ref request) => request.from,
|
||||||
ConfirmationPayload::SignTransaction(ref request) => request.from,
|
ConfirmationPayload::SignTransaction(ref request) => request.from,
|
||||||
ConfirmationPayload::Signature(ref address, _) => *address,
|
ConfirmationPayload::EthSignMessage(ref address, _) => *address,
|
||||||
ConfirmationPayload::Decrypt(ref address, _) => *address,
|
ConfirmationPayload::Decrypt(ref address, _) => *address,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -544,23 +544,23 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
Err(errors::deprecated("Compilation functionality is deprecated.".to_string()))
|
Err(errors::deprecated("Compilation functionality is deprecated.".to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn logs(&self, filter: Filter) -> Result<Vec<Log>, Error> {
|
fn logs(&self, filter: Filter) -> BoxFuture<Vec<Log>, Error> {
|
||||||
let include_pending = filter.to_block == Some(BlockNumber::Pending);
|
let include_pending = filter.to_block == Some(BlockNumber::Pending);
|
||||||
let filter: EthcoreFilter = filter.into();
|
let filter: EthcoreFilter = filter.into();
|
||||||
let mut logs = take_weak!(self.client).logs(filter.clone())
|
let mut logs = take_weakf!(self.client).logs(filter.clone())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(From::from)
|
.map(From::from)
|
||||||
.collect::<Vec<Log>>();
|
.collect::<Vec<Log>>();
|
||||||
|
|
||||||
if include_pending {
|
if include_pending {
|
||||||
let best_block = take_weak!(self.client).chain_info().best_block_number;
|
let best_block = take_weakf!(self.client).chain_info().best_block_number;
|
||||||
let pending = pending_logs(&*take_weak!(self.miner), best_block, &filter);
|
let pending = pending_logs(&*take_weakf!(self.miner), best_block, &filter);
|
||||||
logs.extend(pending);
|
logs.extend(pending);
|
||||||
}
|
}
|
||||||
|
|
||||||
let logs = limit_logs(logs, filter.limit);
|
let logs = limit_logs(logs, filter.limit);
|
||||||
|
|
||||||
Ok(logs)
|
future::ok(logs).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn work(&self, no_new_work_timeout: Trailing<u64>) -> Result<Work, Error> {
|
fn work(&self, no_new_work_timeout: Trailing<u64>) -> Result<Work, Error> {
|
||||||
|
@ -16,89 +16,133 @@
|
|||||||
|
|
||||||
//! Eth Filter RPC implementation
|
//! Eth Filter RPC implementation
|
||||||
|
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::Arc;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use jsonrpc_core::*;
|
use jsonrpc_core::*;
|
||||||
use ethcore::miner::MinerService;
|
use ethcore::miner::MinerService;
|
||||||
use ethcore::filter::Filter as EthcoreFilter;
|
use ethcore::filter::Filter as EthcoreFilter;
|
||||||
use ethcore::client::{BlockChainClient, BlockId};
|
use ethcore::client::{BlockChainClient, BlockId};
|
||||||
use util::Mutex;
|
use util::{H256, Mutex};
|
||||||
|
|
||||||
|
use futures::{future, Future, BoxFuture};
|
||||||
|
|
||||||
use v1::traits::EthFilter;
|
use v1::traits::EthFilter;
|
||||||
use v1::types::{BlockNumber, Index, Filter, FilterChanges, Log, H256 as RpcH256, U256 as RpcU256};
|
use v1::types::{BlockNumber, Index, Filter, FilterChanges, Log, H256 as RpcH256, U256 as RpcU256};
|
||||||
use v1::helpers::{PollFilter, PollManager, limit_logs};
|
use v1::helpers::{PollFilter, PollManager, limit_logs};
|
||||||
use v1::impls::eth::pending_logs;
|
use v1::impls::eth::pending_logs;
|
||||||
|
|
||||||
/// Eth filter rpc implementation.
|
/// Something which provides data that can be filtered over.
|
||||||
|
pub trait Filterable {
|
||||||
|
/// Current best block number.
|
||||||
|
fn best_block_number(&self) -> u64;
|
||||||
|
|
||||||
|
/// Get a block hash by block id.
|
||||||
|
fn block_hash(&self, id: BlockId) -> Option<RpcH256>;
|
||||||
|
|
||||||
|
/// pending transaction hashes at the given block.
|
||||||
|
fn pending_transactions_hashes(&self, block_number: u64) -> Vec<H256>;
|
||||||
|
|
||||||
|
/// Get logs that match the given filter.
|
||||||
|
fn logs(&self, filter: EthcoreFilter) -> BoxFuture<Vec<Log>, Error>;
|
||||||
|
|
||||||
|
/// Get logs from the pending block.
|
||||||
|
fn pending_logs(&self, block_number: u64, filter: &EthcoreFilter) -> Vec<Log>;
|
||||||
|
|
||||||
|
/// Get a reference to the poll manager.
|
||||||
|
fn polls(&self) -> &Mutex<PollManager<PollFilter>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Eth filter rpc implementation for a full node.
|
||||||
pub struct EthFilterClient<C, M> where
|
pub struct EthFilterClient<C, M> where
|
||||||
C: BlockChainClient,
|
C: BlockChainClient,
|
||||||
M: MinerService {
|
M: MinerService {
|
||||||
|
|
||||||
client: Weak<C>,
|
client: Arc<C>,
|
||||||
miner: Weak<M>,
|
miner: Arc<M>,
|
||||||
polls: Mutex<PollManager<PollFilter>>,
|
polls: Mutex<PollManager<PollFilter>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C, M> EthFilterClient<C, M> where
|
impl<C, M> EthFilterClient<C, M> where C: BlockChainClient, M: MinerService {
|
||||||
C: BlockChainClient,
|
|
||||||
M: MinerService {
|
|
||||||
|
|
||||||
/// Creates new Eth filter client.
|
/// Creates new Eth filter client.
|
||||||
pub fn new(client: &Arc<C>, miner: &Arc<M>) -> Self {
|
pub fn new(client: Arc<C>, miner: Arc<M>) -> Self {
|
||||||
EthFilterClient {
|
EthFilterClient {
|
||||||
client: Arc::downgrade(client),
|
client: client,
|
||||||
miner: Arc::downgrade(miner),
|
miner: miner,
|
||||||
polls: Mutex::new(PollManager::new()),
|
polls: Mutex::new(PollManager::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C, M> EthFilter for EthFilterClient<C, M>
|
impl<C, M> Filterable for EthFilterClient<C, M> where C: BlockChainClient, M: MinerService {
|
||||||
where C: BlockChainClient + 'static, M: MinerService + 'static
|
fn best_block_number(&self) -> u64 {
|
||||||
{
|
self.client.chain_info().best_block_number
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block_hash(&self, id: BlockId) -> Option<RpcH256> {
|
||||||
|
self.client.block_hash(id).map(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pending_transactions_hashes(&self, best: u64) -> Vec<H256> {
|
||||||
|
self.miner.pending_transactions_hashes(best)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn logs(&self, filter: EthcoreFilter) -> BoxFuture<Vec<Log>, Error> {
|
||||||
|
future::ok(self.client.logs(filter).into_iter().map(Into::into).collect()).boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pending_logs(&self, block_number: u64, filter: &EthcoreFilter) -> Vec<Log> {
|
||||||
|
pending_logs(&*self.miner, block_number, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn polls(&self) -> &Mutex<PollManager<PollFilter>> { &self.polls }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
impl<T: Filterable + Send + Sync + 'static> EthFilter for T {
|
||||||
fn new_filter(&self, filter: Filter) -> Result<RpcU256, Error> {
|
fn new_filter(&self, filter: Filter) -> Result<RpcU256, Error> {
|
||||||
let mut polls = self.polls.lock();
|
let mut polls = self.polls().lock();
|
||||||
let block_number = take_weak!(self.client).chain_info().best_block_number;
|
let block_number = self.best_block_number();
|
||||||
let id = polls.create_poll(PollFilter::Logs(block_number, Default::default(), filter));
|
let id = polls.create_poll(PollFilter::Logs(block_number, Default::default(), filter));
|
||||||
Ok(id.into())
|
Ok(id.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_block_filter(&self) -> Result<RpcU256, Error> {
|
fn new_block_filter(&self) -> Result<RpcU256, Error> {
|
||||||
let mut polls = self.polls.lock();
|
let mut polls = self.polls().lock();
|
||||||
let id = polls.create_poll(PollFilter::Block(take_weak!(self.client).chain_info().best_block_number));
|
let id = polls.create_poll(PollFilter::Block(self.best_block_number()));
|
||||||
Ok(id.into())
|
Ok(id.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_pending_transaction_filter(&self) -> Result<RpcU256, Error> {
|
fn new_pending_transaction_filter(&self) -> Result<RpcU256, Error> {
|
||||||
let mut polls = self.polls.lock();
|
let mut polls = self.polls().lock();
|
||||||
let best_block = take_weak!(self.client).chain_info().best_block_number;
|
let best_block = self.best_block_number();
|
||||||
let pending_transactions = take_weak!(self.miner).pending_transactions_hashes(best_block);
|
let pending_transactions = self.pending_transactions_hashes(best_block);
|
||||||
let id = polls.create_poll(PollFilter::PendingTransaction(pending_transactions));
|
let id = polls.create_poll(PollFilter::PendingTransaction(pending_transactions));
|
||||||
Ok(id.into())
|
Ok(id.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn filter_changes(&self, index: Index) -> Result<FilterChanges, Error> {
|
fn filter_changes(&self, index: Index) -> BoxFuture<FilterChanges, Error> {
|
||||||
let client = take_weak!(self.client);
|
let mut polls = self.polls().lock();
|
||||||
let mut polls = self.polls.lock();
|
|
||||||
match polls.poll_mut(&index.value()) {
|
match polls.poll_mut(&index.value()) {
|
||||||
None => Ok(FilterChanges::Empty),
|
None => future::ok(FilterChanges::Empty).boxed(),
|
||||||
Some(filter) => match *filter {
|
Some(filter) => match *filter {
|
||||||
PollFilter::Block(ref mut block_number) => {
|
PollFilter::Block(ref mut block_number) => {
|
||||||
// + 1, cause we want to return hashes including current block hash.
|
// + 1, cause we want to return hashes including current block hash.
|
||||||
let current_number = client.chain_info().best_block_number + 1;
|
let current_number = self.best_block_number() + 1;
|
||||||
let hashes = (*block_number..current_number).into_iter()
|
let hashes = (*block_number..current_number).into_iter()
|
||||||
.map(BlockId::Number)
|
.map(BlockId::Number)
|
||||||
.filter_map(|id| client.block_hash(id))
|
.filter_map(|id| self.block_hash(id))
|
||||||
.map(Into::into)
|
|
||||||
.collect::<Vec<RpcH256>>();
|
.collect::<Vec<RpcH256>>();
|
||||||
|
|
||||||
*block_number = current_number;
|
*block_number = current_number;
|
||||||
|
|
||||||
Ok(FilterChanges::Hashes(hashes))
|
future::ok(FilterChanges::Hashes(hashes)).boxed()
|
||||||
},
|
},
|
||||||
PollFilter::PendingTransaction(ref mut previous_hashes) => {
|
PollFilter::PendingTransaction(ref mut previous_hashes) => {
|
||||||
// get hashes of pending transactions
|
// get hashes of pending transactions
|
||||||
let best_block = take_weak!(self.client).chain_info().best_block_number;
|
let best_block = self.best_block_number();
|
||||||
let current_hashes = take_weak!(self.miner).pending_transactions_hashes(best_block);
|
let current_hashes = self.pending_transactions_hashes(best_block);
|
||||||
|
|
||||||
let new_hashes =
|
let new_hashes =
|
||||||
{
|
{
|
||||||
@ -117,11 +161,11 @@ impl<C, M> EthFilter for EthFilterClient<C, M>
|
|||||||
*previous_hashes = current_hashes;
|
*previous_hashes = current_hashes;
|
||||||
|
|
||||||
// return new hashes
|
// return new hashes
|
||||||
Ok(FilterChanges::Hashes(new_hashes))
|
future::ok(FilterChanges::Hashes(new_hashes)).boxed()
|
||||||
},
|
},
|
||||||
PollFilter::Logs(ref mut block_number, ref mut previous_logs, ref filter) => {
|
PollFilter::Logs(ref mut block_number, ref mut previous_logs, ref filter) => {
|
||||||
// retrive the current block number
|
// retrive the current block number
|
||||||
let current_number = client.chain_info().best_block_number;
|
let current_number = self.best_block_number();
|
||||||
|
|
||||||
// check if we need to check pending hashes
|
// check if we need to check pending hashes
|
||||||
let include_pending = filter.to_block == Some(BlockNumber::Pending);
|
let include_pending = filter.to_block == Some(BlockNumber::Pending);
|
||||||
@ -131,16 +175,9 @@ impl<C, M> EthFilter for EthFilterClient<C, M>
|
|||||||
filter.from_block = BlockId::Number(*block_number);
|
filter.from_block = BlockId::Number(*block_number);
|
||||||
filter.to_block = BlockId::Latest;
|
filter.to_block = BlockId::Latest;
|
||||||
|
|
||||||
// retrieve logs in range from_block..min(BlockId::Latest..to_block)
|
// retrieve pending logs
|
||||||
let mut logs = client.logs(filter.clone())
|
let pending = if include_pending {
|
||||||
.into_iter()
|
let pending_logs = self.pending_logs(current_number, &filter);
|
||||||
.map(From::from)
|
|
||||||
.collect::<Vec<Log>>();
|
|
||||||
|
|
||||||
// additionally retrieve pending logs
|
|
||||||
if include_pending {
|
|
||||||
let best_block = take_weak!(self.client).chain_info().best_block_number;
|
|
||||||
let pending_logs = pending_logs(&*take_weak!(self.miner), best_block, &filter);
|
|
||||||
|
|
||||||
// remove logs about which client was already notified about
|
// remove logs about which client was already notified about
|
||||||
let new_pending_logs: Vec<_> = pending_logs.iter()
|
let new_pending_logs: Vec<_> = pending_logs.iter()
|
||||||
@ -151,49 +188,56 @@ impl<C, M> EthFilter for EthFilterClient<C, M>
|
|||||||
// save all logs retrieved by client
|
// save all logs retrieved by client
|
||||||
*previous_logs = pending_logs.into_iter().collect();
|
*previous_logs = pending_logs.into_iter().collect();
|
||||||
|
|
||||||
// append logs array with new pending logs
|
new_pending_logs
|
||||||
logs.extend(new_pending_logs);
|
} else {
|
||||||
}
|
Vec::new()
|
||||||
|
};
|
||||||
let logs = limit_logs(logs, filter.limit);
|
|
||||||
|
|
||||||
// save the number of the next block as a first block from which
|
// save the number of the next block as a first block from which
|
||||||
// we want to get logs
|
// we want to get logs
|
||||||
*block_number = current_number + 1;
|
*block_number = current_number + 1;
|
||||||
|
|
||||||
Ok(FilterChanges::Logs(logs))
|
// retrieve logs in range from_block..min(BlockId::Latest..to_block)
|
||||||
|
let limit = filter.limit;
|
||||||
|
self.logs(filter)
|
||||||
|
.map(move |mut logs| { logs.extend(pending); logs }) // append fetched pending logs
|
||||||
|
.map(move |logs| limit_logs(logs, limit)) // limit the logs
|
||||||
|
.map(FilterChanges::Logs)
|
||||||
|
.boxed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn filter_logs(&self, index: Index) -> Result<Vec<Log>, Error> {
|
fn filter_logs(&self, index: Index) -> BoxFuture<Vec<Log>, Error> {
|
||||||
let mut polls = self.polls.lock();
|
let mut polls = self.polls().lock();
|
||||||
match polls.poll(&index.value()) {
|
match polls.poll(&index.value()) {
|
||||||
Some(&PollFilter::Logs(ref _block_number, ref _previous_log, ref filter)) => {
|
Some(&PollFilter::Logs(ref _block_number, ref _previous_log, ref filter)) => {
|
||||||
let include_pending = filter.to_block == Some(BlockNumber::Pending);
|
let include_pending = filter.to_block == Some(BlockNumber::Pending);
|
||||||
let filter: EthcoreFilter = filter.clone().into();
|
let filter: EthcoreFilter = filter.clone().into();
|
||||||
let mut logs = take_weak!(self.client).logs(filter.clone())
|
|
||||||
.into_iter()
|
|
||||||
.map(From::from)
|
|
||||||
.collect::<Vec<Log>>();
|
|
||||||
|
|
||||||
if include_pending {
|
// fetch pending logs.
|
||||||
let best_block = take_weak!(self.client).chain_info().best_block_number;
|
let pending = if include_pending {
|
||||||
logs.extend(pending_logs(&*take_weak!(self.miner), best_block, &filter));
|
let best_block = self.best_block_number();
|
||||||
}
|
self.pending_logs(best_block, &filter)
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
|
||||||
let logs = limit_logs(logs, filter.limit);
|
// retrieve logs asynchronously, appending pending logs.
|
||||||
|
let limit = filter.limit;
|
||||||
Ok(logs)
|
self.logs(filter)
|
||||||
|
.map(move |mut logs| { logs.extend(pending); logs })
|
||||||
|
.map(move |logs| limit_logs(logs, limit))
|
||||||
|
.boxed()
|
||||||
},
|
},
|
||||||
// just empty array
|
// just empty array
|
||||||
_ => Ok(Vec::new()),
|
_ => future::ok(Vec::new()).boxed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn uninstall_filter(&self, index: Index) -> Result<bool, Error> {
|
fn uninstall_filter(&self, index: Index) -> Result<bool, Error> {
|
||||||
self.polls.lock().remove_poll(&index.value());
|
self.polls().lock().remove_poll(&index.value());
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,7 @@ use ethcore::basic_account::BasicAccount;
|
|||||||
use ethcore::encoded;
|
use ethcore::encoded;
|
||||||
use ethcore::executed::{Executed, ExecutionError};
|
use ethcore::executed::{Executed, ExecutionError};
|
||||||
use ethcore::ids::BlockId;
|
use ethcore::ids::BlockId;
|
||||||
|
use ethcore::filter::Filter as EthcoreFilter;
|
||||||
use ethcore::transaction::{Action, SignedTransaction, Transaction as EthTransaction};
|
use ethcore::transaction::{Action, SignedTransaction, Transaction as EthTransaction};
|
||||||
use ethsync::LightSync;
|
use ethsync::LightSync;
|
||||||
use rlp::UntrustedRlp;
|
use rlp::UntrustedRlp;
|
||||||
@ -43,7 +44,9 @@ use util::{RwLock, Mutex, Uint, U256};
|
|||||||
use futures::{future, Future, BoxFuture, IntoFuture};
|
use futures::{future, Future, BoxFuture, IntoFuture};
|
||||||
use futures::sync::oneshot;
|
use futures::sync::oneshot;
|
||||||
|
|
||||||
|
use v1::impls::eth_filter::Filterable;
|
||||||
use v1::helpers::{CallRequest as CRequest, errors, limit_logs, dispatch};
|
use v1::helpers::{CallRequest as CRequest, errors, limit_logs, dispatch};
|
||||||
|
use v1::helpers::{PollFilter, PollManager};
|
||||||
use v1::helpers::block_import::is_major_importing;
|
use v1::helpers::block_import::is_major_importing;
|
||||||
use v1::helpers::light_fetch::LightFetch;
|
use v1::helpers::light_fetch::LightFetch;
|
||||||
use v1::traits::Eth;
|
use v1::traits::Eth;
|
||||||
@ -56,7 +59,7 @@ use v1::metadata::Metadata;
|
|||||||
|
|
||||||
use util::Address;
|
use util::Address;
|
||||||
|
|
||||||
/// Light client `ETH` RPC.
|
/// Light client `ETH` (and filter) RPC.
|
||||||
pub struct EthClient {
|
pub struct EthClient {
|
||||||
sync: Arc<LightSync>,
|
sync: Arc<LightSync>,
|
||||||
client: Arc<LightClient>,
|
client: Arc<LightClient>,
|
||||||
@ -64,6 +67,22 @@ pub struct EthClient {
|
|||||||
transaction_queue: Arc<RwLock<TransactionQueue>>,
|
transaction_queue: Arc<RwLock<TransactionQueue>>,
|
||||||
accounts: Arc<AccountProvider>,
|
accounts: Arc<AccountProvider>,
|
||||||
cache: Arc<Mutex<LightDataCache>>,
|
cache: Arc<Mutex<LightDataCache>>,
|
||||||
|
polls: Mutex<PollManager<PollFilter>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for EthClient {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
// each instance should have its own poll manager.
|
||||||
|
EthClient {
|
||||||
|
sync: self.sync.clone(),
|
||||||
|
client: self.client.clone(),
|
||||||
|
on_demand: self.on_demand.clone(),
|
||||||
|
transaction_queue: self.transaction_queue.clone(),
|
||||||
|
accounts: self.accounts.clone(),
|
||||||
|
cache: self.cache.clone(),
|
||||||
|
polls: Mutex::new(PollManager::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -85,6 +104,7 @@ impl EthClient {
|
|||||||
transaction_queue: transaction_queue,
|
transaction_queue: transaction_queue,
|
||||||
accounts: accounts,
|
accounts: accounts,
|
||||||
cache: cache,
|
cache: cache,
|
||||||
|
polls: Mutex::new(PollManager::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,6 +118,95 @@ impl EthClient {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get a "rich" block structure
|
||||||
|
fn rich_block(&self, id: BlockId, include_txs: bool) -> BoxFuture<Option<RichBlock>, Error> {
|
||||||
|
let (on_demand, sync) = (self.on_demand.clone(), self.sync.clone());
|
||||||
|
let (client, engine) = (self.client.clone(), self.client.engine().clone());
|
||||||
|
|
||||||
|
// helper for filling out a rich block once we've got a block and a score.
|
||||||
|
let fill_rich = move |block: encoded::Block, score: Option<U256>| {
|
||||||
|
let header = block.decode_header();
|
||||||
|
let extra_info = engine.extra_info(&header);
|
||||||
|
RichBlock {
|
||||||
|
inner: Block {
|
||||||
|
hash: Some(header.hash().into()),
|
||||||
|
size: Some(block.rlp().as_raw().len().into()),
|
||||||
|
parent_hash: header.parent_hash().clone().into(),
|
||||||
|
uncles_hash: header.uncles_hash().clone().into(),
|
||||||
|
author: header.author().clone().into(),
|
||||||
|
miner: header.author().clone().into(),
|
||||||
|
state_root: header.state_root().clone().into(),
|
||||||
|
transactions_root: header.transactions_root().clone().into(),
|
||||||
|
receipts_root: header.receipts_root().clone().into(),
|
||||||
|
number: Some(header.number().into()),
|
||||||
|
gas_used: header.gas_used().clone().into(),
|
||||||
|
gas_limit: header.gas_limit().clone().into(),
|
||||||
|
logs_bloom: header.log_bloom().clone().into(),
|
||||||
|
timestamp: header.timestamp().into(),
|
||||||
|
difficulty: header.difficulty().clone().into(),
|
||||||
|
total_difficulty: score.map(Into::into),
|
||||||
|
seal_fields: header.seal().into_iter().cloned().map(Into::into).collect(),
|
||||||
|
uncles: block.uncle_hashes().into_iter().map(Into::into).collect(),
|
||||||
|
transactions: match include_txs {
|
||||||
|
true => BlockTransactions::Full(block.view().localized_transactions().into_iter().map(Into::into).collect()),
|
||||||
|
false => BlockTransactions::Hashes(block.transaction_hashes().into_iter().map(Into::into).collect()),
|
||||||
|
},
|
||||||
|
extra_data: Bytes::new(header.extra_data().to_vec()),
|
||||||
|
},
|
||||||
|
extra_info: extra_info
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// get the block itself.
|
||||||
|
self.fetcher().block(id).and_then(move |block| match block {
|
||||||
|
None => return future::ok(None).boxed(),
|
||||||
|
Some(block) => {
|
||||||
|
// then fetch the total difficulty (this is much easier after getting the block).
|
||||||
|
match client.score(id) {
|
||||||
|
Some(score) => future::ok(fill_rich(block, Some(score))).map(Some).boxed(),
|
||||||
|
None => {
|
||||||
|
// make a CHT request to fetch the chain score.
|
||||||
|
let req = cht::block_to_cht_number(block.number())
|
||||||
|
.and_then(|num| client.cht_root(num as usize))
|
||||||
|
.and_then(|root| request::HeaderProof::new(block.number(), root));
|
||||||
|
|
||||||
|
|
||||||
|
let req = match req {
|
||||||
|
Some(req) => req,
|
||||||
|
None => {
|
||||||
|
// somehow the genesis block slipped past other checks.
|
||||||
|
// return it now.
|
||||||
|
let score = client.block_header(BlockId::Number(0))
|
||||||
|
.expect("genesis always stored; qed")
|
||||||
|
.difficulty();
|
||||||
|
|
||||||
|
return future::ok(fill_rich(block, Some(score))).map(Some).boxed()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// three possible outcomes:
|
||||||
|
// - network is down.
|
||||||
|
// - we get a score, but our hash is non-canonical.
|
||||||
|
// - we get ascore, and our hash is canonical.
|
||||||
|
let maybe_fut = sync.with_context(move |ctx| on_demand.hash_and_score_by_number(ctx, req));
|
||||||
|
match maybe_fut {
|
||||||
|
Some(fut) => fut.map(move |(hash, score)| {
|
||||||
|
let score = if hash == block.hash() {
|
||||||
|
Some(score)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(fill_rich(block, score))
|
||||||
|
}).map_err(errors::on_demand_cancel).boxed(),
|
||||||
|
None => return future::err(errors::network_disabled()).boxed(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).boxed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eth for EthClient {
|
impl Eth for EthClient {
|
||||||
@ -139,7 +248,10 @@ impl Eth for EthClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn gas_price(&self) -> Result<RpcU256, Error> {
|
fn gas_price(&self) -> Result<RpcU256, Error> {
|
||||||
Ok(Default::default())
|
Ok(self.cache.lock().gas_price_corpus()
|
||||||
|
.and_then(|c| c.median().cloned())
|
||||||
|
.map(RpcU256::from)
|
||||||
|
.unwrap_or_else(Default::default))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn accounts(&self, meta: Metadata) -> BoxFuture<Vec<RpcH160>, Error> {
|
fn accounts(&self, meta: Metadata) -> BoxFuture<Vec<RpcH160>, Error> {
|
||||||
@ -168,11 +280,11 @@ impl Eth for EthClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn block_by_hash(&self, hash: RpcH256, include_txs: bool) -> BoxFuture<Option<RichBlock>, Error> {
|
fn block_by_hash(&self, hash: RpcH256, include_txs: bool) -> BoxFuture<Option<RichBlock>, Error> {
|
||||||
future::err(errors::unimplemented(None)).boxed()
|
self.rich_block(BlockId::Hash(hash.into()), include_txs)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn block_by_number(&self, num: BlockNumber, include_txs: bool) -> BoxFuture<Option<RichBlock>, Error> {
|
fn block_by_number(&self, num: BlockNumber, include_txs: bool) -> BoxFuture<Option<RichBlock>, Error> {
|
||||||
future::err(errors::unimplemented(None)).boxed()
|
self.rich_block(num.into(), include_txs)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transaction_count(&self, address: RpcH160, num: Trailing<BlockNumber>) -> BoxFuture<RpcU256, Error> {
|
fn transaction_count(&self, address: RpcH160, num: Trailing<BlockNumber>) -> BoxFuture<RpcU256, Error> {
|
||||||
@ -348,19 +460,101 @@ impl Eth for EthClient {
|
|||||||
Err(errors::deprecated("Compilation of Solidity via RPC is deprecated".to_string()))
|
Err(errors::deprecated("Compilation of Solidity via RPC is deprecated".to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn logs(&self, _filter: Filter) -> Result<Vec<Log>, Error> {
|
fn logs(&self, filter: Filter) -> BoxFuture<Vec<Log>, Error> {
|
||||||
Err(errors::unimplemented(None))
|
let limit = filter.limit;
|
||||||
|
|
||||||
|
Filterable::logs(self, filter.into())
|
||||||
|
.map(move|logs| limit_logs(logs, limit))
|
||||||
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn work(&self, _timeout: Trailing<u64>) -> Result<Work, Error> {
|
fn work(&self, _timeout: Trailing<u64>) -> Result<Work, Error> {
|
||||||
Err(errors::unimplemented(None))
|
Err(errors::light_unimplemented(None))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn submit_work(&self, _nonce: RpcH64, _pow_hash: RpcH256, _mix_hash: RpcH256) -> Result<bool, Error> {
|
fn submit_work(&self, _nonce: RpcH64, _pow_hash: RpcH256, _mix_hash: RpcH256) -> Result<bool, Error> {
|
||||||
Err(errors::unimplemented(None))
|
Err(errors::light_unimplemented(None))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn submit_hashrate(&self, _rate: RpcU256, _id: RpcH256) -> Result<bool, Error> {
|
fn submit_hashrate(&self, _rate: RpcU256, _id: RpcH256) -> Result<bool, Error> {
|
||||||
Err(errors::unimplemented(None))
|
Err(errors::light_unimplemented(None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This trait implementation triggers a blanked impl of `EthFilter`.
|
||||||
|
impl Filterable for EthClient {
|
||||||
|
fn best_block_number(&self) -> u64 { self.client.chain_info().best_block_number }
|
||||||
|
|
||||||
|
fn block_hash(&self, id: BlockId) -> Option<RpcH256> {
|
||||||
|
self.client.block_hash(id).map(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pending_transactions_hashes(&self, _block_number: u64) -> Vec<::util::H256> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn logs(&self, filter: EthcoreFilter) -> BoxFuture<Vec<Log>, Error> {
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use futures::stream::{self, Stream};
|
||||||
|
use util::H2048;
|
||||||
|
|
||||||
|
// early exit for "to" block before "from" block.
|
||||||
|
let best_number = self.client.chain_info().best_block_number;
|
||||||
|
let block_number = |id| match id {
|
||||||
|
BlockId::Earliest => Some(0),
|
||||||
|
BlockId::Latest | BlockId::Pending => Some(best_number),
|
||||||
|
BlockId::Hash(h) => self.client.block_header(BlockId::Hash(h)).map(|hdr| hdr.number()),
|
||||||
|
BlockId::Number(x) => Some(x),
|
||||||
|
};
|
||||||
|
|
||||||
|
match (block_number(filter.to_block), block_number(filter.from_block)) {
|
||||||
|
(Some(to), Some(from)) if to < from => return future::ok(Vec::new()).boxed(),
|
||||||
|
(Some(_), Some(_)) => {},
|
||||||
|
_ => return future::err(errors::unknown_block()).boxed(),
|
||||||
|
}
|
||||||
|
|
||||||
|
let maybe_future = self.sync.with_context(move |ctx| {
|
||||||
|
// find all headers which match the filter, and fetch the receipts for each one.
|
||||||
|
// match them with their numbers for easy sorting later.
|
||||||
|
let bit_combos = filter.bloom_possibilities();
|
||||||
|
let receipts_futures: Vec<_> = self.client.ancestry_iter(filter.to_block)
|
||||||
|
.take_while(|ref hdr| BlockId::Number(hdr.number()) != filter.from_block)
|
||||||
|
.take_while(|ref hdr| BlockId::Hash(hdr.hash()) != filter.from_block)
|
||||||
|
.filter(|ref hdr| {
|
||||||
|
let hdr_bloom = hdr.log_bloom();
|
||||||
|
bit_combos.iter().find(|&bloom| hdr_bloom & *bloom == *bloom).is_some()
|
||||||
|
})
|
||||||
|
.map(|hdr| (hdr.number(), request::BlockReceipts(hdr)))
|
||||||
|
.map(|(num, req)| self.on_demand.block_receipts(ctx, req).map(move |x| (num, x)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// as the receipts come in, find logs within them which match the filter.
|
||||||
|
// insert them into a BTreeMap to maintain order by number and block index.
|
||||||
|
stream::futures_unordered(receipts_futures)
|
||||||
|
.fold(BTreeMap::new(), move |mut matches, (num, receipts)| {
|
||||||
|
for (block_index, log) in receipts.into_iter().flat_map(|r| r.logs).enumerate() {
|
||||||
|
if filter.matches(&log) {
|
||||||
|
matches.insert((num, block_index), log.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
future::ok(matches)
|
||||||
|
}) // and then collect them into a vector.
|
||||||
|
.map(|matches| matches.into_iter().map(|(_, v)| v).collect())
|
||||||
|
.map_err(errors::on_demand_cancel)
|
||||||
|
});
|
||||||
|
|
||||||
|
match maybe_future {
|
||||||
|
Some(fut) => fut.boxed(),
|
||||||
|
None => future::err(errors::network_disabled()).boxed(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pending_logs(&self, _block_number: u64, _filter: &EthcoreFilter) -> Vec<Log> {
|
||||||
|
Vec::new() // light clients don't mine.
|
||||||
|
}
|
||||||
|
|
||||||
|
fn polls(&self) -> &Mutex<PollManager<PollFilter>> {
|
||||||
|
&self.polls
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
//! Account management (personal) rpc implementation
|
//! Account management (personal) rpc implementation
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use util::{Address};
|
use util::Address;
|
||||||
|
|
||||||
use ethkey::{Brain, Generator, Secret};
|
use ethkey::{Brain, Generator, Secret};
|
||||||
use ethstore::KeyFile;
|
use ethstore::KeyFile;
|
||||||
@ -27,7 +27,7 @@ use jsonrpc_core::Error;
|
|||||||
use v1::helpers::errors;
|
use v1::helpers::errors;
|
||||||
use v1::helpers::accounts::unwrap_provider;
|
use v1::helpers::accounts::unwrap_provider;
|
||||||
use v1::traits::ParityAccounts;
|
use v1::traits::ParityAccounts;
|
||||||
use v1::types::{H160 as RpcH160, H256 as RpcH256, DappId, Derive, DeriveHierarchical, DeriveHash};
|
use v1::types::{H160 as RpcH160, H256 as RpcH256, H520 as RpcH520, DappId, Derive, DeriveHierarchical, DeriveHash};
|
||||||
|
|
||||||
/// Account management (personal) rpc implementation.
|
/// Account management (personal) rpc implementation.
|
||||||
pub struct ParityAccountsClient {
|
pub struct ParityAccountsClient {
|
||||||
@ -334,6 +334,17 @@ impl ParityAccounts for ParityAccountsClient {
|
|||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
.map_err(|e| errors::account("Could not export account.", e))
|
.map_err(|e| errors::account("Could not export account.", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sign_message(&self, addr: RpcH160, password: String, message: RpcH256) -> Result<RpcH520, Error> {
|
||||||
|
self.account_provider()?
|
||||||
|
.sign(
|
||||||
|
addr.into(),
|
||||||
|
Some(password),
|
||||||
|
message.into()
|
||||||
|
)
|
||||||
|
.map(Into::into)
|
||||||
|
.map_err(|e| errors::account("Could not sign message.", e))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_vec<A, B>(a: Vec<A>) -> Vec<B> where
|
fn into_vec<A, B>(a: Vec<A>) -> Vec<B> where
|
||||||
|
@ -140,7 +140,7 @@ impl<D: Dispatcher + 'static> ParitySigning for SigningQueueClient<D> {
|
|||||||
fn post_sign(&self, meta: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcEither<RpcU256, RpcConfirmationResponse>, Error> {
|
fn post_sign(&self, meta: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcEither<RpcU256, RpcConfirmationResponse>, Error> {
|
||||||
let pending = self.pending.clone();
|
let pending = self.pending.clone();
|
||||||
self.dispatch(
|
self.dispatch(
|
||||||
RpcConfirmationPayload::Signature((address.clone(), data).into()),
|
RpcConfirmationPayload::EthSignMessage((address.clone(), data).into()),
|
||||||
DefaultAccount::Provided(address.into()),
|
DefaultAccount::Provided(address.into()),
|
||||||
meta.origin
|
meta.origin
|
||||||
).map(move |result| match result {
|
).map(move |result| match result {
|
||||||
@ -216,7 +216,7 @@ impl<D: Dispatcher + 'static> EthSigning for SigningQueueClient<D> {
|
|||||||
|
|
||||||
fn sign(&self, meta: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcH520, Error> {
|
fn sign(&self, meta: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcH520, Error> {
|
||||||
let res = self.dispatch(
|
let res = self.dispatch(
|
||||||
RpcConfirmationPayload::Signature((address.clone(), data).into()),
|
RpcConfirmationPayload::EthSignMessage((address.clone(), data).into()),
|
||||||
address.into(),
|
address.into(),
|
||||||
meta.origin,
|
meta.origin,
|
||||||
);
|
);
|
||||||
|
@ -78,7 +78,7 @@ impl<D: Dispatcher + 'static> EthSigning for SigningUnsafeClient<D>
|
|||||||
type Metadata = Metadata;
|
type Metadata = Metadata;
|
||||||
|
|
||||||
fn sign(&self, _: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcH520, Error> {
|
fn sign(&self, _: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture<RpcH520, Error> {
|
||||||
self.handle(RpcConfirmationPayload::Signature((address.clone(), data).into()), address.into())
|
self.handle(RpcConfirmationPayload::EthSignMessage((address.clone(), data).into()), address.into())
|
||||||
.then(|res| match res {
|
.then(|res| match res {
|
||||||
Ok(RpcConfirmationResponse::Signature(signature)) => Ok(signature),
|
Ok(RpcConfirmationResponse::Signature(signature)) => Ok(signature),
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
|
@ -90,7 +90,7 @@ impl EthTester {
|
|||||||
let hashrates = Arc::new(Mutex::new(HashMap::new()));
|
let hashrates = Arc::new(Mutex::new(HashMap::new()));
|
||||||
let external_miner = Arc::new(ExternalMiner::new(hashrates.clone()));
|
let external_miner = Arc::new(ExternalMiner::new(hashrates.clone()));
|
||||||
let eth = EthClient::new(&client, &snapshot, &sync, &opt_ap, &miner, &external_miner, options).to_delegate();
|
let eth = EthClient::new(&client, &snapshot, &sync, &opt_ap, &miner, &external_miner, options).to_delegate();
|
||||||
let filter = EthFilterClient::new(&client, &miner).to_delegate();
|
let filter = EthFilterClient::new(client.clone(), miner.clone()).to_delegate();
|
||||||
|
|
||||||
let dispatcher = FullDispatcher::new(Arc::downgrade(&client), Arc::downgrade(&miner));
|
let dispatcher = FullDispatcher::new(Arc::downgrade(&client), Arc::downgrade(&miner));
|
||||||
let sign = SigningUnsafeClient::new(&opt_ap, dispatcher).to_delegate();
|
let sign = SigningUnsafeClient::new(&opt_ap, dispatcher).to_delegate();
|
||||||
|
@ -500,3 +500,20 @@ fn should_export_account() {
|
|||||||
println!("Response: {:?}", response);
|
println!("Response: {:?}", response);
|
||||||
assert_eq!(result, Some(response.into()));
|
assert_eq!(result, Some(response.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_sign_message() {
|
||||||
|
let tester = setup();
|
||||||
|
let hash = tester.accounts
|
||||||
|
.insert_account(
|
||||||
|
"0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a".parse().unwrap(),
|
||||||
|
"password1")
|
||||||
|
.expect("account should be inserted ok");
|
||||||
|
|
||||||
|
assert_eq!(hash, "c171033d5cbff7175f29dfd3a63dda3d6f8f385e".parse().unwrap());
|
||||||
|
|
||||||
|
let request = r#"{"jsonrpc": "2.0", "method": "parity_signMessage", "params": ["0xc171033d5cbff7175f29dfd3a63dda3d6f8f385e", "password1", "0xbc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a"], "id": 3}"#;
|
||||||
|
let response = r#"{"jsonrpc":"2.0","result":"0x1d9e33a8cf8bfc089a172bca01da462f9e359c6cb1b0f29398bc884e4d18df4f78588aee4fb5cc067ca62d2abab995e0bba29527be6ac98105b0320020a2efaf00","id":3}"#;
|
||||||
|
let res = tester.io.handle_request_sync(&request);
|
||||||
|
assert_eq!(res, Some(response.into()));
|
||||||
|
}
|
||||||
|
@ -90,7 +90,7 @@ fn should_return_list_of_items_to_confirm() {
|
|||||||
nonce: None,
|
nonce: None,
|
||||||
condition: None,
|
condition: None,
|
||||||
}), Origin::Dapps("http://parity.io".into())).unwrap();
|
}), Origin::Dapps("http://parity.io".into())).unwrap();
|
||||||
tester.signer.add_request(ConfirmationPayload::Signature(1.into(), vec![5].into()), Origin::Unknown).unwrap();
|
tester.signer.add_request(ConfirmationPayload::EthSignMessage(1.into(), vec![5].into()), Origin::Unknown).unwrap();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let request = r#"{"jsonrpc":"2.0","method":"signer_requestsToConfirm","params":[],"id":1}"#;
|
let request = r#"{"jsonrpc":"2.0","method":"signer_requestsToConfirm","params":[],"id":1}"#;
|
||||||
@ -163,7 +163,7 @@ fn should_not_remove_transaction_if_password_is_invalid() {
|
|||||||
fn should_not_remove_sign_if_password_is_invalid() {
|
fn should_not_remove_sign_if_password_is_invalid() {
|
||||||
// given
|
// given
|
||||||
let tester = signer_tester();
|
let tester = signer_tester();
|
||||||
tester.signer.add_request(ConfirmationPayload::Signature(0.into(), vec![5].into()), Origin::Unknown).unwrap();
|
tester.signer.add_request(ConfirmationPayload::EthSignMessage(0.into(), vec![5].into()), Origin::Unknown).unwrap();
|
||||||
assert_eq!(tester.signer.requests().len(), 1);
|
assert_eq!(tester.signer.requests().len(), 1);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
|
@ -162,8 +162,8 @@ build_rpc_trait! {
|
|||||||
fn compile_serpent(&self, String) -> Result<Bytes, Error>;
|
fn compile_serpent(&self, String) -> Result<Bytes, Error>;
|
||||||
|
|
||||||
/// Returns logs matching given filter object.
|
/// Returns logs matching given filter object.
|
||||||
#[rpc(name = "eth_getLogs")]
|
#[rpc(async, name = "eth_getLogs")]
|
||||||
fn logs(&self, Filter) -> Result<Vec<Log>, Error>;
|
fn logs(&self, Filter) -> BoxFuture<Vec<Log>, Error>;
|
||||||
|
|
||||||
/// Returns the hash of the current block, the seedHash, and the boundary condition to be met.
|
/// Returns the hash of the current block, the seedHash, and the boundary condition to be met.
|
||||||
#[rpc(name = "eth_getWork")]
|
#[rpc(name = "eth_getWork")]
|
||||||
@ -196,12 +196,12 @@ build_rpc_trait! {
|
|||||||
fn new_pending_transaction_filter(&self) -> Result<U256, Error>;
|
fn new_pending_transaction_filter(&self) -> Result<U256, Error>;
|
||||||
|
|
||||||
/// Returns filter changes since last poll.
|
/// Returns filter changes since last poll.
|
||||||
#[rpc(name = "eth_getFilterChanges")]
|
#[rpc(async, name = "eth_getFilterChanges")]
|
||||||
fn filter_changes(&self, Index) -> Result<FilterChanges, Error>;
|
fn filter_changes(&self, Index) -> BoxFuture<FilterChanges, Error>;
|
||||||
|
|
||||||
/// Returns all logs matching given filter (in a range 'from' - 'to').
|
/// Returns all logs matching given filter (in a range 'from' - 'to').
|
||||||
#[rpc(name = "eth_getFilterLogs")]
|
#[rpc(async, name = "eth_getFilterLogs")]
|
||||||
fn filter_logs(&self, Index) -> Result<Vec<Log>, Error>;
|
fn filter_logs(&self, Index) -> BoxFuture<Vec<Log>, Error>;
|
||||||
|
|
||||||
/// Uninstalls filter.
|
/// Uninstalls filter.
|
||||||
#[rpc(name = "eth_uninstallFilter")]
|
#[rpc(name = "eth_uninstallFilter")]
|
||||||
|
@ -19,7 +19,7 @@ use std::collections::BTreeMap;
|
|||||||
|
|
||||||
use jsonrpc_core::Error;
|
use jsonrpc_core::Error;
|
||||||
use ethstore::KeyFile;
|
use ethstore::KeyFile;
|
||||||
use v1::types::{H160, H256, DappId, DeriveHash, DeriveHierarchical};
|
use v1::types::{H160, H256, H520, DappId, DeriveHash, DeriveHierarchical};
|
||||||
|
|
||||||
build_rpc_trait! {
|
build_rpc_trait! {
|
||||||
/// Personal Parity rpc interface.
|
/// Personal Parity rpc interface.
|
||||||
@ -180,5 +180,9 @@ build_rpc_trait! {
|
|||||||
/// Exports an account with given address if provided password matches.
|
/// Exports an account with given address if provided password matches.
|
||||||
#[rpc(name = "parity_exportAccount")]
|
#[rpc(name = "parity_exportAccount")]
|
||||||
fn export_account(&self, H160, String) -> Result<KeyFile, Error>;
|
fn export_account(&self, H160, String) -> Result<KeyFile, Error>;
|
||||||
|
|
||||||
|
/// Sign raw hash with the key corresponding to address and password.
|
||||||
|
#[rpc(name = "parity_signMessage")]
|
||||||
|
fn sign_message(&self, H160, String, H256) -> Result<H520, Error>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ impl fmt::Display for ConfirmationPayload {
|
|||||||
match *self {
|
match *self {
|
||||||
ConfirmationPayload::SendTransaction(ref transaction) => write!(f, "{}", transaction),
|
ConfirmationPayload::SendTransaction(ref transaction) => write!(f, "{}", transaction),
|
||||||
ConfirmationPayload::SignTransaction(ref transaction) => write!(f, "(Sign only) {}", transaction),
|
ConfirmationPayload::SignTransaction(ref transaction) => write!(f, "(Sign only) {}", transaction),
|
||||||
ConfirmationPayload::Signature(ref sign) => write!(f, "{}", sign),
|
ConfirmationPayload::EthSignMessage(ref sign) => write!(f, "{}", sign),
|
||||||
ConfirmationPayload::Decrypt(ref decrypt) => write!(f, "{}", decrypt),
|
ConfirmationPayload::Decrypt(ref decrypt) => write!(f, "{}", decrypt),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -169,7 +169,7 @@ pub enum ConfirmationPayload {
|
|||||||
SignTransaction(TransactionRequest),
|
SignTransaction(TransactionRequest),
|
||||||
/// Signature
|
/// Signature
|
||||||
#[serde(rename="sign")]
|
#[serde(rename="sign")]
|
||||||
Signature(SignRequest),
|
EthSignMessage(SignRequest),
|
||||||
/// Decryption
|
/// Decryption
|
||||||
#[serde(rename="decrypt")]
|
#[serde(rename="decrypt")]
|
||||||
Decrypt(DecryptRequest),
|
Decrypt(DecryptRequest),
|
||||||
@ -180,7 +180,7 @@ impl From<helpers::ConfirmationPayload> for ConfirmationPayload {
|
|||||||
match c {
|
match c {
|
||||||
helpers::ConfirmationPayload::SendTransaction(t) => ConfirmationPayload::SendTransaction(t.into()),
|
helpers::ConfirmationPayload::SendTransaction(t) => ConfirmationPayload::SendTransaction(t.into()),
|
||||||
helpers::ConfirmationPayload::SignTransaction(t) => ConfirmationPayload::SignTransaction(t.into()),
|
helpers::ConfirmationPayload::SignTransaction(t) => ConfirmationPayload::SignTransaction(t.into()),
|
||||||
helpers::ConfirmationPayload::Signature(address, data) => ConfirmationPayload::Signature(SignRequest {
|
helpers::ConfirmationPayload::EthSignMessage(address, data) => ConfirmationPayload::EthSignMessage(SignRequest {
|
||||||
address: address.into(),
|
address: address.into(),
|
||||||
data: data.into(),
|
data: data.into(),
|
||||||
}),
|
}),
|
||||||
@ -255,7 +255,7 @@ mod tests {
|
|||||||
// given
|
// given
|
||||||
let request = helpers::ConfirmationRequest {
|
let request = helpers::ConfirmationRequest {
|
||||||
id: 15.into(),
|
id: 15.into(),
|
||||||
payload: helpers::ConfirmationPayload::Signature(1.into(), vec![5].into()),
|
payload: helpers::ConfirmationPayload::EthSignMessage(1.into(), vec![5].into()),
|
||||||
origin: Origin::Rpc("test service".into()),
|
origin: Origin::Rpc("test service".into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2132,7 +2132,7 @@ impl ChainSync {
|
|||||||
let queue_info = io.chain().queue_info();
|
let queue_info = io.chain().queue_info();
|
||||||
let is_syncing = self.status().is_syncing(queue_info);
|
let is_syncing = self.status().is_syncing(queue_info);
|
||||||
|
|
||||||
if !is_syncing || !sealed.is_empty() {
|
if !is_syncing || !sealed.is_empty() || !proposed.is_empty() {
|
||||||
trace!(target: "sync", "Propagating blocks, state={:?}", self.state);
|
trace!(target: "sync", "Propagating blocks, state={:?}", self.state);
|
||||||
self.propagate_latest_blocks(io, sealed);
|
self.propagate_latest_blocks(io, sealed);
|
||||||
self.propagate_proposed_blocks(io, proposed);
|
self.propagate_proposed_blocks(io, proposed);
|
||||||
|
@ -32,6 +32,9 @@ use light::provider::LightProvider;
|
|||||||
use network::{NodeId, PeerId};
|
use network::{NodeId, PeerId};
|
||||||
use util::RwLock;
|
use util::RwLock;
|
||||||
|
|
||||||
|
use time::Duration;
|
||||||
|
use light::cache::Cache;
|
||||||
|
|
||||||
const NETWORK_ID: u64 = 0xcafebabe;
|
const NETWORK_ID: u64 = 0xcafebabe;
|
||||||
|
|
||||||
struct TestIoContext<'a> {
|
struct TestIoContext<'a> {
|
||||||
@ -207,7 +210,8 @@ impl TestNet<Peer> {
|
|||||||
pub fn light(n_light: usize, n_full: usize) -> Self {
|
pub fn light(n_light: usize, n_full: usize) -> Self {
|
||||||
let mut peers = Vec::with_capacity(n_light + n_full);
|
let mut peers = Vec::with_capacity(n_light + n_full);
|
||||||
for _ in 0..n_light {
|
for _ in 0..n_light {
|
||||||
let client = LightClient::in_memory(Default::default(), &Spec::new_test(), IoChannel::disconnected());
|
let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::hours(6))));
|
||||||
|
let client = LightClient::in_memory(Default::default(), &Spec::new_test(), IoChannel::disconnected(), cache);
|
||||||
peers.push(Arc::new(Peer::new_light(Arc::new(client))))
|
peers.push(Arc::new(Peer::new_light(Arc::new(client))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,8 +196,8 @@ fn tendermint() {
|
|||||||
// Propose
|
// Propose
|
||||||
net.peer(0).chain.engine().step();
|
net.peer(0).chain.engine().step();
|
||||||
net.peer(1).chain.engine().step();
|
net.peer(1).chain.engine().step();
|
||||||
net.peer(0).chain.miner().import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 2.into())).unwrap();
|
net.peer(0).chain.miner().import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 2.into())).unwrap();
|
||||||
net.peer(1).chain.miner().import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 2.into())).unwrap();
|
net.peer(1).chain.miner().import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 2.into())).unwrap();
|
||||||
// Send different prevotes
|
// Send different prevotes
|
||||||
net.sync();
|
net.sync();
|
||||||
// Prevote timeout
|
// Prevote timeout
|
||||||
|
@ -277,31 +277,32 @@ impl TestNet<EthPeer<EthcoreClient>> {
|
|||||||
started: false,
|
started: false,
|
||||||
disconnect_events: Vec::new(),
|
disconnect_events: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
for _ in 0..n {
|
for _ in 0..n {
|
||||||
let spec = spec_factory();
|
net.add_peer(config.clone(), spec_factory(), accounts.clone());
|
||||||
let client = EthcoreClient::new(
|
|
||||||
ClientConfig::default(),
|
|
||||||
&spec,
|
|
||||||
Arc::new(::util::kvdb::in_memory(::ethcore::db::NUM_COLUMNS.unwrap_or(0))),
|
|
||||||
Arc::new(Miner::with_spec_and_accounts(&spec, accounts.clone())),
|
|
||||||
IoChannel::disconnected(),
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
let ss = Arc::new(TestSnapshotService::new());
|
|
||||||
let sync = ChainSync::new(config.clone(), &*client);
|
|
||||||
let peer = Arc::new(EthPeer {
|
|
||||||
sync: RwLock::new(sync),
|
|
||||||
snapshot_service: ss,
|
|
||||||
chain: client,
|
|
||||||
queue: RwLock::new(VecDeque::new()),
|
|
||||||
});
|
|
||||||
peer.chain.add_notify(peer.clone());
|
|
||||||
net.peers.push(peer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
net
|
net
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_peer(&mut self, config: SyncConfig, spec: Spec, accounts: Option<Arc<AccountProvider>>) {
|
||||||
|
let client = EthcoreClient::new(
|
||||||
|
ClientConfig::default(),
|
||||||
|
&spec,
|
||||||
|
Arc::new(::util::kvdb::in_memory(::ethcore::db::NUM_COLUMNS.unwrap_or(0))),
|
||||||
|
Arc::new(Miner::with_spec_and_accounts(&spec, accounts)),
|
||||||
|
IoChannel::disconnected(),
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
let ss = Arc::new(TestSnapshotService::new());
|
||||||
|
let sync = ChainSync::new(config, &*client);
|
||||||
|
let peer = Arc::new(EthPeer {
|
||||||
|
sync: RwLock::new(sync),
|
||||||
|
snapshot_service: ss,
|
||||||
|
chain: client,
|
||||||
|
queue: RwLock::new(VecDeque::new()),
|
||||||
|
});
|
||||||
|
peer.chain.add_notify(peer.clone());
|
||||||
|
self.peers.push(peer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> TestNet<P> where P: Peer {
|
impl<P> TestNet<P> where P: Peer {
|
||||||
|
Loading…
Reference in New Issue
Block a user