use super::state_computation::apply_block_messages_blocking;
use super::utils::structured;
use super::*;
use crate::blocks::CachingBlockHeader;
use crate::interpreter::{CalledAt, VMTrace};
use crate::rpc::state::{BlockReward, ExecutionTrace, RewardAmounts};
use crate::shim::actors::reward::{RewardDistributionError, award_distribution};
use anyhow::ensure;
use fil_actor_reward_state::v19::DENOM;
use num_traits::Zero as _;
impl StateManager {
pub async fn reward_distribution(
&self,
tipset: &Tipset,
) -> anyhow::Result<Arc<RewardDistribution>> {
ensure!(
self.get_network_version(tipset.epoch()) >= NetworkVersion::V29,
RewardDistributionError::RewardActorBeforeV19
);
self.reward_distribution_cache
.get_or_insert_async(
tipset.key(),
self.reward_distribution_inner(tipset.shallow_clone()),
)
.await
}
async fn reward_distribution_inner(
&self,
tipset: Tipset,
) -> anyhow::Result<Arc<RewardDistribution>> {
let permit = self.replay_permit().await;
let this = self.shallow_clone();
tokio::task::spawn_blocking(move || {
let _permit = permit;
this.reward_distribution_inner_blocking(tipset)
})
.await
.context("tokio join error")?
}
fn reward_distribution_inner_blocking(
&self,
tipset: Tipset,
) -> anyhow::Result<Arc<RewardDistribution>> {
let mut blocks = Vec::with_capacity(tipset.len());
let callback = |ctx: MessageCallbackCtx<'_>| {
if matches!(ctx.at, CalledAt::Reward) {
let header = tipset
.block_headers()
.get(blocks.len())
.context("more reward messages than blocks")?;
blocks.push(block_reward(header, &ctx)?);
}
Ok(())
};
apply_block_messages_blocking(
self.chain_index().shallow_clone(),
self.chain_config().shallow_clone(),
self.beacon_schedule().shallow_clone(),
&self.engine,
tipset.shallow_clone(),
Some(callback),
VMTrace::Traced,
)?;
Ok(Arc::new(RewardDistribution {
tipset_key: tipset.key().clone(),
height: tipset.epoch(),
denom: DENOM,
totals: total_amounts(&blocks),
blocks,
}))
}
}
fn block_reward(
header: &CachingBlockHeader,
ctx: &MessageCallbackCtx<'_>,
) -> anyhow::Result<BlockReward> {
let params: AwardBlockRewardParams = ctx.message.message().params().deserialize()?;
let miner = Address::from(params.miner);
ensure!(
miner == header.miner_address,
"reward message pays {miner} for a block mined by {}",
header.miner_address
);
let trace = structured::parse_events(ctx.apply_ret.exec_trace())?
.context("reward message has no execution trace")?;
let actor_before = trace
.invoked_actor
.context("reward message invoked no actor")?
.state;
let actor_after = ctx
.state
.get_actor(&Address::REWARD_ACTOR)?
.context("reward actor not found")?;
let before = reward::State::load_from_blockstore(&ctx.state, &actor_before)?;
let after = reward::State::load_from_blockstore(&ctx.state, &actor_after)?;
let (streams, burn_weight, mut amounts) =
award_distribution(&before, &after, &ctx.state, header.epoch)?;
amounts.message_reward = params.gas_reward.into();
(amounts.miner_paid, amounts.burn_paid) = direct_payments(trace.subcalls, miner);
Ok(BlockReward {
block: *header.cid(),
miner,
win_count: params.win_count,
amounts,
burn_weight,
streams,
})
}
fn total_amounts(blocks: &[BlockReward]) -> RewardAmounts {
let mut totals = RewardAmounts::default();
for block in blocks {
let RewardAmounts {
minted_reward,
miner_reward,
message_reward,
explicit_reward,
burn_allocation,
miner_paid,
burn_paid,
} = block.amounts.clone();
totals.minted_reward += minted_reward;
totals.miner_reward += miner_reward;
totals.message_reward += message_reward;
totals.explicit_reward += explicit_reward;
totals.burn_allocation += burn_allocation;
totals.miner_paid += miner_paid;
totals.burn_paid += burn_paid;
}
totals
}
fn direct_payments(subcalls: Vec<ExecutionTrace>, miner: Address) -> (TokenAmount, TokenAmount) {
let mut miner_paid = TokenAmount::zero();
let mut burn_paid = TokenAmount::zero();
for call in subcalls {
if !call.msg_rct.exit_code.is_success() {
continue;
}
if call.msg.to == miner {
miner_paid += call.msg.value;
} else if call.msg.to == Address::BURNT_FUNDS_ACTOR {
burn_paid += call.msg.value;
}
}
(miner_paid, burn_paid)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rpc::state::{MessageTrace, ReturnTrace};
use crate::shim::error::ExitCode;
fn transfer(to: Address, value: i64, exit_code: u32) -> ExecutionTrace {
ExecutionTrace {
msg: MessageTrace {
from: Address::REWARD_ACTOR,
to,
value: TokenAmount::from_atto(value),
method: 0,
params: Default::default(),
params_codec: 0,
gas_limit: None,
read_only: None,
},
msg_rct: ReturnTrace {
exit_code: ExitCode::from(exit_code),
r#return: Default::default(),
return_codec: 0,
},
invoked_actor: None,
gas_charges: vec![],
subcalls: vec![],
logs: vec![],
ipld_ops: vec![],
}
}
#[test]
fn direct_payments_sum_successful_transfers_to_the_miner_and_to_burn() {
const OK: u32 = 0;
const ILLEGAL_STATE: u32 = 20;
let miner = Address::new_id(1000);
let mut miner_payment = transfer(miner, 107, OK);
miner_payment.subcalls = vec![transfer(Address::BURNT_FUNDS_ACTOR, 13, OK)];
let subcalls = vec![
miner_payment,
transfer(miner, 41, ILLEGAL_STATE),
transfer(Address::BURNT_FUNDS_ACTOR, 2, OK),
transfer(Address::BURNT_FUNDS_ACTOR, 20, OK),
transfer(Address::BURNT_FUNDS_ACTOR, 99, ILLEGAL_STATE),
transfer(Address::new_id(1001), 5, OK),
];
let (miner_paid, burn_paid) = direct_payments(subcalls, miner);
assert_eq!(miner_paid, TokenAmount::from_atto(107)); assert_eq!(burn_paid, TokenAmount::from_atto(22));
}
}