Skip to content

Commit

Permalink
go/consensus/tendermint: Handle LIGHT_CLIENT_ATTACK evidence
Browse files Browse the repository at this point in the history
  • Loading branch information
kostko committed Feb 2, 2021
1 parent f1d17e9 commit be3eab0
Show file tree
Hide file tree
Showing 6 changed files with 39 additions and 15 deletions.
1 change: 1 addition & 0 deletions .changelog/3156.breaking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
go/consensus/tendermint: Handle LIGHT_CLIENT_ATTACK evidence
12 changes: 7 additions & 5 deletions go/consensus/tendermint/apps/staking/slashing.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import (
staking "github.com/oasisprotocol/oasis-core/go/staking/api"
)

func onEvidenceConsensusEquivocation(
func onEvidenceByzantineConsensus(
ctx *abciAPI.Context,
reason staking.SlashReason,
addr tmcrypto.Address,
height int64,
time time.Time,
Expand Down Expand Up @@ -57,16 +58,16 @@ func onEvidenceConsensusEquivocation(
return nil
}

// Retrieve the slash procedure for double signing.
// Retrieve the slash procedure.
st, err := stakeState.Slashing(ctx)
if err != nil {
ctx.Logger().Error("failed to get slashing table entry for double signing",
ctx.Logger().Error("failed to get slashing table entry",
"err", err,
)
return err
}

penalty := st[staking.SlashConsensusEquivocation]
penalty := st[reason]

// Freeze validator to prevent it being slashed again. This also prevents the
// validator from being scheduled in the next epoch.
Expand Down Expand Up @@ -106,7 +107,8 @@ func onEvidenceConsensusEquivocation(
return err
}

ctx.Logger().Warn("slashed validator for double signing",
ctx.Logger().Warn("slashed validator",
"reason", reason,
"node_id", node.ID,
"entity_id", node.EntityID,
)
Expand Down
18 changes: 11 additions & 7 deletions go/consensus/tendermint/apps/staking/slashing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
staking "github.com/oasisprotocol/oasis-core/go/staking/api"
)

func TestOnEvidenceConsensusEquivocation(t *testing.T) {
func TestOnEvidenceByzantineConsensus(t *testing.T) {
require := require.New(t)

now := time.Unix(1580461674, 0)
Expand All @@ -39,7 +39,7 @@ func TestOnEvidenceConsensusEquivocation(t *testing.T) {
stakeState := stakingState.NewMutableState(ctx.State())

// Validator address is not known as there are no nodes.
err := onEvidenceConsensusEquivocation(ctx, validatorAddress, 1, now, 1)
err := onEvidenceByzantineConsensus(ctx, staking.SlashConsensusEquivocation, validatorAddress, 1, now, 1)
require.NoError(err, "should not fail when validator address is not known")

// Add entity.
Expand All @@ -64,15 +64,15 @@ func TestOnEvidenceConsensusEquivocation(t *testing.T) {
require.NoError(err, "SetNode")

// Should not fail if node status is not available.
err = onEvidenceConsensusEquivocation(ctx, validatorAddress, 1, now, 1)
err = onEvidenceByzantineConsensus(ctx, staking.SlashConsensusEquivocation, validatorAddress, 1, now, 1)
require.NoError(err, "should not fail when node status is not available")

// Add node status.
err = regState.SetNodeStatus(ctx, nod.ID, &registry.NodeStatus{})
require.NoError(err, "SetNodeStatus")

// Should fail if unable to get the slashing procedure.
err = onEvidenceConsensusEquivocation(ctx, validatorAddress, 1, now, 1)
err = onEvidenceByzantineConsensus(ctx, staking.SlashConsensusEquivocation, validatorAddress, 1, now, 1)
require.Error(err, "should fail when unable to get the slashing procedure")

// Add slashing procedure.
Expand All @@ -90,7 +90,7 @@ func TestOnEvidenceConsensusEquivocation(t *testing.T) {

// Should not fail if the validator has no stake (which is in any case an
// invariant violation as a validator needs to have some stake).
err = onEvidenceConsensusEquivocation(ctx, validatorAddress, 1, now, 1)
err = onEvidenceByzantineConsensus(ctx, staking.SlashConsensusEquivocation, validatorAddress, 1, now, 1)
require.NoError(err, "should not fail when validator has no stake")
// Node should be frozen.
status, err := regState.NodeStatus(ctx, nod.ID)
Expand All @@ -99,7 +99,7 @@ func TestOnEvidenceConsensusEquivocation(t *testing.T) {
require.EqualValues(registry.FreezeForever, status.FreezeEndTime, "node should be frozen forever")

// Should not fail slashing a frozen node.
err = onEvidenceConsensusEquivocation(ctx, validatorAddress, 1, now, 1)
err = onEvidenceByzantineConsensus(ctx, staking.SlashConsensusEquivocation, validatorAddress, 1, now, 1)
require.NoError(err, "should not fail when validator is frozen")
// Unfreeze the node.
err = regState.SetNodeStatus(ctx, nod.ID, &registry.NodeStatus{FreezeEndTime: 0})
Expand All @@ -124,7 +124,7 @@ func TestOnEvidenceConsensusEquivocation(t *testing.T) {
require.NoError(err, "SetAccount")

// Should slash.
err = onEvidenceConsensusEquivocation(ctx, validatorAddress, 1, now, 1)
err = onEvidenceByzantineConsensus(ctx, staking.SlashConsensusEquivocation, validatorAddress, 1, now, 1)
require.NoError(err, "slashing should succeed")

// Entity stake should be slashed.
Expand All @@ -138,4 +138,8 @@ func TestOnEvidenceConsensusEquivocation(t *testing.T) {
require.NoError(err, "NodeStatus")
require.True(status.IsFrozen(), "node should be frozen after slashing")
require.EqualValues(registry.FreezeForever, status.FreezeEndTime, "node should be frozen forever")

// Should not fail in case the slashing penalty is not configured.
err = onEvidenceByzantineConsensus(ctx, staking.SlashConsensusLightClientAttack, validatorAddress, 1, now, 1)
require.NoError(err, "slashing should not fail")
}
12 changes: 9 additions & 3 deletions go/consensus/tendermint/apps/staking/staking.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,21 @@ func (app *stakingApplication) BeginBlock(ctx *api.Context, request types.Reques
// Iterate over any submitted evidence of a validator misbehaving. Note that
// the actual evidence has already been verified by Tendermint to be valid.
for _, evidence := range request.ByzantineValidators {
var reason staking.SlashReason
switch evidence.Type {
case types.EvidenceType_DUPLICATE_VOTE:
if err := onEvidenceConsensusEquivocation(ctx, evidence.Validator.Address, evidence.Height, evidence.Time, evidence.Validator.Power); err != nil {
return err
}
reason = staking.SlashConsensusEquivocation
case types.EvidenceType_LIGHT_CLIENT_ATTACK:
reason = staking.SlashConsensusLightClientAttack
default:
ctx.Logger().Warn("ignoring unknown evidence type",
"evidence_type", evidence.Type,
)
continue
}

if err := onEvidenceByzantineConsensus(ctx, reason, evidence.Validator.Address, evidence.Height, evidence.Time, evidence.Validator.Power); err != nil {
return err
}
}

Expand Down
3 changes: 3 additions & 0 deletions go/consensus/tendermint/apps/staking/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,9 @@ func (s *ImmutableState) Slashing(ctx context.Context) (map[staking.SlashReason]
if err != nil {
return nil, err
}
if params.Slashing == nil {
return make(map[staking.SlashReason]staking.Slash), nil
}

return params.Slashing, nil
}
Expand Down
8 changes: 8 additions & 0 deletions go/staking/api/slashing.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const (
SlashBeaconInvalidReveal SlashReason = 0x02
// SlashBeaconNonparticipation is slashing due to nonparticipation.
SlashBeaconNonparticipation SlashReason = 0x03
// SlashConsensusLightClientAttack is slashing due to light client attacks.
SlashConsensusLightClientAttack SlashReason = 0x04

// SlashConsensusEquivocationName is the string representation of SlashConsensusEquivocation.
SlashConsensusEquivocationName = "consensus-equivocation"
Expand All @@ -28,6 +30,8 @@ const (
SlashBeaconInvalidRevealName = "beacon-invalid-reveal"
// SlashBeaconNonparticipationName is the string representation of SlashBeaconNonparticipation.
SlashBeaconNonparticipationName = "beacon-nonparticipation"
// SlashConsensusLightClientAttackName is the string representation of SlashConsensusLightClientAttack.
SlashConsensusLightClientAttackName = "consensus-light-client-attack"
)

// String returns a string representation of a SlashReason.
Expand All @@ -46,6 +50,8 @@ func (s SlashReason) checkedString() (string, error) {
return SlashBeaconInvalidRevealName, nil
case SlashBeaconNonparticipation:
return SlashBeaconNonparticipationName, nil
case SlashConsensusLightClientAttack:
return SlashConsensusLightClientAttackName, nil
default:
return "[unknown slash reason]", fmt.Errorf("unknown slash reason: %d", s)
}
Expand Down Expand Up @@ -76,6 +82,8 @@ func (s *SlashReason) UnmarshalText(text []byte) error {
*s = SlashBeaconInvalidReveal
case SlashBeaconNonparticipationName:
*s = SlashBeaconNonparticipation
case SlashConsensusLightClientAttackName:
*s = SlashConsensusLightClientAttack
default:
return fmt.Errorf("invalid slash reason: %s", string(text))
}
Expand Down

0 comments on commit be3eab0

Please sign in to comment.