mod event;
use std::{collections::HashMap, convert::Infallible, fmt::Debug};
use semver::Version;
use tracing::{debug, error, warn};
use crate::{
components::{chainspec_loader::Chainspec, storage::Storage, Component},
effect::{
announcements::DeployAcceptorAnnouncement, requests::StorageRequest, EffectBuilder,
EffectExt, Effects,
},
small_network::NodeId,
types::{CryptoRngCore, Deploy},
utils::Source,
};
pub use event::Event;
use super::chainspec_loader::DeployConfig;
pub trait ReactorEventT:
From<Event> + From<DeployAcceptorAnnouncement<NodeId>> + From<StorageRequest<Storage>> + Send
{
}
impl<REv> ReactorEventT for REv where
REv: From<Event>
+ From<DeployAcceptorAnnouncement<NodeId>>
+ From<StorageRequest<Storage>>
+ Send
{
}
#[derive(Debug, Clone)]
pub struct DeployAcceptorConfig {
chain_name: String,
deploy_config: DeployConfig,
}
impl From<Chainspec> for DeployAcceptorConfig {
fn from(c: Chainspec) -> Self {
DeployAcceptorConfig {
chain_name: c.genesis.name,
deploy_config: c.genesis.deploy_config,
}
}
}
#[derive(Debug)]
pub(crate) struct DeployAcceptor {
cached_deploy_configs: HashMap<Version, DeployAcceptorConfig>,
}
impl DeployAcceptor {
pub(crate) fn new() -> Self {
DeployAcceptor {
cached_deploy_configs: HashMap::new(),
}
}
fn accept<REv: ReactorEventT>(
&mut self,
effect_builder: EffectBuilder<REv>,
deploy: Box<Deploy>,
source: Source<NodeId>,
) -> Effects<Event> {
let chainspec_version = Version::new(1, 0, 0);
let cached_config = self.cached_deploy_configs.get(&chainspec_version).cloned();
match cached_config {
Some(genesis_config) => {
effect_builder
.immediately()
.event(move |_| Event::GetChainspecResult {
deploy,
source,
chainspec_version,
maybe_deploy_config: Box::new(Some(genesis_config)),
})
}
None => effect_builder
.get_chainspec(chainspec_version.clone())
.event(move |maybe_chainspec| Event::GetChainspecResult {
deploy,
source,
chainspec_version,
maybe_deploy_config: Box::new(maybe_chainspec.map(|c| c.into())),
}),
}
}
fn validate<REv: ReactorEventT>(
&mut self,
effect_builder: EffectBuilder<REv>,
deploy: Box<Deploy>,
source: Source<NodeId>,
deploy_config: DeployAcceptorConfig,
) -> Effects<Event> {
let mut cloned_deploy = deploy.clone();
if is_valid(&mut cloned_deploy, deploy_config) {
effect_builder
.put_deploy_to_storage(cloned_deploy)
.event(move |is_new| Event::PutToStorageResult {
deploy,
source,
is_new,
})
} else {
effect_builder
.announce_invalid_deploy(deploy, source)
.ignore()
}
}
fn failed_to_get_chainspec(
&self,
deploy: Box<Deploy>,
source: Source<NodeId>,
chainspec_version: Version,
) -> Effects<Event> {
error!(%deploy, %source, %chainspec_version, "failed to get chainspec");
Effects::new()
}
fn handle_put_to_storage<REv: ReactorEventT>(
&mut self,
effect_builder: EffectBuilder<REv>,
deploy: Box<Deploy>,
source: Source<NodeId>,
is_new: bool,
) -> Effects<Event> {
if is_new {
return effect_builder
.announce_new_deploy_accepted(deploy, source)
.ignore();
}
Effects::new()
}
}
impl<REv: ReactorEventT> Component<REv> for DeployAcceptor {
type Event = Event;
type ConstructionError = Infallible;
fn handle_event(
&mut self,
effect_builder: EffectBuilder<REv>,
_rng: &mut dyn CryptoRngCore,
event: Self::Event,
) -> Effects<Self::Event> {
debug!(?event, "handling event");
match event {
Event::Accept { deploy, source } => self.accept(effect_builder, deploy, source),
Event::GetChainspecResult {
deploy,
source,
chainspec_version,
maybe_deploy_config,
} => match *maybe_deploy_config {
Some(deploy_config) => {
self.cached_deploy_configs
.insert(chainspec_version, deploy_config.clone());
self.validate(effect_builder, deploy, source, deploy_config)
}
None => self.failed_to_get_chainspec(deploy, source, chainspec_version),
},
Event::PutToStorageResult {
deploy,
source,
is_new,
} => self.handle_put_to_storage(effect_builder, deploy, source, is_new),
}
}
}
fn is_valid(deploy: &mut Deploy, config: DeployAcceptorConfig) -> bool {
if deploy.header().chain_name() != config.chain_name {
warn!(
deploy_hash = %deploy.id(),
deploy_header = %deploy.header(),
chain_name = %config.chain_name,
"invalid chain identifier"
);
return false;
}
if deploy.header().dependencies().len() > config.deploy_config.max_dependencies as usize {
warn!(
deploy_hash = %deploy.id(),
deploy_header = %deploy.header(),
max_dependencies = %config.deploy_config.max_dependencies,
"deploy dependency ceiling exceeded"
);
return false;
}
if deploy.header().ttl() > config.deploy_config.max_ttl {
warn!(
deploy_hash = %deploy.id(),
deploy_header = %deploy.header(),
max_ttl = %config.deploy_config.max_ttl,
"deploy ttl excessive"
);
return false;
}
deploy.is_valid()
}