/
slashing.go
117 lines (102 loc) · 3 KB
/
slashing.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package staking
import (
"context"
"encoding/hex"
"math"
"time"
tmcrypto "github.com/tendermint/tendermint/crypto"
beacon "github.com/oasisprotocol/oasis-core/go/beacon/api"
abciAPI "github.com/oasisprotocol/oasis-core/go/consensus/tendermint/api"
registryState "github.com/oasisprotocol/oasis-core/go/consensus/tendermint/apps/registry/state"
stakingState "github.com/oasisprotocol/oasis-core/go/consensus/tendermint/apps/staking/state"
registry "github.com/oasisprotocol/oasis-core/go/registry/api"
staking "github.com/oasisprotocol/oasis-core/go/staking/api"
)
func onEvidenceByzantineConsensus(
ctx *abciAPI.Context,
reason staking.SlashReason,
addr tmcrypto.Address,
height int64,
time time.Time,
power int64,
) error {
regState := registryState.NewMutableState(ctx.State())
stakeState := stakingState.NewMutableState(ctx.State())
// Resolve consensus node. Note that in order for this to work even in light
// of node expirations, the node descriptor must be available for at least
// the debonding period after expiration.
node, err := regState.NodeByConsensusAddress(ctx, addr)
if err != nil {
ctx.Logger().Warn("failed to get validator node",
"err", err,
"address", hex.EncodeToString(addr),
)
return nil
}
nodeStatus, err := regState.NodeStatus(ctx, node.ID)
if err != nil {
ctx.Logger().Warn("failed to get validator node status",
"err", err,
"node_id", node.ID,
)
return nil
}
// Do not slash a frozen validator.
if nodeStatus.IsFrozen() {
ctx.Logger().Debug("not slashing frozen validator",
"node_id", node.ID,
"entity_id", node.EntityID,
"freeze_end_time", nodeStatus.FreezeEndTime,
)
return nil
}
// Retrieve the slash procedure.
st, err := stakeState.Slashing(ctx)
if err != nil {
ctx.Logger().Error("failed to get slashing table entry",
"err", err,
)
return err
}
penalty := st[reason]
// Freeze validator to prevent it being slashed again. This also prevents the
// validator from being scheduled in the next epoch.
if penalty.FreezeInterval > 0 {
var epoch beacon.EpochTime
epoch, err = ctx.AppState().GetEpoch(context.Background(), ctx.BlockHeight()+1)
if err != nil {
return err
}
// Check for overflow.
if math.MaxUint64-penalty.FreezeInterval < epoch {
nodeStatus.FreezeEndTime = registry.FreezeForever
} else {
nodeStatus.FreezeEndTime = epoch + penalty.FreezeInterval
}
}
// Slash validator.
entityAddr := staking.NewAddress(node.EntityID)
_, err = stakeState.SlashEscrow(ctx, entityAddr, &penalty.Amount)
if err != nil {
ctx.Logger().Error("failed to slash validator entity",
"err", err,
"node_id", node.ID,
"entity_id", node.EntityID,
)
return err
}
if err = regState.SetNodeStatus(ctx, node.ID, nodeStatus); err != nil {
ctx.Logger().Error("failed to set validator node status",
"err", err,
"node_id", node.ID,
"entity_id", node.EntityID,
)
return err
}
ctx.Logger().Warn("slashed validator",
"reason", reason,
"node_id", node.ID,
"entity_id", node.EntityID,
)
return nil
}