Skip to content

Commit

Permalink
Fix: jail validator on withdraw full stake (#157)
Browse files Browse the repository at this point in the history
  • Loading branch information
aopoltorzhicky authored Mar 11, 2024
1 parent 18cf505 commit 68fc8b5
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 12 deletions.
3 changes: 3 additions & 0 deletions internal/storage/block_signature_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2024 PK Lab AG <[email protected]>
// SPDX-License-Identifier: MIT

package storage

import (
Expand Down
2 changes: 2 additions & 0 deletions internal/storage/generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ type Transaction interface {
LastNamespaceMessage(ctx context.Context, nsId uint64) (msg NamespaceMessage, err error)
LastAddressAction(ctx context.Context, address []byte) (uint64, error)
GetProposerId(ctx context.Context, address string) (uint64, error)
Validator(ctx context.Context, id uint64) (val Validator, err error)
Delegation(ctx context.Context, validatorId, addressId uint64) (val Delegation, err error)
}

const (
Expand Down
78 changes: 78 additions & 0 deletions internal/storage/mock/generic.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 24 additions & 6 deletions internal/storage/postgres/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -668,12 +668,30 @@ func (tx Transaction) UpdateValidators(ctx context.Context, validators ...*model
return nil
}

_, err := tx.Tx().NewUpdate().Model(&validators).
Set("stake = validator.stake + EXCLUDED.stake").
Set("jailed = EXCLUDED.jailed").
Set("commissions = validator.commissions + EXCLUDED.commissions").
Set("rewards = validator.rewards + EXCLUDED.rewards").
Exec(ctx)
values := tx.Tx().NewValues(&validators)

_, err := tx.Tx().NewUpdate().
With("_data", values).
Model((*models.Validator)(nil)).
TableExpr("_data").
Set("stake = validator.stake + _data.stake").
Set("jailed = _data.jailed").
Set("commissions = validator.commissions + _data.commissions").
Set("rewards = validator.rewards + _data.rewards").
Where("validator.id = _data.id").
Exec(ctx)
return err
}

func (tx Transaction) Validator(ctx context.Context, id uint64) (val models.Validator, err error) {
err = tx.Tx().NewSelect().Model(&val).Where("id = ?", id).Scan(ctx)
return
}

func (tx Transaction) Delegation(ctx context.Context, validatorId, addressId uint64) (val models.Delegation, err error) {
err = tx.Tx().NewSelect().Model(&val).
Where("validator_id = ?", validatorId).
Where("address_id = ?", addressId).
Scan(ctx)
return
}
64 changes: 64 additions & 0 deletions pkg/indexer/storage/delegations.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ import (
"github.com/shopspring/decimal"
)

const (
withdrawStakeReason = "not enough self delegation"
)

type jailed struct {
storage.Jail

addressId uint64
}

func (module *Module) saveDelegations(
ctx context.Context,
tx storage.Transaction,
Expand Down Expand Up @@ -69,6 +79,8 @@ func (module *Module) saveDelegations(
}
}

withdrawStake := make(map[uint64]jailed)

if len(dCtx.Redelegations) > 0 {
for i := range dCtx.Redelegations {
addressId, ok := addrToId[dCtx.Redelegations[i].Address.Address]
Expand All @@ -88,6 +100,19 @@ func (module *Module) saveDelegations(
return total, errors.Wrapf(errCantFindAddress, "dest validator address %s", dCtx.Redelegations[i].Destination.Address)
}
dCtx.Redelegations[i].DestId = destId

if id, ok := module.validatorsByDelegator[dCtx.Redelegations[i].Address.Address]; ok && id == srcId {
withdrawStake[id] = jailed{
Jail: storage.Jail{
Height: dCtx.Block.Height,
Time: dCtx.Block.Time,
ValidatorId: srcId,
Reason: withdrawStakeReason,
Burned: decimal.Zero,
},
addressId: addressId,
}
}
}

if err := tx.SaveRedelegations(ctx, dCtx.Redelegations...); err != nil {
Expand All @@ -110,6 +135,19 @@ func (module *Module) saveDelegations(
dCtx.Undelegations[i].ValidatorId = validatorId

total = total.Sub(dCtx.Undelegations[i].Amount)

if id, ok := module.validatorsByDelegator[dCtx.Undelegations[i].Address.Address]; ok && id == validatorId {
withdrawStake[id] = jailed{
Jail: storage.Jail{
Height: dCtx.Block.Height,
Time: dCtx.Block.Time,
ValidatorId: validatorId,
Reason: withdrawStakeReason,
Burned: decimal.Zero,
},
addressId: addressId,
}
}
}

if err := tx.SaveUndelegations(ctx, dCtx.Undelegations...); err != nil {
Expand Down Expand Up @@ -146,5 +184,31 @@ func (module *Module) saveDelegations(
return total, errors.Wrap(err, "retention completed unbondings")
}

for validatorId, jail := range withdrawStake {
validator, err := tx.Validator(ctx, validatorId)
if err != nil {
return total, errors.Wrap(err, "can't find validator")
}
delegation, err := tx.Delegation(ctx, validatorId, jail.addressId)
if err != nil {
return total, errors.Wrap(err, "can't find delegation")
}
if delegation.Amount.IsPositive() && delegation.Amount.GreaterThanOrEqual(validator.MinSelfDelegation) {
continue
}

j := true
validator.Jailed = &j
validator.Stake = decimal.Zero

if err := tx.Jail(ctx, &validator); err != nil {
return total, errors.Wrap(err, "jail on withdraw stake")
}

if err := tx.SaveJails(ctx, jail.Jail); err != nil {
return total, errors.Wrap(err, "save jail on withdraw stake")
}
}

return total, nil
}
7 changes: 5 additions & 2 deletions pkg/indexer/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type Module struct {
notificator storage.Notificator
validatorsByConsAddress map[string]uint64
validatorsByAddress map[string]uint64
validatorsByDelegator map[string]uint64

slashingForDowntime decimal.Decimal
slashingForDoubleSign decimal.Decimal
Expand All @@ -51,20 +52,21 @@ var _ modules.Module = (*Module)(nil)

// NewModule -
func NewModule(
storage sdk.Transactable,
tx sdk.Transactable,
constants storage.IConstant,
validators storage.IValidator,
notificator storage.Notificator,
cfg config.Indexer,
) Module {
m := Module{
BaseModule: modules.New("storage"),
storage: storage,
storage: tx,
constants: constants,
validators: validators,
notificator: notificator,
validatorsByConsAddress: make(map[string]uint64),
validatorsByAddress: make(map[string]uint64),
validatorsByDelegator: make(map[string]uint64),
slashingForDowntime: decimal.Zero,
slashingForDoubleSign: decimal.Zero,
indexerName: cfg.Name,
Expand Down Expand Up @@ -99,6 +101,7 @@ func (module *Module) init(ctx context.Context) error {
for i := range validators {
module.validatorsByConsAddress[validators[i].ConsAddress] = validators[i].Id
module.validatorsByAddress[validators[i].Address] = validators[i].Id
module.validatorsByDelegator[validators[i].Delegator] = validators[i].Id
}
offset += len(validators)
end = limit > len(validators)
Expand Down
12 changes: 8 additions & 4 deletions pkg/indexer/storage/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,15 @@ func (module *Module) saveValidators(
}

for i := range validators {
if validators[i].ConsAddress == "" {
continue
if validators[i].ConsAddress != "" {
module.validatorsByConsAddress[validators[i].ConsAddress] = validators[i].Id
}
if validators[i].Address != "" {
module.validatorsByAddress[validators[i].Address] = validators[i].Id
}
if validators[i].Delegator != "" {
module.validatorsByDelegator[validators[i].Delegator] = validators[i].Id
}
module.validatorsByConsAddress[validators[i].ConsAddress] = validators[i].Id
module.validatorsByAddress[validators[i].Address] = validators[i].Id
}

return count, nil
Expand Down

0 comments on commit 68fc8b5

Please sign in to comment.