Skip to content

Commit

Permalink
Encapsulate access to the client for secret store (openethereum#11232)
Browse files Browse the repository at this point in the history
* Move all client usages into trusted_client

* Move confirmed hash method to trusted client

* Tree route and logs encapsuluted

* Remove not used method for keys sharing

* NodeKeyPair renamed and moved to trusted client

* Use public key error in trusted client

* Move contract address definition into trusted client

* Block id and number types from ethcore wrapped

* Trusted client renamed to more general Blockchain

* Trusted client implementation moved to parity code

* Move node key pair under secret store feature as well

* Registar crate removed from deps

* Accounts feature removed from secret store

* Fix after merge

* Blockchain renamed to SecretStoreChain

* Module documentations added
  • Loading branch information
grbIzl authored Jan 7, 2020
1 parent 5bd6b20 commit 424b38a
Show file tree
Hide file tree
Showing 31 changed files with 634 additions and 478 deletions.
9 changes: 2 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" }
dir = { path = "util/dir" }
docopt = "1.0"
engine = { path = "ethcore/engine" }
ethabi = { version = "9.0.1", optional = true }
ethcore = { path = "ethcore", features = ["parity"] }
ethcore-accounts = { path = "accounts", optional = true }
ethcore-blockchain = { path = "ethcore/blockchain" }
ethcore-call-contract = { path = "ethcore/call-contract", optional = true }
ethcore-db = { path = "ethcore/db" }
ethcore-io = { path = "util/io" }
ethcore-light = { path = "ethcore/light" }
Expand Down Expand Up @@ -97,7 +99,7 @@ test-heavy = ["ethcore/test-heavy"]
evm-debug = ["ethcore/evm-debug"]
evm-debug-tests = ["ethcore/evm-debug-tests"]
slow-blocks = ["ethcore/slow-blocks"]
secretstore = ["ethcore-secretstore", "ethcore-secretstore/accounts"]
secretstore = ["ethcore-secretstore", "accounts", "ethabi", "ethcore-call-contract"]
final = ["parity-version/final"]
deadlock_detection = ["parking_lot/deadlock_detection"]
# to create a memory profile (requires nightly rust), use e.g.
Expand Down
6 changes: 6 additions & 0 deletions parity/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ extern crate ethcore_accounts as accounts;
#[cfg(feature = "secretstore")]
extern crate ethcore_secretstore;

#[cfg(feature = "secretstore")]
extern crate ethabi;

#[cfg(feature = "secretstore")]
extern crate ethcore_call_contract as call_contract;

#[cfg(test)]
#[macro_use]
extern crate pretty_assertions;
Expand Down
246 changes: 246 additions & 0 deletions parity/secretstore/blockchain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
// This file is part of Parity Ethereum.

// Parity Ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Parity Ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.

//! SecretStoreChain implementation with information about blockchain, retrieved from the client
use std::sync::{Arc, Weak};
use ethereum_types::{H256, Address};
use parking_lot::RwLock;
use types::{
ids::BlockId as EthcoreBlockId,
transaction::{Transaction, SignedTransaction, Action},
chain_notify::NewBlocks,
tree_route::TreeRoute,
filter::Filter as BlockchainFilter,
log_entry::LocalizedLogEntry,
};
use ethcore::client::Client;
use bytes::Bytes;
use ethabi::RawLog;
use client_traits::BlockChainClient;
use call_contract::CallContract;
use client_traits::{ChainInfo, Nonce, ChainNotify};
use ethcore::miner::{Miner, MinerService};
use parity_crypto::publickey::Error as EthKeyError;
use sync::SyncProvider;
use registrar::RegistrarClient;
use ethcore_secretstore::{BlockId, BlockNumber, SecretStoreChain, NewBlocksNotify, SigningKeyPair, ContractAddress, Filter};

// TODO: Instead of a constant, make this based on consensus finality.
/// Number of confirmations required before request can be processed.
const REQUEST_CONFIRMATIONS_REQUIRED: u64 = 3;

fn into_ethcore_block_id(id: BlockId) -> EthcoreBlockId {
match id {
BlockId::Hash(hash) => EthcoreBlockId::Hash(hash),
BlockId::Number(number) => EthcoreBlockId::Number(number),
BlockId::Earliest => EthcoreBlockId::Earliest,
BlockId::Latest => EthcoreBlockId::Latest,
}
}

/// SecretStore blockchain implementation (client's wrapper)
/// This implementation is trusted, when underlying client is synced and chain's security level is full
/// This trust is guaranteed by return result in get_trusted method (if it's not trusted, None is returned)
pub struct TrustedClient {
/// This key server node key pair.
self_key_pair: Arc<dyn SigningKeyPair>,
/// Blockchain client.
client: Weak<Client>,
/// Sync provider.
sync: Weak<dyn SyncProvider>,
/// Miner service.
miner: Weak<Miner>,
/// Chain new blocks listeners
listeners: RwLock<Vec<Weak<dyn NewBlocksNotify>>>,
}

impl TrustedClient {
/// Create new trusted client.
pub fn new(self_key_pair: Arc<dyn SigningKeyPair>, client: Arc<Client>, sync: Arc<dyn SyncProvider>, miner: Arc<Miner>) -> Arc<Self> {
let trusted_client = Arc::new(TrustedClient {
self_key_pair,
client: Arc::downgrade(&client),
sync: Arc::downgrade(&sync),
miner: Arc::downgrade(&miner),
listeners: RwLock::default(),
});
client.add_notify(trusted_client.clone());
trusted_client
}

fn notify_listeners(&self, new_enacted_len: usize) {
for listener_pointer in self.listeners.read().iter() {
if let Some(listener) = listener_pointer.upgrade() {
listener.new_blocks(new_enacted_len);
}
}
}

/// Get 'trusted' `Client` reference only if it is synchronized && trusted.
fn get_trusted(&self) -> Option<Arc<Client>> {
self.client.upgrade()
.and_then(|client| self.sync.upgrade().map(|sync| (client, sync)))
.and_then(|(client, sync)| {
let is_synced = !sync.is_major_syncing();
let is_trusted = client.chain_info().security_level().is_full();
match is_synced && is_trusted {
true => Some(client),
false => None,
}
})
}

fn tree_route(&self, from: &H256, to: &H256) -> Option<TreeRoute> {
if let Some(client) = self.get_trusted() {
client.tree_route(from, to)
} else {
None
}
}

fn logs(&self, filter: BlockchainFilter) -> Option<Vec<LocalizedLogEntry>> {
if let Some(client) = self.get_trusted() {
client.logs(filter).ok()
} else {
None
}
}

}

impl SecretStoreChain for TrustedClient {
fn add_listener(&self, target: Arc<dyn NewBlocksNotify>) {
self.listeners.write().push(Arc::downgrade(&target));
}

fn is_trusted(&self) -> bool {
self.get_trusted().is_some()
}

fn transact_contract(&self, contract: Address, tx_data: Bytes) -> Result<(), EthKeyError> {
let client = self.client.upgrade().ok_or_else(|| EthKeyError::Custom("cannot submit tx when client is offline".into()))?;
let miner = self.miner.upgrade().ok_or_else(|| EthKeyError::Custom("cannot submit tx when miner is offline".into()))?;
let engine = client.engine();
let transaction = Transaction {
nonce: client.latest_nonce(&self.self_key_pair.address()),
action: Action::Call(contract),
gas: miner.authoring_params().gas_range_target.0,
gas_price: miner.sensible_gas_price(),
value: Default::default(),
data: tx_data,
};
let chain_id = engine.signing_chain_id(&client.latest_env_info());
let signature = self.self_key_pair.sign(&transaction.hash(chain_id))?;
let signed = SignedTransaction::new(transaction.with_signature(signature, chain_id))?;
miner.import_own_transaction(&*client, signed.into())
.map_err(|e| EthKeyError::Custom(format!("failed to import tx: {}", e)))
}

fn read_contract_address(
&self,
registry_name: &str,
address: &ContractAddress
) -> Option<Address> {
match *address {
ContractAddress::Address(ref address) => Some(address.clone()),
ContractAddress::Registry => self.get_trusted().and_then(|client|
self.get_confirmed_block_hash()
.and_then(|block| {
client.get_address(registry_name, EthcoreBlockId::Hash(block))
.unwrap_or(None)
})
),
}
}

fn call_contract(&self, block_id: BlockId, contract_address: Address, data: Bytes) -> Result<Bytes, String> {
if let Some(client) = self.get_trusted() {
client.call_contract(into_ethcore_block_id(block_id), contract_address, data)
} else {
Err("Calling ACL contract without trusted blockchain client".into())
}
}

fn block_hash(&self, id: BlockId) -> Option<H256> {
if let Some(client) = self.get_trusted() {
client.block_hash(into_ethcore_block_id(id))
} else {
None
}
}

fn block_number(&self, id: BlockId) -> Option<BlockNumber> {
if let Some(client) = self.get_trusted() {
client.block_number(into_ethcore_block_id(id))
} else {
None
}
}

fn retrieve_last_logs(&self, filter: Filter) -> Option<Vec<RawLog>> {
let confirmed_block = match self.get_confirmed_block_hash() {
Some(confirmed_block) => confirmed_block,
None => return None, // no block with enough confirmations
};

let from_block = self.block_hash(filter.from_block).unwrap_or_else(|| confirmed_block);
let first_block = match self.tree_route(&from_block, &confirmed_block) {
// if we have a route from last_log_block to confirmed_block => search for logs on this route
//
// potentially this could lead us to reading same logs twice when reorganizing to the fork, which
// already has been canonical previosuly
// the worst thing that can happen in this case is spending some time reading unneeded data from SS db
Some(ref route) if route.index < route.blocks.len() => route.blocks[route.index],
// else we care only about confirmed block
_ => confirmed_block.clone(),
};

self.logs(BlockchainFilter {
from_block: EthcoreBlockId::Hash(first_block),
to_block: EthcoreBlockId::Hash(confirmed_block),
address: filter.address,
topics: filter.topics,
limit: None,
})
.map(|blockchain_logs| {
blockchain_logs
.into_iter()
.map(|log| {
let raw_log: RawLog = (log.entry.topics.into_iter().map(|t| t.0.into()).collect(), log.entry.data).into();
raw_log
})
.collect::<Vec<_>>()
})
}

fn get_confirmed_block_hash(&self) -> Option<H256> {
self.block_number(BlockId::Latest)
.map(|b| b.saturating_sub(REQUEST_CONFIRMATIONS_REQUIRED))
.and_then(|b| self.block_hash(BlockId::Number(b)))
}
}

impl ChainNotify for TrustedClient {
fn new_blocks(&self, new_blocks: NewBlocks) {
if new_blocks.has_more_blocks_to_import { return }
if !new_blocks.route.enacted().is_empty() || !new_blocks.route.retracted().is_empty() {
let enacted_len = new_blocks.route.enacted().len();
self.notify_listeners(enacted_len);
}
}
}

30 changes: 15 additions & 15 deletions secret-store/src/helpers.rs → parity/secretstore/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,18 @@
// You should have received a copy of the GNU General Public License
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.

use ethcore::client::Client;
use client_traits::BlockChainClient;
use common_types::ids::BlockId;
use ethereum_types::H256;

// TODO: Instead of a constant, make this based on consensus finality.
/// Number of confirmations required before request can be processed.
pub const REQUEST_CONFIRMATIONS_REQUIRED: u64 = 3;

/// Get hash of the last block with at least n confirmations.
pub fn get_confirmed_block_hash(client: &Client, confirmations: u64) -> Option<H256> {
client.block_number(BlockId::Latest)
.map(|b| b.saturating_sub(confirmations))
.and_then(|b| client.block_hash(BlockId::Number(b)))
}
//! Secret store related components.
mod server;

#[cfg(feature = "secretstore")]
mod blockchain;

#[cfg(all(feature = "accounts", feature = "secretstore"))]
mod nodekeypair;

pub use self::server::{Configuration, NodeSecretKey, ContractAddress, Dependencies, start};
#[cfg(feature = "secretstore")]
use self::blockchain::TrustedClient;
#[cfg(all(feature = "accounts", feature = "secretstore"))]
use self::nodekeypair::KeyStoreNodeKeyPair;
Loading

0 comments on commit 424b38a

Please sign in to comment.