diff --git a/bridge/setu/processor/slashing.go b/bridge/setu/processor/slashing.go index 08753ad38..385d3fb0f 100644 --- a/bridge/setu/processor/slashing.go +++ b/bridge/setu/processor/slashing.go @@ -182,6 +182,7 @@ func (sp *SlashingProcessor) sendTickAckToHeimdall(eventName string, logBytes st "totalSlashedAmount", event.Amount, "txHash", hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), "logIndex", uint64(vLog.Index), + "blockNumber", vLog.BlockNumber, ) return nil } @@ -191,12 +192,13 @@ func (sp *SlashingProcessor) sendTickAckToHeimdall(eventName string, logBytes st "totalSlashedAmount", event.Amount, "txHash", hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), "logIndex", uint64(vLog.Index), + "blockNumber", vLog.BlockNumber, ) // TODO - check if i am the proposer of this tick ack or not. // create msg checkpoint ack message - msg := slashingTypes.NewMsgTickAck(helper.GetFromAddress(sp.cliCtx), hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), uint64(vLog.Index)) + msg := slashingTypes.NewMsgTickAck(helper.GetFromAddress(sp.cliCtx), event.Amount, hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), uint64(vLog.Index), vLog.BlockNumber) // return broadcast to heimdall if err := sp.txBroadcaster.BroadcastToHeimdall(msg); err != nil { @@ -228,6 +230,7 @@ func (sp *SlashingProcessor) sendUnjailToHeimdall(eventName string, logBytes str "ValidatorID", event.ValidatorId, "txHash", hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), "logIndex", uint64(vLog.Index), + "blockNumber", vLog.BlockNumber, ) return nil } @@ -237,6 +240,7 @@ func (sp *SlashingProcessor) sendUnjailToHeimdall(eventName string, logBytes str "ValidatorID", event.ValidatorId, "txHash", hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), "logIndex", uint64(vLog.Index), + "blockNumber", vLog.BlockNumber, ) // TODO - check if i am the proposer of unjail or not. @@ -247,6 +251,7 @@ func (sp *SlashingProcessor) sendUnjailToHeimdall(eventName string, logBytes str event.ValidatorId.Uint64(), hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), uint64(vLog.Index), + vLog.BlockNumber, ) // return broadcast to heimdall diff --git a/slashing/client/cli/flags.go b/slashing/client/cli/flags.go index 32e263396..813e44547 100644 --- a/slashing/client/cli/flags.go +++ b/slashing/client/cli/flags.go @@ -6,5 +6,7 @@ const ( FlagValidatorID = "id" FlagTxHash = "tx-hash" FlagLogIndex = "log-index" + FlagAmount = "slashed-amount" FlagSlashInfoHash = "slashinfo-hash" + FlagBlockNumber = "block-number" ) diff --git a/slashing/client/cli/tx.go b/slashing/client/cli/tx.go index 8fe557746..eefca75eb 100644 --- a/slashing/client/cli/tx.go +++ b/slashing/client/cli/tx.go @@ -1,7 +1,9 @@ package cli import ( + "errors" "fmt" + "math/big" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" @@ -16,6 +18,8 @@ import ( hmTypes "github.com/maticnetwork/heimdall/types" ) +var logger = helper.Logger.With("module", "staking/client/cli") + func GetTxCmd(cdc *codec.Codec) *cobra.Command { slashingTxCmd := &cobra.Command{ Use: types.ModuleName, @@ -67,6 +71,7 @@ $ tx slashing unjail --from mykey uint64(validator), hmTypes.HexToHeimdallHash(txHash), uint64(viper.GetInt64(FlagLogIndex)), + viper.GetUint64(FlagBlockNumber), ) // broadcast messages @@ -75,9 +80,12 @@ $ tx slashing unjail --from mykey } cmd.Flags().StringP(FlagProposerAddress, "p", "", "--proposer=") cmd.Flags().String(FlagTxHash, "", "--tx-hash=") + cmd.Flags().Uint64(FlagBlockNumber, 0, "--block-number=") cmd.MarkFlagRequired(FlagProposerAddress) cmd.MarkFlagRequired(FlagTxHash) - + if err := cmd.MarkFlagRequired(FlagBlockNumber); err != nil { + logger.Error("SendValidatorJoinTx | MarkFlagRequired | FlagBlockNumber", "Error", err) + } return cmd } @@ -139,10 +147,17 @@ func GetCmdTickAck(cdc *codec.Codec) *cobra.Command { return fmt.Errorf("transaction hash is required") } + amount, ok := big.NewInt(0).SetString(viper.GetString(FlagAmount), 10) + if !ok { + return errors.New("Invalid stake amount") + } + msg := types.NewMsgTickAck( proposer, + amount, hmTypes.HexToHeimdallHash(txHash), uint64(viper.GetInt64(FlagLogIndex)), + viper.GetUint64(FlagBlockNumber), ) // broadcast messages @@ -152,10 +167,17 @@ func GetCmdTickAck(cdc *codec.Codec) *cobra.Command { cmd.Flags().StringP(FlagProposerAddress, "p", "", "--proposer=") cmd.Flags().String(FlagTxHash, "", "--tx-hash=") + cmd.Flags().Uint64(FlagBlockNumber, 0, "--block-number=") cmd.Flags().String(FlagLogIndex, "", "--log-index=") + cmd.Flags().String(FlagAmount, "0", "--amount=") + + if err := cmd.MarkFlagRequired(FlagBlockNumber); err != nil { + logger.Error("SendValidatorJoinTx | MarkFlagRequired | FlagBlockNumber", "Error", err) + } cmd.MarkFlagRequired(FlagProposerAddress) cmd.MarkFlagRequired(FlagTxHash) cmd.MarkFlagRequired(FlagLogIndex) + cmd.MarkFlagRequired(FlagAmount) return cmd } diff --git a/slashing/client/rest/tx.go b/slashing/client/rest/tx.go index 3e88a9e87..b4153d2bb 100644 --- a/slashing/client/rest/tx.go +++ b/slashing/client/rest/tx.go @@ -1,6 +1,7 @@ package rest import ( + "math/big" "net/http" "github.com/cosmos/cosmos-sdk/client/context" @@ -35,23 +36,24 @@ func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) { type UnjailReq struct { BaseReq rest.BaseReq `json:"base_req"` - ID uint64 `json:"ID"` - TxHash string `json:"tx_hash"` - LogIndex uint64 `json:"log_index"` + ID uint64 `json:"ID"` + TxHash string `json:"tx_hash"` + LogIndex uint64 `json:"log_index"` + BlockNumber uint64 `json:"block_number" yaml:"block_number"` } type TickReq struct { - BaseReq rest.BaseReq `json:"base_req"` - - Proposer string `json:"proposer"` - SlashingInfoHash string `json:"slashing_info_hash"` + BaseReq rest.BaseReq `json:"base_req"` + Proposer string `json:"proposer"` + SlashingInfoHash string `json:"slashing_info_hash"` } type TickAckReq struct { - BaseReq rest.BaseReq `json:"base_req"` - - TxHash string `json:"tx_hash"` - LogIndex uint64 `json:"log_index"` + BaseReq rest.BaseReq `json:"base_req"` + Amount string `json:"amount"` + TxHash string `json:"tx_hash"` + LogIndex uint64 `json:"log_index"` + BlockNumber uint64 `json:"block_number" yaml:"block_number"` } func newUnjailRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { @@ -73,6 +75,7 @@ func newUnjailRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { req.ID, hmTypes.HexToHeimdallHash(req.TxHash), req.LogIndex, + req.BlockNumber, ) err := msg.ValidateBasic() if err != nil { @@ -111,10 +114,17 @@ func newTickAckHandler(cliCtx context.CLIContext) http.HandlerFunc { return } + amount, ok := big.NewInt(0).SetString(req.Amount, 10) + if !ok { + rest.WriteErrorResponse(w, http.StatusBadRequest, "invalid amount") + } + msg := types.NewMsgTickAck( hmTypes.HexToHeimdallAddress(req.BaseReq.From), + amount, hmTypes.HexToHeimdallHash(req.TxHash), req.LogIndex, + req.BlockNumber, ) restClient.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg}) diff --git a/slashing/handler.go b/slashing/handler.go index f3ef4b740..d7557e784 100644 --- a/slashing/handler.go +++ b/slashing/handler.go @@ -1,27 +1,165 @@ package slashing import ( + "bytes" + "fmt" + "math/big" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/maticnetwork/heimdall/common" "github.com/maticnetwork/heimdall/helper" + "github.com/maticnetwork/heimdall/slashing/types" + hmTypes "github.com/maticnetwork/heimdall/types" + + hmCommon "github.com/maticnetwork/heimdall/common" ) // NewHandler creates an sdk.Handler for all the slashing type messages func NewHandler(k Keeper, contractCaller helper.IContractCaller) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { ctx = ctx.WithEventManager(sdk.NewEventManager()) - return sdk.ErrTxDecode("Invalid message in slashing module").Result() - // switch msg := msg.(type) { -/* case types.MsgUnjail: - return handleMsgUnjail(ctx, msg, k, contractCaller) + + switch msg := msg.(type) { case types.MsgTick: return handlerMsgTick(ctx, msg, k, contractCaller) case types.MsgTickAck: - return handleMsgTickAck(ctx, msg, k, contractCaller) */ - // default: - // return sdk.ErrTxDecode("Invalid message in slashing module").Result() - // } + return handleMsgTickAck(ctx, msg, k, contractCaller) + case types.MsgUnjail: + return handleMsgUnjail(ctx, msg, k, contractCaller) + default: + return sdk.ErrTxDecode("Invalid message in slashing module").Result() + } } } + +// handlerMsgTick - handles slashing of validators +// 0. check if slashLimit is exceeded or not. +// 1. Validate input slashing info hash data +// 2. If hash matches, copy slashBuffer into latestTickData +// 3. flushes slashBuffer, totalSlashedAmount +// 4. iterate and reduce the power of slashed validators. +// 5. Also update the jailStatus of Validator +// 6. emit event TickConfirmation +func handlerMsgTick(ctx sdk.Context, msg types.MsgTick, k Keeper, contractCaller helper.IContractCaller) sdk.Result { + + k.Logger(ctx).Debug("✅ Validating tick msg", + "SlashInfoHash", msg.SlashingInfoHash, + ) + + // check if slash limit is exceeded or not + if !k.IsSlashedLimitExceeded(ctx) { + k.Logger(ctx).Error("TotalSlashedAmount is less than SlashLimit") + return hmCommon.ErrInvalidMsg(k.Codespace(), fmt.Sprintf("TotalSlashedAmount %v is less than SlashLimit", k.GetTotalSlashedAmount(ctx))).Result() + } + + valSlashingInfos, err := k.GetBufferValSlashingInfos(ctx) + if err != nil { + k.Logger(ctx).Error("Error fetching slash Info list from buffer", "error", err) + return hmCommon.ErrSlashInfoDetails(k.Codespace()).Result() + } + + slashingInfoHash, err := types.GetSlashingInfoHash(valSlashingInfos) + if err != nil { + k.Logger(ctx).Info("Error generating slashing info hash", "error", err) + return hmCommon.ErrSlashInfoDetails(k.Codespace()).Result() + } + + // compare slashingInfoHash with msg hash + k.Logger(ctx).Info("SlashInfo hash generated", "SlashInfoHash", hmTypes.BytesToHeimdallHash(slashingInfoHash).String()) + + if !bytes.Equal(slashingInfoHash, msg.SlashingInfoHash.Bytes()) { + k.Logger(ctx).Error("SlashInfoHash of current buffer state", "bufferSlashInfoHash", hmTypes.BytesToHeimdallHash(slashingInfoHash).String(), + "doesn't match with SlashInfoHash of msg", "msgSlashInfoHash", msg.SlashingInfoHash) + return hmCommon.ErrSlashInfoDetails(k.Codespace()).Result() + } + + k.Logger(ctx).Debug("SlashInfoHash matches") + + // ensure latestTickData is empty + tickSlashingInfos, err := k.GetTickValSlashingInfos(ctx) + if err != nil { + k.Logger(ctx).Error("Error fetching slash Info list from tick", "error", err) + return common.ErrSlashInfoDetails(k.Codespace()).Result() + } + + if tickSlashingInfos != nil && len(tickSlashingInfos) > 0 { + k.Logger(ctx).Error("Waiting for tick data to be pushed to contract", "tickSlashingInfo", tickSlashingInfos) + return common.ErrSlashInfoDetails(k.Codespace()).Result() + } + + return sdk.Result{ + Events: ctx.EventManager().Events(), + } +} + +// Validators must submit a transaction to unjail itself after +// having been jailed (and thus unbonded) for downtime +func handleMsgUnjail(ctx sdk.Context, msg types.MsgUnjail, k Keeper, contractCaller helper.IContractCaller) sdk.Result { + + k.Logger(ctx).Debug("✅ Validating unjail msg", + "validatorId", msg.ID, + "txHash", hmTypes.BytesToHeimdallHash(msg.TxHash.Bytes()), + "logIndex", uint64(msg.LogIndex), + "blockNumber", msg.BlockNumber, +) + + // sequence id + blockNumber := new(big.Int).SetUint64(msg.BlockNumber) + sequence := new(big.Int).Mul(blockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) + sequence.Add(sequence, new(big.Int).SetUint64(msg.LogIndex)) + + // check if incoming tx is older + if k.HasSlashingSequence(ctx, sequence.String()) { + k.Logger(ctx).Error("Older invalid tx found") + return hmCommon.ErrOldTx(k.Codespace()).Result() + } + + // pull validator from store + validator, ok := k.sk.GetValidatorFromValID(ctx, msg.ID) + if !ok { + k.Logger(ctx).Error("Fetching of validator from store failed", "validatorId", msg.ID) + return hmCommon.ErrNoValidator(k.Codespace()).Result() + } + + + if !validator.Jailed { + k.Logger(ctx).Error("Fetching of validator from store failed", "validatorId", msg.ID) + return hmCommon.ErrNoValidator(k.Codespace()).Result() + } + return sdk.Result{ + Events: ctx.EventManager().Events(), + } +} + + +/* + handleMsgTickAck - handle msg tick ack event + 1. validate the tx hash in the event + 2. flush the last tick slashing info +*/ +func handleMsgTickAck(ctx sdk.Context, msg types.MsgTickAck, k Keeper, contractCaller helper.IContractCaller) sdk.Result { + + k.Logger(ctx).Debug("✅ Validating TickAck msg", + "SlashedAmount", msg.SlashedAmount, + "txHash", hmTypes.BytesToHeimdallHash(msg.TxHash.Bytes()), + "logIndex", uint64(msg.LogIndex), + "blockNumber", msg.BlockNumber, + ) + + // sequence id + blockNumber := new(big.Int).SetUint64(msg.BlockNumber) + sequence := new(big.Int).Mul(blockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) + sequence.Add(sequence, new(big.Int).SetUint64(msg.LogIndex)) + + // check if incoming tx is older + if k.HasSlashingSequence(ctx, sequence.String()) { + k.Logger(ctx).Error("Older invalid tx found") + return hmCommon.ErrOldTx(k.Codespace()).Result() + } + + return sdk.Result{ + Events: ctx.EventManager().Events(), + } +} diff --git a/slashing/side_handler.go b/slashing/side_handler.go new file mode 100644 index 000000000..fb195f74c --- /dev/null +++ b/slashing/side_handler.go @@ -0,0 +1,306 @@ +package slashing + +import ( + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/maticnetwork/heimdall/common" + hmCommon "github.com/maticnetwork/heimdall/common" + "github.com/maticnetwork/heimdall/helper" + "github.com/maticnetwork/heimdall/slashing/types" + hmTypes "github.com/maticnetwork/heimdall/types" + tmTypes "github.com/tendermint/tendermint/types" +) + +// NewSideTxHandler returns a side handler for "topup" type messages. +func NewSideTxHandler(k Keeper, contractCaller helper.IContractCaller) hmTypes.SideTxHandler { + return func(ctx sdk.Context, msg sdk.Msg) abci.ResponseDeliverSideTx { + ctx = ctx.WithEventManager(sdk.NewEventManager()) + switch msg := msg.(type) { + case types.MsgTick: + return SideHandleMsgTick(ctx, k, msg, contractCaller) + case types.MsgTickAck: + return SideHandleMsgTickAck(ctx, k, msg, contractCaller) + case types.MsgUnjail: + return SideHandleMsgUnjail(ctx, k, msg, contractCaller) + default: + return abci.ResponseDeliverSideTx{ + Code: uint32(sdk.CodeUnknownRequest), + } + } + } +} + +// NewPostTxHandler returns a side handler for "bank" type messages. +func NewPostTxHandler(k Keeper, contractCaller helper.IContractCaller) hmTypes.PostTxHandler { + return func(ctx sdk.Context, msg sdk.Msg, sideTxResult abci.SideTxResultType) sdk.Result { + ctx = ctx.WithEventManager(sdk.NewEventManager()) + switch msg := msg.(type) { + case types.MsgTick: + return PostHandleMsgTick(ctx, k, msg, sideTxResult) + case types.MsgTickAck: + return PostHandleMsgTickAck(ctx, k, msg, sideTxResult) + default: + errMsg := "Unrecognized slash Msg type: %s" + msg.Type() + return sdk.ErrUnknownRequest(errMsg).Result() + } + } +} + +// SideHandleMsgTick handles MsgTick message for external call +func SideHandleMsgTick(ctx sdk.Context, k Keeper, msg types.MsgTick, contractCaller helper.IContractCaller) (result abci.ResponseDeliverSideTx) { + k.Logger(ctx).Debug("✅ Validating External call for tick msg") + k.Logger(ctx).Debug("✅ Succesfully validated External call for tick msg") + result.Result = abci.SideTxResultType_Yes + return +} + +// SideHandleMsgTick handles MsgTick message for external call +func SideHandleMsgTickAck(ctx sdk.Context, k Keeper, msg types.MsgTickAck, contractCaller helper.IContractCaller) (result abci.ResponseDeliverSideTx) { + k.Logger(ctx).Debug("✅ Validating External call for tick-ack msg", + "txHash", hmTypes.BytesToHeimdallHash(msg.TxHash.Bytes()), + "logIndex", uint64(msg.LogIndex), + "blockNumber", msg.BlockNumber, + ) + + // chainManager params + params := k.chainKeeper.GetParams(ctx) + chainParams := params.ChainParams + + // get main tx receipt + receipt, err := contractCaller.GetConfirmedTxReceipt(msg.TxHash.EthHash(), params.MainchainTxConfirmations) + if err != nil || receipt == nil { + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeWaitFrConfirmation) + } + + // get event log for slashed event + eventLog, err := contractCaller.DecodeSlashedEvent(chainParams.StakingInfoAddress.EthAddress(), receipt, msg.LogIndex) + if err != nil || eventLog == nil { + k.Logger(ctx).Error("Error fetching log from txhash") + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeErrDecodeEvent) + } + + if receipt.BlockNumber.Uint64() != msg.BlockNumber { + k.Logger(ctx).Error("BlockNumber in message doesn't match blocknumber in receipt", "MsgBlockNumber", msg.BlockNumber, "ReceiptBlockNumber", receipt.BlockNumber.Uint64) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + if eventLog.Amount.Cmp(msg.SlashedAmount) != 0 { + k.Logger(ctx).Error("SlashedAmount in message doesn't match SlashedAmount in event logs", "MsgSlashedAmount", msg.SlashedAmount, "SlashedAmountFromEvent", eventLog.Amount) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + k.Logger(ctx).Debug("✅ Succesfully validated External call for tick-ack msg") + result.Result = abci.SideTxResultType_Yes + return +} + +// SideHandleMsgUnjail handles MsgUnjail message for external call +func SideHandleMsgUnjail(ctx sdk.Context, k Keeper, msg types.MsgUnjail, contractCaller helper.IContractCaller) (result abci.ResponseDeliverSideTx) { + k.Logger(ctx).Debug("✅ Validating External call for unjail msg", + "txHash", hmTypes.BytesToHeimdallHash(msg.TxHash.Bytes()), + "logIndex", uint64(msg.LogIndex), + "blockNumber", msg.BlockNumber, + "validatorID", msg.ID, + ) + + // chainManager params + params := k.chainKeeper.GetParams(ctx) + chainParams := params.ChainParams + + // get main tx receipt + receipt, err := contractCaller.GetConfirmedTxReceipt(msg.TxHash.EthHash(), params.MainchainTxConfirmations) + if err != nil || receipt == nil { + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeWaitFrConfirmation) + } + + // get unjail event + eventLog, err := contractCaller.DecodeUnJailedEvent(chainParams.StakingInfoAddress.EthAddress(), receipt, msg.LogIndex) + if err != nil || eventLog == nil { + k.Logger(ctx).Error("Error fetching log from txhash") + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeErrDecodeEvent) + } + + if receipt.BlockNumber.Uint64() != msg.BlockNumber { + k.Logger(ctx).Error("BlockNumber in message doesn't match blocknumber in receipt", "MsgBlockNumber", msg.BlockNumber, "ReceiptBlockNumber", receipt.BlockNumber.Uint64) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + if eventLog.ValidatorId.Uint64() != msg.ID.Uint64() { + k.Logger(ctx).Error("ID in message doesn't match id in logs", "MsgID", msg.ID, "IdFromTx", eventLog.ValidatorId) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + k.Logger(ctx).Debug("✅ Succesfully validated External call for tick msg") + result.Result = abci.SideTxResultType_Yes + return +} + +// PostHandleMsgTick - handles slashing of validators +// 1. copy slashBuffer into latestTickData +// 2. flush slashBuffer, totalSlashedAmount +// 3. iterate and reduce the power of slashed validators. +// 4. Also update the jailStatus of Validator +// 5. emit event TickConfirmation +func PostHandleMsgTick(ctx sdk.Context, k Keeper, msg types.MsgTick, sideTxResult abci.SideTxResultType) sdk.Result { + + // Skip handler if tick is not approved + if sideTxResult != abci.SideTxResultType_Yes { + k.Logger(ctx).Debug("Skipping new tick since side-tx didn't get yes votes") + return common.ErrSideTxValidation(k.Codespace()).Result() + } + + k.Logger(ctx).Debug("Persisting tick state", "sideTxResult", sideTxResult) + + // copy slashBuffer into latestTickData + if err := k.CopyBufferValSlashingInfosToTickData(ctx); err != nil { + k.Logger(ctx).Error("Error copying bufferSlashInfo to tickSlashInfo", "error", err) + return common.ErrSlashInfoDetails(k.Codespace()).Result() + } + + // Flush slashBuffer + if err := k.FlushBufferValSlashingInfos(ctx); err != nil { + k.Logger(ctx).Error("Error flushing buffer slash info in tick handler", "error", err) + return common.ErrSlashInfoDetails(k.Codespace()).Result() + } + + // Flush TotalSlashedAmount + k.FlushTotalSlashedAmount(ctx) + + // slash validator - Iterate lastTickData and reduce power of each validator along with jailing if needed + if err := k.SlashAndJailTickValSlashingInfos(ctx); err != nil { + k.Logger(ctx).Error("Error slashing and jailing validator in tick handler", "error", err) + return common.ErrSlashInfoDetails(k.Codespace()).Result() + } + + k.Logger(ctx).Debug("Successfully slashed and jailed") + // TX bytes + txBytes := ctx.TxBytes() + hash := tmTypes.Tx(txBytes).Hash() + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeTickConfirm, + sdk.NewAttribute(sdk.AttributeKeyAction, msg.Type()), // action + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), // module name + sdk.NewAttribute(hmTypes.AttributeKeyTxHash, hmTypes.BytesToHeimdallHash(hash).Hex()), // tx hash + sdk.NewAttribute(hmTypes.AttributeKeySideTxResult, sideTxResult.String()), // result + sdk.NewAttribute(types.AttributeKeyProposer, msg.Proposer.String()), + sdk.NewAttribute(types.AttributeKeySlashInfoHash, msg.SlashingInfoHash.String()), + ), + }) + + return sdk.Result{ + Events: ctx.EventManager().Events(), + } +} + +// PostHandleMsgTickAck - handles slashing of validators +/* + handleMsgTickAck - handle msg tick ack event + 1. validate the tx hash in the event + 2. flush the last tick slashing info +*/ +func PostHandleMsgTickAck(ctx sdk.Context, k Keeper, msg types.MsgTickAck, sideTxResult abci.SideTxResultType) sdk.Result { + + // Skip handler if topup is not approved + if sideTxResult != abci.SideTxResultType_Yes { + k.Logger(ctx).Debug("Skipping new topup since side-tx didn't get yes votes") + return common.ErrSideTxValidation(k.Codespace()).Result() + } + + // check if incoming tx is older + blockNumber := new(big.Int).SetUint64(msg.BlockNumber) + sequence := new(big.Int).Mul(blockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) + sequence.Add(sequence, new(big.Int).SetUint64(msg.LogIndex)) + + if k.HasSlashingSequence(ctx, sequence.String()) { + k.Logger(ctx).Error("Older invalid tx found") + return hmCommon.ErrOldTx(k.Codespace()).Result() + } + + // remove validator slashing infos from tick data + if err := k.FlushTickValSlashingInfos(ctx); err != nil { + k.Logger(ctx).Error("Error flushing tick slash info in tick-ack handler", "error", err) + return common.ErrSlashInfoDetails(k.Codespace()).Result() + } + + k.Logger(ctx).Debug("Successfully flushed tick slash info in tick-ack handler") + + // save staking sequence + k.SetSlashingSequence(ctx, sequence.String()) + + // TX bytes + txBytes := ctx.TxBytes() + hash := tmTypes.Tx(txBytes).Hash() + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeTickAck, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeyAction, msg.Type()), // action + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), // module name + sdk.NewAttribute(hmTypes.AttributeKeyTxHash, hmTypes.BytesToHeimdallHash(hash).Hex()), // tx hash + sdk.NewAttribute(hmTypes.AttributeKeySideTxResult, sideTxResult.String()), // result + ), + ) + + return sdk.Result{ + Events: ctx.EventManager().Events(), + } +} + +// Validators must submit a transaction to unjail itself after +// having been jailed (and thus unbonded) for downtime +func PostHandleMsgUnjail(ctx sdk.Context, k Keeper, msg types.MsgUnjail, sideTxResult abci.SideTxResultType) sdk.Result { + // Skip handler if topup is not approved + if sideTxResult != abci.SideTxResultType_Yes { + k.Logger(ctx).Debug("Skipping new topup since side-tx didn't get yes votes") + return common.ErrSideTxValidation(k.Codespace()).Result() + } + + // check if incoming tx is older + blockNumber := new(big.Int).SetUint64(msg.BlockNumber) + sequence := new(big.Int).Mul(blockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) + sequence.Add(sequence, new(big.Int).SetUint64(msg.LogIndex)) + + if k.HasSlashingSequence(ctx, sequence.String()) { + k.Logger(ctx).Error("Older invalid tx found") + return hmCommon.ErrOldTx(k.Codespace()).Result() + } + + k.Logger(ctx).Debug("Persisting jail status", "sideTxResult", sideTxResult) + + // unjail validator + k.sk.Unjail(ctx, msg.ID) + + // check if unjail is successful or not + val, _ := k.sk.GetValidatorFromValID(ctx, msg.ID) + if val.Jailed { + k.Logger(ctx).Error("Error unjailing validator", "validatorId", msg.ID, "jailStatus", val.Jailed) + return hmCommon.ErrUnjailValidator(k.Codespace()).Result() + } + + // save staking sequence + k.SetSlashingSequence(ctx, sequence.String()) + + // TX bytes + txBytes := ctx.TxBytes() + hash := tmTypes.Tx(txBytes).Hash() + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeUnjail, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeyAction, msg.Type()), // action + sdk.NewAttribute(hmTypes.AttributeKeyTxHash, hmTypes.BytesToHeimdallHash(hash).Hex()), // tx hash + sdk.NewAttribute(hmTypes.AttributeKeySideTxResult, sideTxResult.String()), // result + ), + ) + + return sdk.Result{ + Events: ctx.EventManager().Events(), + } +} diff --git a/slashing/types/msg.go b/slashing/types/msg.go index 68f02316f..7252bbdb8 100644 --- a/slashing/types/msg.go +++ b/slashing/types/msg.go @@ -2,6 +2,7 @@ package types import ( "bytes" + "math/big" sdk "github.com/cosmos/cosmos-sdk/types" hmCommon "github.com/maticnetwork/heimdall/common" @@ -15,18 +16,20 @@ var _ sdk.Msg = &MsgUnjail{} // MsgUnjail - struct for unjailing jailed validator type MsgUnjail struct { - From types.HeimdallAddress `json:"from"` - ID hmTypes.ValidatorID `json:"id"` - TxHash types.HeimdallHash `json:"tx_hash"` - LogIndex uint64 `json:"log_index"` + From types.HeimdallAddress `json:"from"` + ID hmTypes.ValidatorID `json:"id"` + TxHash types.HeimdallHash `json:"tx_hash"` + LogIndex uint64 `json:"log_index"` + BlockNumber uint64 `json:"block_number"` } -func NewMsgUnjail(from types.HeimdallAddress, id uint64, txHash types.HeimdallHash, logIndex uint64) MsgUnjail { +func NewMsgUnjail(from types.HeimdallAddress, id uint64, txHash types.HeimdallHash, logIndex uint64, blockNumber uint64) MsgUnjail { return MsgUnjail{ - From: from, - ID: hmTypes.NewValidatorID(id), - TxHash: txHash, - LogIndex: logIndex, + From: from, + ID: hmTypes.NewValidatorID(id), + TxHash: txHash, + LogIndex: logIndex, + BlockNumber: blockNumber, } } @@ -106,6 +109,17 @@ func (msg MsgTick) ValidateBasic() sdk.Error { return nil } +// GetSideSignBytes returns side sign bytes +func (msg MsgTick) GetSideSignBytes() []byte { + // keccak256(abi.encoded(proposer, startBlock, endBlock, rootHash, accountRootHash, bor chain id)) + /* return appendBytes32( + msg.Proposer.Bytes(), + msg.SlashingInfoHash.Bytes(), + ) + */ + return nil +} + // // Msg Tick Ack // @@ -113,16 +127,20 @@ func (msg MsgTick) ValidateBasic() sdk.Error { var _ sdk.Msg = &MsgTickAck{} type MsgTickAck struct { - From types.HeimdallAddress `json:"from"` - TxHash types.HeimdallHash `json:"tx_hash"` - LogIndex uint64 `json:"log_index"` + From types.HeimdallAddress `json:"from"` + SlashedAmount *big.Int `json:"slashed_amount"` + TxHash types.HeimdallHash `json:"tx_hash"` + LogIndex uint64 `json:"log_index"` + BlockNumber uint64 `json:"block_number"` } -func NewMsgTickAck(from types.HeimdallAddress, txHash types.HeimdallHash, logIndex uint64) MsgTickAck { +func NewMsgTickAck(from types.HeimdallAddress, slashedAmount *big.Int, txHash types.HeimdallHash, logIndex uint64, blockNumber uint64) MsgTickAck { return MsgTickAck{ - From: from, - TxHash: txHash, - LogIndex: logIndex, + From: from, + SlashedAmount: slashedAmount, + TxHash: txHash, + BlockNumber: blockNumber, + LogIndex: logIndex, } } @@ -152,6 +170,20 @@ func (msg MsgTickAck) ValidateBasic() sdk.Error { if msg.From.Empty() { return hmCommon.ErrInvalidMsg(hmCommon.DefaultCodespace, "Invalid from %v", msg.From.String()) } + return nil +} + +// GetTxHash Returns tx hash +func (msg MsgTickAck) GetTxHash() types.HeimdallHash { + return msg.TxHash +} + +// GetLogIndex Returns log index +func (msg MsgTickAck) GetLogIndex() uint64 { + return msg.LogIndex +} +// GetSideSignBytes returns side sign bytes +func (msg MsgTickAck) GetSideSignBytes() []byte { return nil }