ee5b55fab6
* Feature/ephemera compile (#3437) * Include ephemera node code in repo * Upgrade deps * Bump minor version of cosmwasm-std * Include ephemera in nym-api dep and downgrade rusqlite * Fix clippy and ephemera docs code * More clippy on ephemera --------- Co-authored-by: Andrus Salumets <andrus@nymtech.net> * Start ephemera components in nym-api (#3475) * Start ephemera components in nym-api * Pass nyxd client and use common metric structures * Swap url endpoint with contract for sending rewarding messages * Fix build after rebase * Perform ephemera rewards computation before normal nym-api ones * Remove contract mock from ephemera * Take raw rewards from network monitor * Remove ephemera old reward version * Use nym shutdown procedure in ephemera * Temporary fix for some warnings * Umock contract membership of ephemera (#3574) * Pass nyxd client to members provider * Basic ephemera contract * Add register peer tx * Add query all peers * Nyxd ephemera client * Add registration of ephemera peer * Replace epoch http api with actual contract * Merge ephemera config into nym-api config * Load cluster from contract * Guard nym-outfox out of cosmwasm builds (#3650) * Feature/fixes while testing (#3668) * Commit local peer before querying contract * Default to anyonline * Remove string from template * Fix avg computing * Use updated qa env * Fix clippy * Add unit tests for ephemera contract * Upload ephemera contract in CI * Add group check for peer signup * Peer registration unit test * Start ephemera only on monitoring * Remove old MixnodeToReward struct * Move all ephemera config to its file * Skip with serde ephemera config * Fix default value in args * Feature/add ephemera flag (#3727) * Replace unwrap with error handling * Add ephemera enable flag * Fix template * Add json schema to ephemera contract (#3735) * Update lock files * Update changelog --------- Co-authored-by: Andrus Salumets <andrus@nymtech.net>
210 lines
7.2 KiB
Rust
210 lines
7.2 KiB
Rust
use ephemera::{
|
|
configuration::Configuration,
|
|
ephemera_api::{
|
|
ApiBlock, ApiEphemeraMessage, Application, ApplicationResult, CheckBlockResult,
|
|
RemoveMessages,
|
|
},
|
|
};
|
|
use log::{debug, error, info};
|
|
|
|
use crate::ephemera::client::Client;
|
|
use crate::ephemera::epoch::Epoch;
|
|
use crate::ephemera::peers::members::MembersProvider;
|
|
use crate::ephemera::peers::{NymApiEphemeraPeerInfo, NymPeer};
|
|
use crate::ephemera::reward::aggregator::RewardsAggregator;
|
|
use crate::ephemera::reward::{EphemeraAccess, RewardManager};
|
|
use crate::ephemera::Args;
|
|
use crate::epoch_operations::MixnodeWithPerformance;
|
|
use crate::support::nyxd;
|
|
use ephemera::crypto::{EphemeraKeypair, EphemeraPublicKey, Keypair};
|
|
use ephemera::ephemera_api::CommandExecutor;
|
|
use ephemera::{Ephemera, EphemeraStarterInit};
|
|
use nym_task::TaskManager;
|
|
|
|
pub struct NymApi;
|
|
|
|
impl NymApi {
|
|
pub(crate) async fn run(
|
|
args: Args,
|
|
ephemera_config: Configuration,
|
|
nyxd_client: nyxd::Client,
|
|
shutdown: &TaskManager,
|
|
) -> anyhow::Result<RewardManager> {
|
|
//KEYPAIR - Ephemera keypair or Validator keypair
|
|
//Can be a file, keystore etc
|
|
let key_pair = Self::read_nym_api_keypair(&ephemera_config)?;
|
|
|
|
//EPHEMERA
|
|
let ephemera = Self::init_ephemera(ephemera_config, nyxd_client.clone()).await?;
|
|
let ephemera_handle = ephemera.handle();
|
|
|
|
//REWARDS
|
|
let rewards =
|
|
Self::create_rewards_manager(args, nyxd_client, key_pair, ephemera_handle.api.clone())
|
|
.await?;
|
|
|
|
//STARTING
|
|
let shutdown_listener = shutdown.subscribe();
|
|
tokio::spawn(ephemera.run(shutdown_listener));
|
|
|
|
Ok(rewards)
|
|
}
|
|
|
|
pub(crate) async fn init_ephemera(
|
|
ephemera_config: Configuration,
|
|
nyxd_client: nyxd::Client,
|
|
) -> anyhow::Result<Ephemera<RewardsEphemeraApplication>> {
|
|
info!("Initializing ephemera ...");
|
|
|
|
let node_info = ephemera_config.node.clone();
|
|
|
|
let keypair = bs58::decode(&node_info.private_key).into_vec().unwrap();
|
|
let keypair = Keypair::from_bytes(&keypair).unwrap();
|
|
let local_peer_id = keypair.public_key().to_base58();
|
|
|
|
//Members provider for Ephemera
|
|
let members_provider = MembersProvider::new(nyxd_client.clone());
|
|
|
|
let cosmos_address = nyxd_client.client_address().await.to_string();
|
|
let ip_address = format!(
|
|
"/ip4/{}/tcp/{}",
|
|
ephemera_config.node.ip, ephemera_config.libp2p.port
|
|
);
|
|
|
|
if !nyxd_client
|
|
.get_ephemera_peers()
|
|
.await?
|
|
.iter()
|
|
.any(|peer_info| peer_info.cosmos_address == cosmos_address)
|
|
{
|
|
let local_peer = NymPeer::new(
|
|
cosmos_address,
|
|
ip_address,
|
|
keypair.public_key(),
|
|
local_peer_id.clone(),
|
|
);
|
|
members_provider.register_peer(local_peer).await?;
|
|
}
|
|
|
|
//Application for Ephemera
|
|
let rewards_ephemera_application =
|
|
RewardsEphemeraApplication::init(local_peer_id, nyxd_client.clone()).await?;
|
|
|
|
//EPHEMERA
|
|
let ephemera_builder = EphemeraStarterInit::new(ephemera_config)?;
|
|
let ephemera_builder = ephemera_builder.with_application(rewards_ephemera_application);
|
|
let ephemera_builder = ephemera_builder.with_members_provider(members_provider)?;
|
|
let ephemera = ephemera_builder.build();
|
|
Ok(ephemera)
|
|
}
|
|
|
|
async fn create_rewards_manager(
|
|
args: Args,
|
|
nyxd_client: nyxd::Client,
|
|
key_pair: Keypair,
|
|
ephemera_api: CommandExecutor,
|
|
) -> anyhow::Result<RewardManager> {
|
|
let epoch = Epoch::request_epoch(nyxd_client).await?;
|
|
Ok(RewardManager::new(
|
|
args.clone(),
|
|
EphemeraAccess::new(ephemera_api, key_pair).into(),
|
|
Some(RewardsAggregator),
|
|
epoch,
|
|
))
|
|
}
|
|
|
|
fn read_nym_api_keypair(ephemera_config: &Configuration) -> anyhow::Result<Keypair> {
|
|
let key_pair = bs58::decode(&ephemera_config.node.private_key).into_vec()?;
|
|
let key_pair = Keypair::from_bytes(&key_pair)?;
|
|
Ok(key_pair)
|
|
}
|
|
}
|
|
|
|
pub(crate) struct RewardsEphemeraApplicationConfig {
|
|
/// Percentage of messages relative to total number of peers
|
|
pub(crate) peers_rewards_threshold: u64,
|
|
}
|
|
|
|
pub(crate) struct RewardsEphemeraApplication {
|
|
peer_info: NymApiEphemeraPeerInfo,
|
|
app_config: RewardsEphemeraApplicationConfig,
|
|
}
|
|
|
|
impl RewardsEphemeraApplication {
|
|
pub(crate) async fn init(
|
|
local_peer_id: String,
|
|
nyxd_client: nyxd::Client,
|
|
) -> anyhow::Result<Self> {
|
|
let peer_info = match NymApiEphemeraPeerInfo::from_ephemera_dev_cluster_conf(
|
|
local_peer_id,
|
|
nyxd_client,
|
|
)
|
|
.await
|
|
{
|
|
Ok(info) => info,
|
|
Err(err) => {
|
|
error!("Failed to load peers info: {}", err);
|
|
return Err(err);
|
|
}
|
|
};
|
|
let app_config = RewardsEphemeraApplicationConfig {
|
|
peers_rewards_threshold: peer_info.get_peers_count() as u64,
|
|
};
|
|
Ok(Self {
|
|
peer_info,
|
|
app_config,
|
|
})
|
|
}
|
|
}
|
|
|
|
/// - TODO: We should also check that the messages has expected label(like epoch 100)
|
|
/// because next block should have only reward info for correct epoch.
|
|
impl Application for RewardsEphemeraApplication {
|
|
/// Perform validation checks:
|
|
/// - Check that the transaction has a valid signature, we don't want to accept garbage messages
|
|
/// or messages from unknown peers
|
|
fn check_tx(&self, tx: ApiEphemeraMessage) -> ApplicationResult<bool> {
|
|
if serde_json::from_slice::<Vec<MixnodeWithPerformance>>(&tx.data).is_err() {
|
|
error!("Message is not a valid Reward message");
|
|
return Ok(false);
|
|
}
|
|
Ok(true)
|
|
|
|
//TODO
|
|
//PS! message label should also be part of the message hash to prevent replay attacks
|
|
}
|
|
|
|
/// Agree to accept the block if it contains threshold number of transactions
|
|
/// We trust that transactions are valid(checked by check_tx)
|
|
fn check_block(&self, block: &ApiBlock) -> ApplicationResult<CheckBlockResult> {
|
|
info!("Block message count: {}", block.message_count());
|
|
|
|
let block_threshold = ((block.message_count() as f64
|
|
/ self.peer_info.get_peers_count() as f64)
|
|
* 100.0) as u64;
|
|
|
|
if block_threshold > 100 {
|
|
error!("Block threshold is greater than 100%!. We expected only single message from each peer");
|
|
return Ok(CheckBlockResult::RejectAndRemoveMessages(
|
|
RemoveMessages::All,
|
|
));
|
|
}
|
|
|
|
if block_threshold >= self.app_config.peers_rewards_threshold {
|
|
info!(
|
|
"Block accepted {}:{}",
|
|
block.header.height, block.header.hash
|
|
);
|
|
Ok(CheckBlockResult::Accept)
|
|
} else {
|
|
debug!("Block rejected: not enough messages");
|
|
Ok(CheckBlockResult::Reject)
|
|
}
|
|
}
|
|
|
|
/// It is possible to use this method as a callback to get notified when block is committed
|
|
fn deliver_block(&self, _block: ApiBlock) -> ApplicationResult<()> {
|
|
Ok(())
|
|
}
|
|
}
|