From 2d423a8e7c46373162897a8425659d5ad7162bcf Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Fri, 15 Nov 2024 13:22:53 +0100 Subject: [PATCH] Add validation that there is only one fp key in delegation (#270) Fixes: https://github.com/babylonlabs-io/babylon/issues/269 In phase-2 every delegation must have only one finality provider. Adds: - validation - test cases --- CHANGELOG.md | 6 + .../types/create_delegation_parser.go | 4 + x/btcstaking/types/errors.go | 15 +-- .../types/validate_parsed_message_test.go | 119 +++++++++++++----- 4 files changed, 107 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a042e5b09..8de32b28f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## Unreleased + +### Bug fixes + +- [#270](https://github.com/babylonlabs-io/babylon/pull/270) Validate there is only +one finality provider key in the staking request + ## v0.16.0 ### Improvements diff --git a/x/btcstaking/types/create_delegation_parser.go b/x/btcstaking/types/create_delegation_parser.go index a00fa5640..23a775aaf 100644 --- a/x/btcstaking/types/create_delegation_parser.go +++ b/x/btcstaking/types/create_delegation_parser.go @@ -231,6 +231,10 @@ func ParseCreateDelegationMessage(msg *MsgCreateBTCDelegation) (*ParsedCreateDel return nil, ErrDuplicatedFp } + if len(fpPKs.PublicKeysBbnFormat) != 1 { + return nil, ErrTooManyFpKeys + } + // 7. Parse staker public key stakerPK, err := NewParsedPublicKey(msg.BtcPk) diff --git a/x/btcstaking/types/errors.go b/x/btcstaking/types/errors.go index 88c973707..a213d28ae 100644 --- a/x/btcstaking/types/errors.go +++ b/x/btcstaking/types/errors.go @@ -22,11 +22,12 @@ var ( ErrInvalidDelegationState = errorsmod.Register(ModuleName, 1113, "Unexpected delegation state") ErrInvalidUnbondingTx = errorsmod.Register(ModuleName, 1114, "the BTC unbonding tx is not valid") ErrEmptyFpList = errorsmod.Register(ModuleName, 1115, "the finality provider list is empty") - ErrInvalidProofOfPossession = errorsmod.Register(ModuleName, 1116, "the proof of possession is not valid") - ErrDuplicatedFp = errorsmod.Register(ModuleName, 1117, "the staking request contains duplicated finality provider public key") - ErrInvalidBTCUndelegateReq = errorsmod.Register(ModuleName, 1118, "invalid undelegation request") - ErrParamsNotFound = errorsmod.Register(ModuleName, 1119, "the parameters are not found") - ErrFpAlreadyJailed = errorsmod.Register(ModuleName, 1120, "the finality provider has already been jailed") - ErrFpNotJailed = errorsmod.Register(ModuleName, 1121, "the finality provider is not jailed") - ErrDuplicatedCovenantSig = errorsmod.Register(ModuleName, 1122, "the covenant signature is already submitted") + ErrTooManyFpKeys = errorsmod.Register(ModuleName, 1116, "the finality provider list contains too many public keys, it must contain exactly one") + ErrInvalidProofOfPossession = errorsmod.Register(ModuleName, 1117, "the proof of possession is not valid") + ErrDuplicatedFp = errorsmod.Register(ModuleName, 1118, "the staking request contains duplicated finality provider public key") + ErrInvalidBTCUndelegateReq = errorsmod.Register(ModuleName, 1119, "invalid undelegation request") + ErrParamsNotFound = errorsmod.Register(ModuleName, 1120, "the parameters are not found") + ErrFpAlreadyJailed = errorsmod.Register(ModuleName, 1121, "the finality provider has already been jailed") + ErrFpNotJailed = errorsmod.Register(ModuleName, 1122, "the finality provider is not jailed") + ErrDuplicatedCovenantSig = errorsmod.Register(ModuleName, 1123, "the covenant signature is already submitted") ) diff --git a/x/btcstaking/types/validate_parsed_message_test.go b/x/btcstaking/types/validate_parsed_message_test.go index 927b534e9..5c65e2b1c 100644 --- a/x/btcstaking/types/validate_parsed_message_test.go +++ b/x/btcstaking/types/validate_parsed_message_test.go @@ -233,9 +233,10 @@ func createMsgDelegationForParams( func TestValidateParsedMessageAgainstTheParams(t *testing.T) { tests := []struct { - name string - fn func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) - err error + name string + fn func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) + errParsing error + errValidation error }{ { name: "valid create delegation message", @@ -246,7 +247,36 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { return msg, params, checkpointParams }, - err: nil, + errParsing: nil, + errValidation: nil, + }, + { + name: "empty finality provider list", + fn: func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) { + params := testStakingParams(r, t) + checkpointParams := testCheckpointParams() + msg, _ := createMsgDelegationForParams(r, t, params, checkpointParams) + + msg.FpBtcPkList = []bbn.BIP340PubKey{} + + return msg, params, checkpointParams + }, + errParsing: types.ErrEmptyFpList, + errValidation: nil, + }, + { + name: "too many finality providers", + fn: func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) { + params := testStakingParams(r, t) + checkpointParams := testCheckpointParams() + msg, _ := createMsgDelegationForParams(r, t, params, checkpointParams) + + msg.FpBtcPkList = append(msg.FpBtcPkList, *msg.BtcPk) + + return msg, params, checkpointParams + }, + errParsing: types.ErrTooManyFpKeys, + errValidation: nil, }, { name: "too low unbonding time", @@ -259,7 +289,8 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { return msg, params, checkpointParams }, - err: types.ErrInvalidUnbondingTx, + errParsing: nil, + errValidation: types.ErrInvalidUnbondingTx, }, { name: "Msg.BtcPk do not match pk in staking transaction", @@ -277,7 +308,8 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { return msg, params, checkpointParams }, - err: types.ErrInvalidStakingTx, + errParsing: nil, + errValidation: types.ErrInvalidStakingTx, }, { name: "Msg.StakingTime do not match staking time committed in staking transaction", @@ -290,7 +322,8 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { return msg, params, checkpointParams }, - err: types.ErrInvalidStakingTx, + errParsing: nil, + errValidation: types.ErrInvalidStakingTx, }, { name: "Msg.StakingValue do not match staking value committed in staking transaction", @@ -303,7 +336,8 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { return msg, params, checkpointParams }, - err: types.ErrInvalidStakingTx.Wrap("staking tx does not contain expected staking output"), + errParsing: nil, + errValidation: types.ErrInvalidStakingTx.Wrap("staking tx does not contain expected staking output"), }, { name: "Msg.StakingValue is lower than params.MinStakingValueSat", @@ -316,7 +350,8 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { return msg, params, checkpointParams }, - err: types.ErrInvalidStakingTx, + errParsing: nil, + errValidation: types.ErrInvalidStakingTx, }, { name: "Msg.StakingValue is higher than params.MinStakingValueSat", @@ -329,7 +364,8 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { return msg, params, checkpointParams }, - err: types.ErrInvalidStakingTx, + errParsing: nil, + errValidation: types.ErrInvalidStakingTx, }, { name: "Msg.StakingTime is lower than params.MinStakingTimeBlocks", @@ -369,7 +405,8 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { return msg, params, checkpointParams }, - err: types.ErrInvalidStakingTx, + errParsing: nil, + errValidation: types.ErrInvalidStakingTx, }, { name: "Msg.StakingTime is higher than params.MinStakingTimeBlocks", @@ -409,7 +446,8 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { return msg, params, checkpointParams }, - err: types.ErrInvalidStakingTx, + errParsing: nil, + errValidation: types.ErrInvalidStakingTx, }, { name: "Msg.StakingValue is lower than params.MinStakingValueSat", @@ -435,7 +473,8 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { return msg, params, checkpointParams }, - err: types.ErrInvalidStakingTx, + errParsing: nil, + errValidation: types.ErrInvalidStakingTx, }, { name: "Msg.StakingValue is higher than params.MaxStakingValueSat", @@ -461,7 +500,8 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { return msg, params, checkpointParams }, - err: types.ErrInvalidStakingTx, + errParsing: nil, + errValidation: types.ErrInvalidStakingTx, }, { name: "Msg.SlashingTx have invalid pk script", @@ -487,7 +527,8 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { return msg, params, checkpointParams }, - err: types.ErrInvalidStakingTx, + errParsing: nil, + errValidation: types.ErrInvalidStakingTx, }, { name: "Msg.SlashingTx does not point to staking tx hash", @@ -514,7 +555,8 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { return msg, params, checkpointParams }, - err: types.ErrInvalidStakingTx, + errParsing: nil, + errValidation: types.ErrInvalidStakingTx, }, { name: "Msg.SlashingTx does not point to staking tx output index", @@ -534,7 +576,8 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { return msg, params, checkpointParams }, - err: types.ErrInvalidStakingTx, + errParsing: nil, + errValidation: types.ErrInvalidStakingTx, }, { name: "Msg.DelegatorSlashingSig is invalid signature", @@ -557,7 +600,8 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { return msg, params, checkpointParams }, - err: types.ErrInvalidSlashingTx, + errParsing: nil, + errValidation: types.ErrInvalidSlashingTx, }, { name: "Msg.UnbondingSlashingTx does not point to unbonding tx hash", @@ -584,7 +628,8 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { return msg, params, checkpointParams }, - err: types.ErrInvalidUnbondingTx, + errParsing: nil, + errValidation: types.ErrInvalidUnbondingTx, }, { name: "Msg.UnbondingSlashingTx does not point to unbonding tx output index", @@ -604,7 +649,8 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { return msg, params, checkpointParams }, - err: types.ErrInvalidUnbondingTx, + errParsing: nil, + errValidation: types.ErrInvalidUnbondingTx, }, { name: "Msg.UnbondingSlashingTx have invalid pk script", @@ -630,7 +676,8 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { return msg, params, checkpointParams }, - err: types.ErrInvalidUnbondingTx, + errParsing: nil, + errValidation: types.ErrInvalidUnbondingTx, }, { name: "Msg.DelegatorUnbondingSlashingSig is invalid signature", @@ -653,7 +700,8 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { return msg, params, checkpointParams }, - err: types.ErrInvalidSlashingTx, + errParsing: nil, + errValidation: types.ErrInvalidSlashingTx, }, { name: "Msg.UnbondingTx does not point to staking tx hash", @@ -691,7 +739,8 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { return msg, params, checkpointParams }, - err: types.ErrInvalidUnbondingTx, + errParsing: nil, + errValidation: types.ErrInvalidUnbondingTx, }, { name: "Msg.UnbondingTx does not point to staking tx output index", @@ -722,7 +771,8 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { return msg, params, checkpointParams }, - err: types.ErrInvalidUnbondingTx, + errParsing: nil, + errValidation: types.ErrInvalidUnbondingTx, }, { name: "Msg.UnbondingTx does not have required fee", @@ -756,7 +806,8 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { return msg, params, checkpointParams }, - err: types.ErrInvalidUnbondingTx, + errParsing: nil, + errValidation: types.ErrInvalidUnbondingTx, }, { name: "Msg.UnbondingTx has more than one output", @@ -778,7 +829,8 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { return msg, params, checkpointParams }, - err: types.ErrInvalidUnbondingTx.Wrap("unbonding tx is not a valid pre-signed transaction: tx must have exactly 1 outputs"), + errParsing: nil, + errValidation: types.ErrInvalidUnbondingTx.Wrap("unbonding tx is not a valid pre-signed transaction: tx must have exactly 1 outputs"), }, { name: "Msg.UnbondingTx unbonding value in the msg does not match the output value in the unbonding tx", @@ -811,7 +863,8 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { return msg, params, checkpointParams }, - err: types.ErrInvalidUnbondingTx.Wrap("the unbonding output value is not expected"), + errParsing: nil, + errValidation: types.ErrInvalidUnbondingTx.Wrap("the unbonding output value is not expected"), }, } for _, tt := range tests { @@ -821,8 +874,14 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { msg, params, checkpointParams := tt.fn(r, t) parsed, err := types.ParseCreateDelegationMessage(msg) - require.NoError(t, err) + if tt.errParsing != nil { + require.Error(t, err) + require.ErrorAs(t, err, &tt.errParsing) + return + } + + require.NoError(t, err) got, err := types.ValidateParsedMessageAgainstTheParams( parsed, params, @@ -830,9 +889,9 @@ func TestValidateParsedMessageAgainstTheParams(t *testing.T) { &chaincfg.MainNetParams, ) - if tt.err != nil { + if tt.errValidation != nil { require.Error(t, err) - require.ErrorAs(t, err, &tt.err) + require.ErrorAs(t, err, &tt.errValidation) } else { require.NoError(t, err) require.NotNil(t, got)