Scruffy Gingerbread Cormorant
Medium
State mutation in a gRPC query endpoint will cause network-wide state divergence for Babylon blockchain as anyone can trigger unintended state changes by simply querying the RewardGauges
endpoint, leading to inconsistent state.
In https://github.com/sherlock-audit/2024-12-babylon/blob/main/babylon/x/incentive/keeper/grpc_query.go#L30, the query endpoint RewardGauges
calls k.sendAllBtcDelegationTypeToRewardsGauge(ctx, sType, address)
which triggers a chain of state mutations. This violates the fundamental Cosmos SDK principle that query endpoints must be read-only. The call path leads to IncrementFinalityProviderPeriod()
which writes to the KVStore by updating historical rewards and incrementing period counters.
func (k Keeper) RewardGauges(goCtx context.Context, req *types.QueryRewardGaugesRequest) (*types.QueryRewardGaugesResponse, error) {
// ...
for _, sType := range types.GetAllStakeholderTypes() {
if err := k.sendAllBtcDelegationTypeToRewardsGauge(ctx, sType, address); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
// ...
}
// ...
}
The chain of function calls that lead to state mutations:
RewardGauges
→ sendAllBtcDelegationTypeToRewardsGauge
→ sendAllBtcRewardsToGauge
→ btcDelegationModified
→ btcDelegationModifiedWithPreInitDel
→ IncrementFinalityProviderPeriod
→ setFinalityProviderHistoricalRewards
and setFinalityProviderCurrentRewards
The state mutation results in different reward calculations across nodes because:
-
Inconsistent Period Incrementation: Nodes that process queries increment finality provider periods, while others do not, causing rewards to be distributed across different period structures.
-
Different CumulativeRewardsPerSat Values: The critical calculation in
IncrementFinalityProviderPeriod()
that determines rewards is:currentRewardsPerSatWithDecimals := fpCurrentRwd.CurrentRewards.MulInt(types.DecimalAccumulatedRewards) currentRewardsPerSat = currentRewardsPerSatWithDecimals.QuoInt(fpCurrentRwd.TotalActiveSat)
This division operation with truncation produces different results when rewards are split across multiple periods versus accumulated in a single period.
-
Delegation-Specific Discrepancies: Different delegators with varying amounts of staked satoshis will experience different magnitudes of calculation errors since:
- Each delegator's reward is calculated by:
rewardsWithDecimals := differenceWithDecimals.MulInt(btcDelRwdTracker.TotalActiveSat)
- The initial error in
CumulativeRewardsPerSat
is multiplied by each delegator's specific stake amount - These individual calculations are then truncated again with
rewards := rewardsWithDecimals.QuoInt(types.DecimalAccumulatedRewards)
- Each delegator's reward is calculated by:
This creates a system where identical delegators may receive different rewards depending on which node processes their transactions, with the discrepancy varying based on delegation amount and network query patterns.
NA
NA
- Any user queries the
RewardGauges
endpoint for a specific delegator address - The query triggers
sendAllBtcDelegationTypeToRewardsGauge
which processes all delegations for that address - For each delegation,
IncrementFinalityProviderPeriod
is called which:- Calculates the current period's rewards per satoshi
- Adds this to historical cumulative rewards for the finality provider
- Stores this updated historical record
- Creates a new period with zero rewards
- Updates current period number
- Node state diverges as some nodes process these queries and others don't
- Subsequent reward calculations use inconsistent data across the network
The protocol may suffer from consensus failures. When withdrawal transactions are processed, validators will calculate different reward amounts based on their divergent states, resulting in different state hashes after transaction execution. This will cause validators to reject each other's blocks, potentially halting the network.
No response
No response