openethereum/secret_store/src/listener/service_contract.rs

357 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 parking_lot::RwLock;
use ethcore::filter::Filter;
use ethcore::client::{Client, BlockChainClient, BlockId};
use ethkey::{Public, Signature, public_to_address};
use hash::keccak;
use ethereum_types::{H256, U256, Address};
use listener::service_contract_listener::ServiceTask;
use trusted_client::TrustedClient;
use {ServerKeyId, NodeKeyPair, ContractAddress};
use_contract!(service, "Service", "res/service.json");
/// 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<ServiceData>,
}
/// On-chain service contract data.
struct ServiceData {
/// Contract.
pub contract: service::Service,
/// Contract address.
pub contract_address: Address,
/// 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: service::Service,
/// Contract address.
contract_address: Address,
/// 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(), BlockId::Latest)
.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(ServiceData {
contract: service::Service::default(),
contract_address: 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(), BlockId::Latest).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_address = 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;
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 contract_address = data.contract_address;
let do_call = |data| client.call_contract(BlockId::Hash(b), contract_address, data);
data.contract.functions().server_key_generation_requests_count().call(&do_call)
.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: service::Service::default(),
contract_address: data.contract_address,
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 contract_address = data.contract_address;
let do_call = |data| client.call_contract(BlockId::Latest, contract_address, data);
let self_address = public_to_address(self.self_key_pair.public());
if data.contract.functions()
.get_server_key_confirmation_status()
.call(*server_key_id, self_address, &do_call)
.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.functions()
.server_key_generated()
.input(*server_key_id,
server_key.to_vec(),
signed_server_key.v(),
signed_server_key.r(),
signed_server_key.s(),
);
// send transaction
client.transact_contract(
data.contract_address,
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 contract_address = self.contract_address;
let do_call = |data| self.client.call_contract(BlockId::Hash(self.block.clone()), contract_address, data);
self.contract.functions().get_server_key_id().call(index, &do_call)
.and_then(|server_key_id|
self.contract.functions().get_server_key_threshold().call(server_key_id, &do_call)
.map(|threshold| (server_key_id, threshold)))
.and_then(|(server_key_id, threshold)|
self.contract.functions().get_server_key_confirmation_status().call(server_key_id, self_address, &do_call)
.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 ethereum_types::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(())
}
}
}