Feature/rewarding interval updates (#880)

* Introduced rewarding_interval_nonce to contract state

* Queries for ibid.

* Mixnode demanded set size

* Routes for obtaining demanded/active mixnode sets

* Testing only demanded nodes

* Typo

* Initial state

* Feature-locking unused imports

* Generating pseudorandom (with deterministic seed) demanded mixnodes set

* cargo fmt

* Fixed tauri state

* Renamed network monitor address to the rewarding validator

* [ci skip] Generate TS types

* Notice for the future

* Transactions to begin/finish mixnode rewarding + double rewarding protection

* Validator API using new contract calls

* Removed dead code from an old experiment

* [ci skip] Generate TS types

* Removed unused import

* Renamed 'demanded' set to 'rewarded' set

* Some renaming action

* [ci skip] Generate TS types

* Fixed post-merge dependency issue in tests

* Post merge test fix

Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>
This commit is contained in:
Jędrzej Stuczyński
2021-11-10 15:28:16 +00:00
committed by GitHub
parent 04636a569a
commit 7e1cf2f105
15 changed files with 1032 additions and 131 deletions
+10
View File
@@ -162,6 +162,16 @@ impl ValidatorCache {
})
}
// NOTE: this does not guarantee consistent results between multiple validator APIs, because
// currently we do not guarantee the list of mixnodes (i.e. `mixnodes: &[MixNodeBond]`) will be the same -
// somebody might bond/unbond a node or change delegation between different cache refreshes.
//
// I guess that's not a problem right now and we can resolve it later. My idea for that would be as follows:
// since the demanded set changes only monthly, just write the identities of those nodes to the smart
// contract upon finished rewarding (this works under assumption of rewards being distributed by a single validator)
//
// alternatively we could have some state locking mechanism for the duration of determining the demanded set
// this could work with multiple validators via some multisig mechanism
fn determine_rewarded_set(
&self,
mixnodes: &[MixNodeBond],
+34 -1
View File
@@ -203,9 +203,42 @@ impl<C> Client<C> {
.calculate_custom_fee(total_gas_limit)
}
pub(crate) async fn begin_mixnode_rewarding(
&self,
rewarding_interval_nonce: u32,
) -> Result<(), RewardingError>
where
C: SigningCosmWasmClient + Sync,
{
self.0
.write()
.await
.nymd
.begin_mixnode_rewarding(rewarding_interval_nonce)
.await?;
Ok(())
}
pub(crate) async fn finish_mixnode_rewarding(
&self,
rewarding_interval_nonce: u32,
) -> Result<(), RewardingError>
where
C: SigningCosmWasmClient + Sync,
{
self.0
.write()
.await
.nymd
.finish_mixnode_rewarding(rewarding_interval_nonce)
.await?;
Ok(())
}
pub(crate) async fn reward_mixnodes(
&self,
nodes: &[MixnodeToReward],
rewarding_interval_nonce: u32,
) -> Result<(), RewardingError>
where
C: SigningCosmWasmClient + Sync,
@@ -216,7 +249,7 @@ impl<C> Client<C> {
.await;
let msgs: Vec<(ExecuteMsg, _)> = nodes
.iter()
.map(Into::into)
.map(|node| node.to_execute_msg(rewarding_interval_nonce))
.zip(std::iter::repeat(Vec::new()))
.collect();
+45 -21
View File
@@ -55,6 +55,8 @@ pub(crate) struct MixnodeToReward {
impl MixnodeToReward {
/// Somewhat clumsy way of feature gatting tokenomics payments. In a tokenomics scenario this will never be None at reward time. We levarage that to Into a different ExecuteMsg variant
// TODO: to re-integrate in another PR that combines rewarded/active sets with tokenomics
#[allow(dead_code)]
fn params(&self) -> Option<NodeRewardParams> {
if cfg!(feature = "tokenomics") {
self.params
@@ -64,6 +66,26 @@ impl MixnodeToReward {
}
}
impl MixnodeToReward {
pub(crate) fn to_execute_msg(&self, rewarding_interval_nonce: u32) -> ExecuteMsg {
ExecuteMsg::RewardMixnode {
identity: self.identity.clone(),
uptime: self.uptime.u8() as u32,
rewarding_interval_nonce,
}
}
// TODO: to re-integrate in another PR that combines rewarded/active sets with tokenomics
#[allow(dead_code)]
pub(crate) fn to_execute_msg_v2(&self, rewarding_interval_nonce: u32) -> ExecuteMsg {
ExecuteMsg::RewardMixnodeV2 {
identity: self.identity.clone(),
params: self.params().unwrap(),
rewarding_interval_nonce,
}
}
}
pub(crate) struct FailedMixnodeRewardChunkDetails {
possibly_unrewarded: Vec<MixnodeToReward>,
error_message: String,
@@ -74,22 +96,6 @@ pub(crate) struct FailureData {
mixnodes: Option<Vec<FailedMixnodeRewardChunkDetails>>,
}
impl<'a> From<&'a MixnodeToReward> for ExecuteMsg {
fn from(node: &MixnodeToReward) -> Self {
if let Some(params) = node.params() {
ExecuteMsg::RewardMixnodeV2 {
identity: node.identity.clone(),
params,
}
} else {
ExecuteMsg::RewardMixnode {
identity: node.identity.clone(),
uptime: node.uptime.u8() as u32,
}
}
}
}
pub(crate) struct Rewarder {
nymd_client: Client<SigningNymdClient>,
validator_cache: ValidatorCache,
@@ -285,14 +291,20 @@ impl Rewarder {
/// # Arguments
///
/// * `eligible_mixnodes`: list of the nodes that are eligible to receive non-zero rewards.
/// * `rewarding_interval_nonce`: nonce associated with the current rewarding interval
async fn distribute_rewards_to_mixnodes(
&self,
eligible_mixnodes: &[MixnodeToReward],
rewarding_interval_nonce: u32,
) -> Option<Vec<FailedMixnodeRewardChunkDetails>> {
let mut failed_chunks = Vec::new();
for (i, mix_chunk) in eligible_mixnodes.chunks(MAX_TO_REWARD_AT_ONCE).enumerate() {
if let Err(err) = self.nymd_client.reward_mixnodes(mix_chunk).await {
if let Err(err) = self
.nymd_client
.reward_mixnodes(mix_chunk, rewarding_interval_nonce)
.await
{
// this is a super weird edge case that we didn't catch change to sequence and
// resent rewards unnecessarily, but the mempool saved us from executing it again
// however, still we want to wait until we're sure we're into the next block
@@ -327,13 +339,13 @@ impl Rewarder {
///
/// # Arguments
///
/// * `epoch_rewarding_id`: id of the current epoch rewarding.
/// * `epoch_rewarding_id`: id of the current epoch rewarding as stored in the databse.
///
/// * `active_monitor_mixnodes`: list of the nodes that were tested at least once by the network monitor
/// in the last epoch.
async fn distribute_rewards(
&self,
epoch_rewarding_id: i64,
epoch_rewarding_database_id: i64,
active_monitor_mixnodes: &[MixnodeStatusReport],
) -> Result<(RewardingReport, Option<FailureData>), RewardingError> {
let mut failure_data = FailureData::default();
@@ -345,12 +357,20 @@ impl Rewarder {
return Err(RewardingError::NoMixnodesToReward);
}
let current_rewarding_nonce = self
.nymd_client
.get_current_rewarding_interval()
.await?
.current_rewarding_interval_nonce;
self.nymd_client
.begin_mixnode_rewarding(current_rewarding_nonce + 1)
.await?;
failure_data.mixnodes = self
.distribute_rewards_to_mixnodes(&eligible_mixnodes)
.distribute_rewards_to_mixnodes(&eligible_mixnodes, current_rewarding_nonce + 1)
.await;
let report = RewardingReport {
epoch_rewarding_id,
epoch_rewarding_id: epoch_rewarding_database_id,
eligible_mixnodes: eligible_mixnodes.len() as i64,
possibly_unrewarded_mixnodes: failure_data
.mixnodes
@@ -364,6 +384,10 @@ impl Rewarder {
.unwrap_or_default(),
};
self.nymd_client
.finish_mixnode_rewarding(current_rewarding_nonce + 1)
.await?;
if failure_data.mixnodes.is_none() {
Ok((report, None))
} else {