Brilliant Umber Eagle
Medium
Anyone can front-run CreateFinalityProvider
tx.
When any fp(finality provider) creates an fp to Babylon chain by calling CreateFinalityProvider
, it verifies the data fields here, if the sign is verified success, the fp can be added to the chain db. After that, if the same BtcPk
is created again by the same function, the latter tx can fail because here, such that any malicious user can front-run the legit fp create a tx to modify the necessary fields such that Commission
and Description
. The commission represents a percentage of rewards that fp takes from the delegators' rewards as a fee for running and maintaining their validation services, any user can set it to a smaller value than the fp wanted to reduce the fp commission fee.
None.
None.
- Legit fp wants to create the fp to the Babylon chain with correct commission and desc.
- Malicious users monitor the chain mempool and front-run the fp tx but modify the commission by sending the tx with cosmos grpc message instead of cmd client.
- The front-run tx executes successfully.
- The legit tx execute fails because the
BtcPk
is already registered in the chain db.
The fp CreateFinalityProvider
tx can be front-ran by any user to modify the commission parameter to reduce the fp commission fee, such that cause the fp may loss some commission fee until the commission be edited by EditFinalityProvider
function. The fp may loss more if they dont notice that in a long time. Due to it can be edited at any time, so it should be loss funds temporiry and deserve medium severity.
func TestMsgCreateFinalityProviderFrontRun(t *testing.T) {
// Create a new random source with fixed seed for reproducibility
r := rand.New(rand.NewSource(1))
ctrl := gomock.NewController(t)
defer ctrl.Finish()
// mock BTC light client and BTC checkpoint modules
btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl)
btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl)
h := testutil.NewHelper(t, btclcKeeper, btccKeeper)
// set all parameters
h.GenAndApplyParams(r)
// Test creating a finality provider with empty commission
fp, err := datagen.GenRandomFinalityProvider(r)
require.NoError(t, err)
// Set commission to 0.1
commission, err := sdkmath.LegacyNewDecFromStr("0.01")
require.NoError(t, err)
msg := &types.MsgCreateFinalityProvider{
Addr: fp.Addr,
Description: fp.Description,
Commission: &commission,
BtcPk: fp.BtcPk,
Pop: fp.Pop,
}
// First creation should succeed
_, err = h.MsgServer.CreateFinalityProvider(h.Ctx, msg)
require.NoError(t, err)
// Verify finality provider exists in store
btcPK := *fp.BtcPk
require.True(t, h.BTCStakingKeeper.HasFinalityProvider(h.Ctx, btcPK))
// Attempt to create duplicate finality provider
duplicateMsg := &types.MsgCreateFinalityProvider{
Addr: fp.Addr,
Description: fp.Description,
Commission: fp.Commission,
BtcPk: fp.BtcPk,
Pop: fp.Pop,
}
// Duplicate creation should fail
_, err = h.MsgServer.CreateFinalityProvider(h.Ctx, duplicateMsg)
require.Error(t, err)
// Get stored finality provider and verify commission
storedFp, err := h.BTCStakingKeeper.GetFinalityProvider(h.Ctx, btcPK)
require.NoError(t, err)
require.Equal(t, commission, *storedFp.Commission)
}
- It's better to recognize the context sender when validating the fields.
- Sign the address, commission, description, add nonce as well such as ethereum permit function, verify it when creating the fp.