Skip to content

Commit

Permalink
F/rewards distribution 2 (#97)
Browse files Browse the repository at this point in the history
Follow-up / Alternative to #95, in which the rewards are distributed
over the (latest) active finality provider set
  • Loading branch information
maurolacy authored Dec 16, 2024
1 parent 166fda9 commit 84130eb
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 18 deletions.
11 changes: 8 additions & 3 deletions contracts/btc-finality/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ use btc_staking::msg::ActivatedHeightResponse;

use crate::error::ContractError;
use crate::finality::{
compute_active_finality_providers, handle_finality_signature, handle_public_randomness_commit,
compute_active_finality_providers, distribute_rewards, handle_finality_signature,
handle_public_randomness_commit,
};
use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
use crate::state::config::{Config, ADMIN, CONFIG, PARAMS};
Expand Down Expand Up @@ -222,10 +223,14 @@ fn handle_update_staking(
}

fn handle_begin_block(deps: &mut DepsMut, env: Env) -> Result<Response<BabylonMsg>, ContractError> {
// Distribute rewards
distribute_rewards(deps, &env)?;

// Compute active finality provider set
let max_active_fps = PARAMS.load(deps.storage)?.max_active_finality_providers as usize;
compute_active_finality_providers(deps, env, max_active_fps)?;
compute_active_finality_providers(deps, env.block.height, max_active_fps)?;

// TODO: Add events
Ok(Response::new())
}

Expand All @@ -245,7 +250,7 @@ fn handle_end_block(
let ev = finality::index_block(deps, env.block.height, &hex::decode(app_hash_hex)?)?;
res = res.add_event(ev);
// Tally all non-finalised blocks
let (msg, events) = finality::tally_blocks(deps, activated_height, env.block.height)?;
let (msg, events) = finality::tally_blocks(deps, &env, activated_height)?;
if let Some(msg) = msg {
res = res.add_message(msg);
}
Expand Down
55 changes: 44 additions & 11 deletions contracts/btc-finality/src/finality.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::contract::encode_smart_query;
use crate::error::ContractError;
use crate::state::config::{Config, CONFIG, PARAMS};
use crate::state::finality::{BLOCKS, EVIDENCES, FP_SET, NEXT_HEIGHT, SIGNATURES, TOTAL_POWER};
use crate::state::finality::{
BLOCKS, EVIDENCES, FP_SET, NEXT_HEIGHT, REWARDS, SIGNATURES, TOTAL_REWARDS,
};
use crate::state::public_randomness::{
get_last_pub_rand_commit, get_pub_rand_commit_for_height, PUB_RAND_COMMITS, PUB_RAND_VALUES,
};
Expand All @@ -13,7 +15,7 @@ use btc_staking::msg::{FinalityProviderInfo, FinalityProvidersByPowerResponse};
use cosmwasm_std::Order::Ascending;
use cosmwasm_std::{
to_json_binary, Addr, Coin, Decimal, DepsMut, Env, Event, QuerierWrapper, Response, StdResult,
Storage, WasmMsg,
Storage, Uint128, WasmMsg,
};
use k256::ecdsa::signature::Verifier;
use k256::schnorr::{Signature, VerifyingKey};
Expand Down Expand Up @@ -420,8 +422,8 @@ pub fn index_block(
/// It must be invoked only after the BTC staking protocol is activated.
pub fn tally_blocks(
deps: &mut DepsMut,
env: &Env,
activated_height: u64,
height: u64,
) -> Result<(Option<BabylonMsg>, Vec<Event>), ContractError> {
// Start finalising blocks since max(activated_height, next_height)
let next_height = NEXT_HEIGHT.may_load(deps.storage)?.unwrap_or(0);
Expand All @@ -438,7 +440,7 @@ pub fn tally_blocks(
// non-finalisable
let mut events = vec![];
let mut finalized_blocks = 0;
for h in start_height..=height {
for h in start_height..=env.block.height {
let mut indexed_block = BLOCKS.load(deps.storage, h)?;
// Get the finality provider set of this block
let fp_set = FP_SET.may_load(deps.storage, h)?;
Expand Down Expand Up @@ -490,7 +492,7 @@ pub fn tally_blocks(
// Assemble mint message
let mint_msg = BabylonMsg::MintRewards {
amount: rewards,
recipient: cfg.staking.into(),
recipient: env.contract.address.to_string(),
};
Some(mint_msg)
} else {
Expand Down Expand Up @@ -528,8 +530,6 @@ fn finalize_block(
// Set the next height to finalise as height+1
NEXT_HEIGHT.save(store, &(block.height + 1))?;

// TODO: Distribute rewards to BTC staking delegators

// Record the last finalized height metric
let ev = Event::new("finalize_block")
.add_attribute("module", "finality")
Expand Down Expand Up @@ -568,7 +568,7 @@ const QUERY_LIMIT: Option<u32> = Some(30);
/// power of top finality providers, and records them in the contract state
pub fn compute_active_finality_providers(
deps: &mut DepsMut,
env: Env,
height: u64,
max_active_fps: usize,
) -> Result<(), ContractError> {
let cfg = CONFIG.load(deps.storage)?;
Expand Down Expand Up @@ -602,9 +602,7 @@ pub fn compute_active_finality_providers(
// TODO: Filter out slashed / offline / jailed FPs
// Save the new set of active finality providers
// TODO: Purge old (height - finality depth) FP_SET entries to avoid bloating the storage
FP_SET.save(deps.storage, env.block.height, &finality_providers)?;
// Save the total voting power of the top n finality providers
TOTAL_POWER.save(deps.storage, &total_power)?;
FP_SET.save(deps.storage, height, &finality_providers)?;

Ok(())
}
Expand All @@ -622,3 +620,38 @@ pub fn list_fps_by_power(
let res: FinalityProvidersByPowerResponse = querier.query(&query)?;
Ok(res.fps)
}

/// `distribute_rewards` distributes rewards to finality providers who are in the active set at `height`
pub fn distribute_rewards(deps: &mut DepsMut, env: &Env) -> Result<(), ContractError> {
// Try to use the finality provider set at the previous height
let active_fps = FP_SET.may_load(deps.storage, env.block.height - 1)?;
// Short-circuit if there are no active finality providers
let active_fps = match active_fps {
Some(active_fps) => active_fps,
None => return Ok(()),
};
// Get the voting power of the active FPS
let total_voting_power = active_fps.iter().map(|fp| fp.power as u128).sum::<u128>();
// Get the rewards to distribute (bank balance of the finality contract)
let cfg = CONFIG.load(deps.storage)?;
let rewards_amount = deps
.querier
.query_balance(env.contract.address.clone(), cfg.denom)?
.amount;
// Compute the rewards for each active FP
let mut total_rewards = Uint128::zero();
for fp in active_fps {
let reward = (Decimal::from_ratio(fp.power as u128, total_voting_power)
* Decimal::from_ratio(rewards_amount, 1u128))
.to_uint_floor();
// Update the rewards for this FP
REWARDS.update(deps.storage, &fp.btc_pk_hex, |r| {
Ok::<Uint128, ContractError>(r.unwrap_or_default() + reward)
})?;
// Compute the total rewards
total_rewards += reward;
}
// Update the total rewards
TOTAL_REWARDS.save(deps.storage, &total_rewards)?;
Ok(())
}
12 changes: 8 additions & 4 deletions contracts/btc-finality/src/state/finality.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use cosmwasm_std::Uint128;

use cw_storage_plus::{Item, Map};

use babylon_apis::finality_api::{Evidence, IndexedBlock};
Expand All @@ -15,9 +17,11 @@ pub const NEXT_HEIGHT: Item<u64> = Item::new("next_height");
/// `FP_SET` is the calculated list of the active finality providers by height
pub const FP_SET: Map<u64, Vec<FinalityProviderInfo>> = Map::new("fp_set");

/// `TOTAL_POWER` is the total power of all finality providers
// FIXME: Store by height? Remove? Not currently being used in the contract
pub const TOTAL_POWER: Item<u64> = Item::new("total_power");

/// Map of double signing evidence by FP and block height
pub const EVIDENCES: Map<(&str, u64), Evidence> = Map::new("evidences");

/// Map of pending finality provider rewards
pub const REWARDS: Map<&str, Uint128> = Map::new("rewards");

/// Total pending rewards
pub const TOTAL_REWARDS: Item<Uint128> = Item::new("total_rewards");

0 comments on commit 84130eb

Please sign in to comment.