Skip to content

Commit

Permalink
Move fee payment computations to node-common
Browse files Browse the repository at this point in the history
  • Loading branch information
lrubasze committed Nov 13, 2024
1 parent add63fe commit 18a5e2d
Show file tree
Hide file tree
Showing 6 changed files with 350 additions and 317 deletions.
311 changes: 81 additions & 230 deletions core-rust/core-api-server/src/core_api/conversions/lts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,13 @@ pub fn to_api_lts_fungible_balance_changes(
balance_changes: &IndexMap<GlobalAddress, IndexMap<ResourceAddress, BalanceChange>>,
) -> Result<Vec<models::LtsEntityFungibleBalanceChanges>, MappingError> {
let fee_balance_changes = resolve_global_fee_balance_changes(database, fee_source)?;
FeePaymentComputer::compute(FeePaymentComputationInputs {
let fee_payment_computations = FeePaymentComputer::compute(FeePaymentComputationInputs {
fee_balance_changes,
fee_summary,
fee_destination,
balance_changes,
})
.resolve_fungible_balance_changes(context)
});
resolve_fungible_balance_changes(&fee_payment_computations, context)
}

/// Uses the [`SubstateNodeAncestryStore`] (from the given DB) to transform the input
Expand All @@ -143,242 +143,100 @@ fn resolve_global_fee_balance_changes(
Ok(fee_balance_changes)
}

struct FeePaymentComputer<'a> {
inputs: FeePaymentComputationInputs<'a>,
computation: FeePaymentComputation,
}

struct FeePaymentComputationInputs<'a> {
/// The balance changes caused by [`FeeSource#paying_vaults`] (resolved to global ancestors).
/// Note: this information is logically of the same type as [`balance_changes`], but the actual
/// signature is simpler, since all fees are necessarily XRD and thus have fungible balances.
fee_balance_changes: IndexMap<GlobalAddress, Decimal>,
fee_summary: &'a TransactionFeeSummary,
fee_destination: &'a FeeDestination,
/// The total balance changes (i.e. including the [`fee_balance_changes`]).
balance_changes: &'a IndexMap<GlobalAddress, IndexMap<ResourceAddress, BalanceChange>>,
}

struct FeePaymentComputation {
relevant_entities: IndexSet<GlobalAddress>,
fee_balance_changes:
NonIterMap<GlobalAddress, Vec<(models::LtsFeeFungibleResourceBalanceChangeType, Decimal)>>,
non_fee_balance_changes: NonIterMap<GlobalAddress, IndexMap<ResourceAddress, Decimal>>,
}

impl<'a> FeePaymentComputer<'a> {
pub fn compute(inputs: FeePaymentComputationInputs<'a>) -> FeePaymentComputation {
Self {
inputs,
computation: FeePaymentComputation {
relevant_entities: Default::default(),
fee_balance_changes: Default::default(),
non_fee_balance_changes: Default::default(),
},
fn to_api_lts_fee_fungible_resource_balance_change_type(
fee_type: &FeePaymentBalanceChangeType,
) -> models::LtsFeeFungibleResourceBalanceChangeType {
match fee_type {
FeePaymentBalanceChangeType::FeePayment => {
models::LtsFeeFungibleResourceBalanceChangeType::FeePayment
}
.compute_internal()
}

fn compute_internal(mut self) -> FeePaymentComputation {
// Step 1 - Add initial relevant entities
self.add_initial_ordered_relevant_entities();

// Step 2 - Track fee balance changes
self.track_fee_payments();
self.track_fee_distributions();
self.track_royalty_distributions();

// Step 3 - Compute resultant non-fee balance changes
self.finalize_non_fee_balance_changes();

// And finally, return the outputs
self.computation
}

fn add_initial_ordered_relevant_entities(&mut self) {
for entity in self.inputs.balance_changes.keys() {
self.computation.relevant_entities.insert(*entity);
FeePaymentBalanceChangeType::FeeDistributed => {
models::LtsFeeFungibleResourceBalanceChangeType::FeeDistributed
}
}

fn track_fee_payments(&mut self) {
for (fee_payer, fee_balance_change) in self.inputs.fee_balance_changes.clone() {
self.record_fee_balance_change_if_non_zero(
fee_payer,
fee_balance_change,
models::LtsFeeFungibleResourceBalanceChangeType::FeePayment,
);
FeePaymentBalanceChangeType::TipDistributed => {
models::LtsFeeFungibleResourceBalanceChangeType::TipDistributed
}
}

fn track_fee_distributions(&mut self) {
self.record_fee_balance_change_if_non_zero(
CONSENSUS_MANAGER.into(),
self.inputs
.fee_summary
.network_fees()
.sub_or_panic(self.inputs.fee_destination.to_burn),
models::LtsFeeFungibleResourceBalanceChangeType::FeeDistributed,
);
self.record_fee_balance_change_if_non_zero(
CONSENSUS_MANAGER.into(),
self.inputs.fee_summary.total_tipping_cost_in_xrd,
models::LtsFeeFungibleResourceBalanceChangeType::TipDistributed,
);
}

fn track_royalty_distributions(&mut self) {
for (recipient, amount) in &self.inputs.fee_destination.to_royalty_recipients {
let recipient: GlobalAddress = match recipient {
RoyaltyRecipient::Package(address, _) => (*address).into(),
RoyaltyRecipient::Component(address, _) => (*address).into(),
};
self.record_fee_balance_change_if_non_zero(
recipient,
*amount,
models::LtsFeeFungibleResourceBalanceChangeType::RoyaltyDistributed,
);
FeePaymentBalanceChangeType::RoyaltyDistributed => {
models::LtsFeeFungibleResourceBalanceChangeType::RoyaltyDistributed
}
}
}

fn record_fee_balance_change_if_non_zero(
&mut self,
address: GlobalAddress,
balance_change: Decimal,
fee_type: models::LtsFeeFungibleResourceBalanceChangeType,
) {
if balance_change == Decimal::ZERO {
return;
}
// This handles the case that a relevant entity had 0 net balance change.
// For example, if a component received a royalty and output XRD equal to that royalty in the same transaction then
// it wouldn't be in the balance changes - but we'd still want to include it in our output.
self.computation.relevant_entities.insert(address);
self.computation
fn resolve_fungible_balance_changes(
fee_payment_computation: &FeePaymentComputation,
context: &MappingContext,
) -> Result<Vec<models::LtsEntityFungibleBalanceChanges>, MappingError> {
let mut output = Vec::with_capacity(fee_payment_computation.relevant_entities.len());
for entity in &fee_payment_computation.relevant_entities {
// First - calculate the deprecated/duplicated total balance change
let deprecated_fee_payment_balance_change = fee_payment_computation
.fee_balance_changes
.entry(address)
.or_default()
.push((fee_type, balance_change));
}

fn finalize_non_fee_balance_changes(&mut self) {
for (entity, changes) in self.inputs.balance_changes {
let total_fee_balance_changes: Decimal = self
.computation
.get(&entity)
.map(|fee_changes| {
let total_fee_payment_balance_change = fee_changes
.iter()
.filter_map(|fee_balance_change| match &fee_balance_change.0 {
FeePaymentBalanceChangeType::FeePayment => Some(fee_balance_change.1),
_ => None,
})
.sum_or_panic();
let output = if total_fee_payment_balance_change.is_zero() {
None
} else {
Some(Box::new(to_api_lts_fungible_resource_balance_change(
context,
&XRD,
&total_fee_payment_balance_change,
)?))
};
Ok(output)
})
.transpose()?
.flatten();
output.push(models::LtsEntityFungibleBalanceChanges {
entity_address: to_api_global_address(context, &entity)?,
fee_balance_change: deprecated_fee_payment_balance_change,
fee_balance_changes: fee_payment_computation
.fee_balance_changes
.get(entity)
.map(|fee_payments| fee_payments.iter().map(|p| p.1).sum_or_panic())
.unwrap_or_default();
let mut non_fee_balance_changes: IndexMap<ResourceAddress, Decimal> = changes
.iter()
.filter_map(|(resource, balance_change)| {
if resource == &XRD {
let total_balance_change = get_fungible_balance(balance_change)
.expect("Expected XRD to be fungible");
let total_non_fee_balance_change =
total_balance_change.sub_or_panic(total_fee_balance_changes);
if total_non_fee_balance_change == Decimal::ZERO {
None
} else {
Some((*resource, total_non_fee_balance_change))
}
} else {
match balance_change {
BalanceChange::Fungible(change) => Some((*resource, *change)),
BalanceChange::NonFungible { .. } => None,
}
}
.get(&entity)
.map(|fee_balance_changes| {
fee_balance_changes
.iter()
.map(
|(fee_change_type, balance_change)| -> Result<_, MappingError> {
Ok(models::LtsFeeFungibleResourceBalanceChange {
resource_address: to_api_resource_address(context, &XRD)?,
balance_change: to_api_decimal(balance_change),
_type: to_api_lts_fee_fungible_resource_balance_change_type(
fee_change_type,
),
})
},
)
.collect::<Result<_, _>>()
})
.collect();
if total_fee_balance_changes != Decimal::ZERO && !changes.contains_key(&XRD) {
// If there were fee-related balance changes, but XRD is not in the balance change set,
// then there must have been an equal-and-opposite non-fee balance change to offset it
non_fee_balance_changes.insert(XRD, total_fee_balance_changes.neg_or_panic());
}
self.computation
.transpose()?
.unwrap_or_default(),
non_fee_balance_changes: fee_payment_computation
.non_fee_balance_changes
.insert(*entity, non_fee_balance_changes);
}
}
}

impl FeePaymentComputation {
fn resolve_fungible_balance_changes(
self,
context: &MappingContext,
) -> Result<Vec<models::LtsEntityFungibleBalanceChanges>, MappingError> {
let mut output = Vec::with_capacity(self.relevant_entities.len());
for entity in self.relevant_entities {
// First - calculate the deprecated/duplicated total balance change
let deprecated_fee_payment_balance_change = self
.fee_balance_changes
.get(&entity)
.map(|fee_changes| {
let total_fee_payment_balance_change = fee_changes
.map(|non_fee_balance_changes| {
non_fee_balance_changes
.iter()
.filter_map(|fee_balance_change| match &fee_balance_change.0 {
models::LtsFeeFungibleResourceBalanceChangeType::FeePayment => {
Some(fee_balance_change.1)
}
_ => None,
.map(|(resource_address, amount)| {
to_api_lts_fungible_resource_balance_change(
context,
resource_address,
amount,
)
})
.sum_or_panic();
let output = if total_fee_payment_balance_change.is_zero() {
None
} else {
Some(Box::new(to_api_lts_fungible_resource_balance_change(
context,
&XRD,
&total_fee_payment_balance_change,
)?))
};
Ok(output)
.collect::<Result<_, _>>()
})
.transpose()?
.flatten();
output.push(models::LtsEntityFungibleBalanceChanges {
entity_address: to_api_global_address(context, &entity)?,
fee_balance_change: deprecated_fee_payment_balance_change,
fee_balance_changes: self
.fee_balance_changes
.get(&entity)
.map(|fee_balance_changes| {
fee_balance_changes
.iter()
.map(
|(fee_change_type, balance_change)| -> Result<_, MappingError> {
Ok(models::LtsFeeFungibleResourceBalanceChange {
resource_address: to_api_resource_address(context, &XRD)?,
balance_change: to_api_decimal(balance_change),
_type: *fee_change_type,
})
},
)
.collect::<Result<_, _>>()
})
.transpose()?
.unwrap_or_default(),
non_fee_balance_changes: self
.non_fee_balance_changes
.get(&entity)
.map(|non_fee_balance_changes| {
non_fee_balance_changes
.iter()
.map(|(resource_address, amount)| {
to_api_lts_fungible_resource_balance_change(
context,
resource_address,
amount,
)
})
.collect::<Result<_, _>>()
})
.transpose()?
.unwrap_or_default(),
});
}
Ok(output)
.unwrap_or_default(),
});
}
Ok(output)
}

pub fn to_api_lts_fungible_resource_balance_change(
Expand Down Expand Up @@ -415,13 +273,6 @@ pub fn to_api_lts_resultant_account_fungible_balances(
.collect()
}

pub fn get_fungible_balance(balance_change: &BalanceChange) -> Option<Decimal> {
match balance_change {
BalanceChange::Fungible(balance_change) => Some(*balance_change),
BalanceChange::NonFungible { .. } => None,
}
}

/// Retrofits the given transaction root, pretending it is an accumulator hash (for LTS purposes).
/// The transaction root and accumulator hash encode the same information and have the same
/// properties - only their computation differs.
Expand Down
Loading

0 comments on commit 18a5e2d

Please sign in to comment.