use exonum::{
blockchain::{ApiSender, Blockchain, ValidatorKeys},
helpers::Milliseconds,
merkledb::Snapshot,
};
use exonum_api::ApiBuilder;
use std::{
collections::HashSet,
fmt,
sync::{Arc, RwLock},
};
use crate::{
events::network::ConnectedPeerAddr, state::State, ConnectInfo, ExternalMessage, NodeRole,
};
#[derive(Debug, Default)]
struct ApiNodeState {
incoming_connections: HashSet<ConnectInfo>,
outgoing_connections: HashSet<ConnectInfo>,
is_enabled: bool,
node_role: NodeRole,
majority_count: usize,
validators: Vec<ValidatorKeys>,
tx_cache_len: usize,
}
impl ApiNodeState {
fn new() -> Self {
Self {
is_enabled: true,
..Self::default()
}
}
}
#[derive(Clone, Debug)]
pub struct SharedNodeState {
node: Arc<RwLock<ApiNodeState>>,
state_update_timeout: Milliseconds,
}
impl SharedNodeState {
pub fn new(state_update_timeout: Milliseconds) -> Self {
Self {
node: Arc::new(RwLock::new(ApiNodeState::new())),
state_update_timeout,
}
}
pub fn incoming_connections(&self) -> Vec<ConnectInfo> {
self.node
.read()
.expect("Expected read lock.")
.incoming_connections
.iter()
.cloned()
.collect()
}
pub fn outgoing_connections(&self) -> Vec<ConnectInfo> {
self.node
.read()
.expect("Expected read lock.")
.outgoing_connections
.iter()
.cloned()
.collect()
}
pub fn consensus_status(&self) -> bool {
let lock = self.node.read().expect("Expected read lock.");
let mut active_validators = lock
.incoming_connections
.iter()
.chain(lock.outgoing_connections.iter())
.filter(|ci| {
lock.validators
.iter()
.any(|v| v.consensus_key == ci.public_key)
})
.count();
if lock.node_role.is_validator() {
active_validators += 1;
}
active_validators >= lock.majority_count && lock.majority_count > 0
}
pub fn is_enabled(&self) -> bool {
let state = self.node.read().expect("Expected read lock.");
state.is_enabled
}
pub(crate) fn update_node_state(&self, state: &State) {
let mut lock = self.node.write().expect("Expected write lock.");
lock.incoming_connections.clear();
lock.outgoing_connections.clear();
lock.majority_count = state.majority_count();
lock.node_role = NodeRole::new(state.validator_id());
lock.validators = state.validators().to_vec();
lock.tx_cache_len = state.tx_cache_len();
for (public_key, addr) in state.connections() {
match addr {
ConnectedPeerAddr::In(addr) => {
let conn_info = ConnectInfo {
address: addr.to_string(),
public_key: *public_key,
};
lock.incoming_connections.insert(conn_info);
}
ConnectedPeerAddr::Out(_, addr) => {
let conn_info = ConnectInfo {
address: addr.to_string(),
public_key: *public_key,
};
lock.outgoing_connections.insert(conn_info);
}
}
}
}
pub(crate) fn set_enabled(&self, is_enabled: bool) {
let mut node = self.node.write().expect("Expected write lock.");
node.is_enabled = is_enabled;
}
pub(crate) fn set_node_role(&self, role: NodeRole) {
let mut node = self.node.write().expect("Expected write lock.");
node.node_role = role;
}
pub fn state_update_timeout(&self) -> Milliseconds {
self.state_update_timeout
}
pub fn tx_cache_size(&self) -> usize {
let state = self.node.read().expect("Expected read lock");
state.tx_cache_len
}
}
#[derive(Debug, Clone)]
pub struct PluginApiContext<'a> {
blockchain: &'a Blockchain,
node_state: &'a SharedNodeState,
api_sender: ApiSender<ExternalMessage>,
}
impl<'a> PluginApiContext<'a> {
#[doc(hidden)] pub fn new(
blockchain: &'a Blockchain,
node_state: &'a SharedNodeState,
api_sender: ApiSender<ExternalMessage>,
) -> Self {
Self {
blockchain,
node_state,
api_sender,
}
}
pub fn blockchain(&self) -> &Blockchain {
self.blockchain
}
pub fn node_state(&self) -> &SharedNodeState {
self.node_state
}
pub fn api_sender(&self) -> ApiSender<ExternalMessage> {
self.api_sender.clone()
}
}
pub trait NodePlugin: Send {
fn after_commit(&self, _snapshot: &dyn Snapshot) {
}
fn wire_api(&self, _context: PluginApiContext<'_>) -> Vec<(String, ApiBuilder)> {
Vec::new()
}
}
impl fmt::Debug for dyn NodePlugin {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.debug_tuple("NodePlugin").finish()
}
}