use std::collections::HashMap;
use std::mem;
use std::sync::Arc;
use call_contract::CallContract;
use tetsy_registrar::RegistrarClient;
use types::ids::BlockId;
use types::transaction::SignedTransaction;
use vapabi::FunctionOutputDecoder;
use vapory_types::Address;
use parking_lot::RwLock;
use_contract!(service_transaction, "res/contracts/service_transaction.json");
const SERVICE_TRANSACTION_CONTRACT_REGISTRY_NAME: &'static str = "service_transaction_checker";
#[derive(Default, Clone)]
pub struct ServiceTransactionChecker {
certified_addresses_cache: Arc<RwLock<HashMap<Address, bool>>>
}
impl ServiceTransactionChecker {
pub fn check<C: CallContract + RegistrarClient>(
&self,
client: &C,
tx: &SignedTransaction
) -> Result<bool, String> {
if !tx.gas_price.is_zero() {
return Ok(false)
}
let sender = tx.sender();
self.check_address(client, sender)
}
pub fn check_address<C>(&self, client: &C, sender: Address) -> Result<bool, String>
where C: CallContract + RegistrarClient
{
trace!(target: "txqueue", "Checking service transaction checker contract from {}", sender);
if let Some(allowed) = self
.certified_addresses_cache
.try_read()
.as_ref()
.and_then(|c| c.get(&sender))
{
return Ok(*allowed);
}
let contract_address = match client.get_address(
SERVICE_TRANSACTION_CONTRACT_REGISTRY_NAME,
BlockId::Latest
) {
Ok(Some(addr)) => addr,
Ok(None) => return Err("contract is not configured".to_owned()),
Err(e) => return Err(e)
};
self.call_contract(client, contract_address, sender).and_then(|allowed| {
if let Some(mut cache) = self.certified_addresses_cache.try_write() {
cache.insert(sender, allowed);
};
Ok(allowed)
})
}
pub fn refresh_cache<C>(&self, client: &C) -> Result<bool, String>
where C: CallContract + RegistrarClient
{
trace!(target: "txqueue", "Refreshing certified addresses cache");
let cache = mem::replace(&mut *self.certified_addresses_cache.write(), HashMap::default());
if client.registrar_address().is_none() {
return Ok(false);
}
let contract_address_fetch = client.get_address(
SERVICE_TRANSACTION_CONTRACT_REGISTRY_NAME,
BlockId::Latest
)?;
if let Some(contract_address) = contract_address_fetch {
let addresses: Vec<_> = cache.keys().collect();
let mut cache: HashMap<Address, bool> = HashMap::default();
for address in addresses {
let allowed = self.call_contract(client, contract_address, *address)?;
cache.insert(*address, allowed);
}
mem::replace(&mut *self.certified_addresses_cache.write(), cache);
Ok(true)
} else {
Ok(false)
}
}
fn call_contract<C>(
&self,
client: &C,
contract_address: Address,
sender: Address
) -> Result<bool, String>
where C: CallContract + RegistrarClient
{
let (data, decoder) = service_transaction::functions::certified::call(sender);
let value = client.call_contract(BlockId::Latest, contract_address, data)?;
decoder.decode(&value).map_err(|e| e.to_string())
}
}