344 lines
12 KiB
Rust
344 lines
12 KiB
Rust
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
|
// This file is part of Parity.
|
|
|
|
// Parity is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
|
|
// Parity is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
use std::sync::Arc;
|
|
use futures::{future, Future};
|
|
use parking_lot::RwLock;
|
|
use ethcore::filter::Filter;
|
|
use ethcore::client::{Client, BlockChainClient, BlockId};
|
|
use ethkey::{Public, Signature, public_to_address};
|
|
use native_contracts::SecretStoreService;
|
|
use hash::keccak;
|
|
use bigint::hash::H256;
|
|
use bigint::prelude::U256;
|
|
use listener::service_contract_listener::ServiceTask;
|
|
use trusted_client::TrustedClient;
|
|
use {ServerKeyId, NodeKeyPair, ContractAddress};
|
|
|
|
/// Name of the SecretStore contract in the registry.
|
|
const SERVICE_CONTRACT_REGISTRY_NAME: &'static str = "secretstore_service";
|
|
|
|
/// Key server has been added to the set.
|
|
const SERVER_KEY_REQUESTED_EVENT_NAME: &'static [u8] = &*b"ServerKeyRequested(bytes32,uint256)";
|
|
|
|
/// Number of confirmations required before request can be processed.
|
|
const REQUEST_CONFIRMATIONS_REQUIRED: u64 = 3;
|
|
|
|
lazy_static! {
|
|
static ref SERVER_KEY_REQUESTED_EVENT_NAME_HASH: H256 = keccak(SERVER_KEY_REQUESTED_EVENT_NAME);
|
|
}
|
|
|
|
/// Service contract trait.
|
|
pub trait ServiceContract: Send + Sync {
|
|
/// Update contract when new blocks are enacted. Returns true if contract is installed && up-to-date (i.e. chain is synced).
|
|
fn update(&self) -> bool;
|
|
/// Read recent contract logs. Returns topics of every entry.
|
|
fn read_logs(&self) -> Box<Iterator<Item=Vec<H256>>>;
|
|
/// Publish generated key.
|
|
fn read_pending_requests(&self) -> Box<Iterator<Item=(bool, ServiceTask)>>;
|
|
/// Publish server key.
|
|
fn publish_server_key(&self, server_key_id: &ServerKeyId, server_key: &Public) -> Result<(), String>;
|
|
}
|
|
|
|
/// On-chain service contract.
|
|
pub struct OnChainServiceContract {
|
|
/// Blockchain client.
|
|
client: TrustedClient,
|
|
/// This node key pair.
|
|
self_key_pair: Arc<NodeKeyPair>,
|
|
/// Contract addresss.
|
|
address: ContractAddress,
|
|
/// Contract.
|
|
data: RwLock<SecretStoreServiceData>,
|
|
}
|
|
|
|
/// On-chain service contract data.
|
|
struct SecretStoreServiceData {
|
|
/// Contract.
|
|
pub contract: Arc<SecretStoreService>,
|
|
/// Last block we have read logs from.
|
|
pub last_log_block: Option<H256>,
|
|
}
|
|
|
|
/// Pending requests iterator.
|
|
struct PendingRequestsIterator {
|
|
/// Blockchain client.
|
|
client: Arc<Client>,
|
|
/// Contract.
|
|
contract: Arc<SecretStoreService>,
|
|
/// This node key pair.
|
|
self_key_pair: Arc<NodeKeyPair>,
|
|
/// Block, this iterator is created for.
|
|
block: H256,
|
|
/// Current request index.
|
|
index: U256,
|
|
/// Requests length.
|
|
length: U256,
|
|
}
|
|
|
|
impl OnChainServiceContract {
|
|
/// Create new on-chain service contract.
|
|
pub fn new(client: TrustedClient, address: ContractAddress, self_key_pair: Arc<NodeKeyPair>) -> Self {
|
|
let contract_addr = match address {
|
|
ContractAddress::Registry => client.get().and_then(|c| c.registry_address(SERVICE_CONTRACT_REGISTRY_NAME.to_owned())
|
|
.map(|address| {
|
|
trace!(target: "secretstore", "{}: installing service contract from address {}",
|
|
self_key_pair.public(), address);
|
|
address
|
|
}))
|
|
.unwrap_or_default(),
|
|
ContractAddress::Address(ref address) => {
|
|
trace!(target: "secretstore", "{}: installing service contract from address {}",
|
|
self_key_pair.public(), address);
|
|
address.clone()
|
|
},
|
|
};
|
|
|
|
OnChainServiceContract {
|
|
client: client,
|
|
self_key_pair: self_key_pair,
|
|
address: address,
|
|
data: RwLock::new(SecretStoreServiceData {
|
|
contract: Arc::new(SecretStoreService::new(contract_addr)),
|
|
last_log_block: None,
|
|
}),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ServiceContract for OnChainServiceContract {
|
|
fn update(&self) -> bool {
|
|
// TODO [Sec]: registry_address currently reads from BlockId::Latest, instead of
|
|
// from block with REQUEST_CONFIRMATIONS_REQUIRED confirmations
|
|
if let &ContractAddress::Registry = &self.address {
|
|
if let Some(client) = self.client.get() {
|
|
// update contract address from registry
|
|
let service_contract_addr = client.registry_address(SERVICE_CONTRACT_REGISTRY_NAME.to_owned()).unwrap_or_default();
|
|
if self.data.read().contract.address != service_contract_addr {
|
|
trace!(target: "secretstore", "{}: installing service contract from address {}",
|
|
self.self_key_pair.public(), service_contract_addr);
|
|
self.data.write().contract = Arc::new(SecretStoreService::new(service_contract_addr));
|
|
}
|
|
}
|
|
}
|
|
|
|
self.data.read().contract.address != Default::default()
|
|
&& self.client.get().is_some()
|
|
}
|
|
|
|
fn read_logs(&self) -> Box<Iterator<Item=Vec<H256>>> {
|
|
let client = match self.client.get() {
|
|
Some(client) => client,
|
|
None => {
|
|
warn!(target: "secretstore", "{}: client is offline during read_logs call",
|
|
self.self_key_pair.public());
|
|
return Box::new(::std::iter::empty());
|
|
},
|
|
};
|
|
|
|
// prepare range of blocks to read logs from
|
|
let (address, first_block, last_block) = {
|
|
let mut data = self.data.write();
|
|
let address = data.contract.address.clone();
|
|
let confirmed_block = match get_confirmed_block_hash(&*client, REQUEST_CONFIRMATIONS_REQUIRED) {
|
|
Some(confirmed_block) => confirmed_block,
|
|
None => return Box::new(::std::iter::empty()), // no block with enough confirmations
|
|
};
|
|
let first_block = match data.last_log_block.take().and_then(|b| client.tree_route(&b, &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(),
|
|
};
|
|
|
|
data.last_log_block = Some(confirmed_block.clone());
|
|
(address, first_block, confirmed_block)
|
|
};
|
|
|
|
// read server key generation requests
|
|
let request_logs = client.logs(Filter {
|
|
from_block: BlockId::Hash(first_block),
|
|
to_block: BlockId::Hash(last_block),
|
|
address: Some(vec![address]),
|
|
topics: vec![
|
|
Some(vec![*SERVER_KEY_REQUESTED_EVENT_NAME_HASH]),
|
|
None,
|
|
None,
|
|
None,
|
|
],
|
|
limit: None,
|
|
});
|
|
|
|
Box::new(request_logs.into_iter().map(|log| log.entry.topics))
|
|
}
|
|
|
|
fn read_pending_requests(&self) -> Box<Iterator<Item=(bool, ServiceTask)>> {
|
|
let client = match self.client.get() {
|
|
Some(client) => client,
|
|
None => return Box::new(::std::iter::empty()),
|
|
};
|
|
|
|
// we only need requests that are here for more than REQUEST_CONFIRMATIONS_REQUIRED blocks
|
|
// => we're reading from Latest - (REQUEST_CONFIRMATIONS_REQUIRED + 1) block
|
|
let data = self.data.read();
|
|
match data.contract.address == Default::default() {
|
|
true => Box::new(::std::iter::empty()),
|
|
false => get_confirmed_block_hash(&*client, REQUEST_CONFIRMATIONS_REQUIRED + 1)
|
|
.and_then(|b| {
|
|
let do_call = |a, d| future::done(client.call_contract(BlockId::Hash(b.clone()), a, d));
|
|
data.contract.server_key_generation_requests_count(&do_call).wait()
|
|
.map_err(|error| {
|
|
warn!(target: "secretstore", "{}: call to server_key_generation_requests_count failed: {}",
|
|
self.self_key_pair.public(), error);
|
|
error
|
|
})
|
|
.map(|l| (b, l))
|
|
.ok()
|
|
})
|
|
.map(|(b, l)| Box::new(PendingRequestsIterator {
|
|
client: client,
|
|
contract: data.contract.clone(),
|
|
self_key_pair: self.self_key_pair.clone(),
|
|
block: b,
|
|
index: 0.into(),
|
|
length: l,
|
|
}) as Box<Iterator<Item=(bool, ServiceTask)>>)
|
|
.unwrap_or_else(|| Box::new(::std::iter::empty()))
|
|
}
|
|
}
|
|
|
|
fn publish_server_key(&self, server_key_id: &ServerKeyId, server_key: &Public) -> Result<(), String> {
|
|
// only publish if contract address is set && client is online
|
|
let data = self.data.read();
|
|
if data.contract.address == Default::default() {
|
|
// it is not an error, because key could be generated even without contract
|
|
return Ok(());
|
|
}
|
|
|
|
let client = match self.client.get() {
|
|
Some(client) => client,
|
|
None => return Err("trusted client is required to publish key".into()),
|
|
};
|
|
|
|
// only publish key if contract waits for publication
|
|
// failing is ok here - it could be that enough confirmations have been recevied
|
|
// or key has been requested using HTTP API
|
|
let do_call = |a, d| future::done(client.call_contract(BlockId::Latest, a, d));
|
|
let self_address = public_to_address(self.self_key_pair.public());
|
|
if data.contract.get_server_key_confirmation_status(&do_call, server_key_id.clone(), self_address).wait().unwrap_or(false) {
|
|
return Ok(());
|
|
}
|
|
|
|
// prepare transaction data
|
|
let server_key_hash = keccak(server_key);
|
|
let signed_server_key = self.self_key_pair.sign(&server_key_hash).map_err(|e| format!("{}", e))?;
|
|
let signed_server_key: Signature = signed_server_key.into_electrum().into();
|
|
let transaction_data = data.contract.encode_server_key_generated_input(server_key_id.clone(),
|
|
server_key.to_vec(),
|
|
signed_server_key.v(),
|
|
signed_server_key.r().into(),
|
|
signed_server_key.s().into()
|
|
)?;
|
|
|
|
// send transaction
|
|
client.transact_contract(
|
|
data.contract.address.clone(),
|
|
transaction_data
|
|
).map_err(|e| format!("{}", e))?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Iterator for PendingRequestsIterator {
|
|
type Item = (bool, ServiceTask);
|
|
|
|
fn next(&mut self) -> Option<(bool, ServiceTask)> {
|
|
if self.index >= self.length {
|
|
return None;
|
|
}
|
|
|
|
let index = self.index.clone();
|
|
self.index = self.index + 1.into();
|
|
|
|
let self_address = public_to_address(self.self_key_pair.public());
|
|
let do_call = |a, d| future::done(self.client.call_contract(BlockId::Hash(self.block.clone()), a, d));
|
|
self.contract.get_server_key_id(&do_call, index).wait()
|
|
.and_then(|server_key_id|
|
|
self.contract.get_server_key_threshold(&do_call, server_key_id.clone()).wait()
|
|
.map(|threshold| (server_key_id, threshold)))
|
|
.and_then(|(server_key_id, threshold)|
|
|
self.contract.get_server_key_confirmation_status(&do_call, server_key_id.clone(), self_address).wait()
|
|
.map(|is_confirmed| (server_key_id, threshold, is_confirmed)))
|
|
.map(|(server_key_id, threshold, is_confirmed)|
|
|
Some((is_confirmed, ServiceTask::GenerateServerKey(server_key_id, threshold.into()))))
|
|
.map_err(|error| {
|
|
warn!(target: "secretstore", "{}: reading service contract request failed: {}",
|
|
self.self_key_pair.public(), error);
|
|
()
|
|
})
|
|
.unwrap_or(None)
|
|
}
|
|
}
|
|
|
|
/// Get hash of the last block with at least n confirmations.
|
|
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)))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub mod tests {
|
|
use parking_lot::Mutex;
|
|
use ethkey::Public;
|
|
use bigint::hash::H256;
|
|
use listener::service_contract_listener::ServiceTask;
|
|
use ServerKeyId;
|
|
use super::ServiceContract;
|
|
|
|
#[derive(Default)]
|
|
pub struct DummyServiceContract {
|
|
pub is_actual: bool,
|
|
pub logs: Vec<Vec<H256>>,
|
|
pub pending_requests: Vec<(bool, ServiceTask)>,
|
|
pub published_keys: Mutex<Vec<(ServerKeyId, Public)>>,
|
|
}
|
|
|
|
impl ServiceContract for DummyServiceContract {
|
|
fn update(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
fn read_logs(&self) -> Box<Iterator<Item=Vec<H256>>> {
|
|
Box::new(self.logs.clone().into_iter())
|
|
}
|
|
|
|
fn read_pending_requests(&self) -> Box<Iterator<Item=(bool, ServiceTask)>> {
|
|
Box::new(self.pending_requests.clone().into_iter())
|
|
}
|
|
|
|
fn publish_server_key(&self, server_key_id: &ServerKeyId, server_key: &Public) -> Result<(), String> {
|
|
self.published_keys.lock().push((server_key_id.clone(), server_key.clone()));
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|