Skip to content

Commit

Permalink
Merge PR #1278: Slashing v2
Browse files Browse the repository at this point in the history
Implement semifinal Gaia slashing spec (#1263), less #1348, #1378, and #1440 which are TBD.
  • Loading branch information
cwgoes committed Jun 30, 2018
1 parent 2f508f5 commit 3654579
Show file tree
Hide file tree
Showing 23 changed files with 1,192 additions and 117 deletions.
3 changes: 2 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ jobs:
test_cover:
<<: *defaults
parallelism: 1
parallelism: 2
steps:
- attach_workspace:
at: /tmp/workspace
Expand All @@ -126,6 +126,7 @@ jobs:

upload_coverage:
<<: *defaults
parallelism: 1
steps:
- attach_workspace:
at: /tmp/workspace
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ BREAKING CHANGES
* `gaiacli stake complete-unbonding`
* `gaiacli stake begin-redelegation`
* `gaiacli stake complete-redelegation`
* [slashing] update slashing for unbonding period
* Slash according to power at time of infraction instead of power at
time of discovery
* Iterate through unbonding delegations & redelegations which contributed
to an infraction, slash them proportional to their stake at the time
* Add REST endpoint to unrevoke a validator previously revoked for downtime
* Add REST endpoint to retrieve liveness signing information for a validator

FEATURES
* [gaiacli] You can now attach a simple text-only memo to any transaction, with the `--memo` flag
Expand Down
2 changes: 1 addition & 1 deletion Gopkg.lock

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

24 changes: 24 additions & 0 deletions client/lcd/lcd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/slashing"
"github.com/cosmos/cosmos-sdk/x/stake"
stakerest "github.com/cosmos/cosmos-sdk/x/stake/client/rest"
)
Expand Down Expand Up @@ -521,6 +522,19 @@ func TestVote(t *testing.T) {
require.Equal(t, gov.VoteOptionToString(gov.OptionYes), vote.Option)
}

func TestUnrevoke(t *testing.T) {
_, password := "test", "1234567890"
addr, _ := CreateAddr(t, "test", password, GetKB(t))
cleanup, pks, port := InitializeTestLCD(t, 1, []sdk.Address{addr})
defer cleanup()

signingInfo := getSigningInfo(t, port, pks[0].Address())
tests.WaitForHeight(4, port)
require.Equal(t, true, signingInfo.IndexOffset > 0)
require.Equal(t, int64(0), signingInfo.JailedUntil)
require.Equal(t, true, signingInfo.SignedBlocksCounter > 0)
}

func TestProposalsQuery(t *testing.T) {
name, password1 := "test", "1234567890"
name2, password2 := "test2", "1234567890"
Expand Down Expand Up @@ -679,6 +693,16 @@ func doIBCTransfer(t *testing.T, port, seed, name, password string, addr sdk.Add
return resultTx
}

func getSigningInfo(t *testing.T, port string, validatorAddr sdk.Address) slashing.ValidatorSigningInfo {
validatorAddrBech := sdk.MustBech32ifyVal(validatorAddr)
res, body := Request(t, port, "GET", "/slashing/signing_info/"+validatorAddrBech, nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
var signingInfo slashing.ValidatorSigningInfo
err := cdc.UnmarshalJSON([]byte(body), &signingInfo)
require.Nil(t, err)
return signingInfo
}

func getDelegation(t *testing.T, port string, delegatorAddr, validatorAddr sdk.Address) stake.Delegation {

delegatorAddrBech := sdk.MustBech32ifyAcc(delegatorAddr)
Expand Down
2 changes: 2 additions & 0 deletions client/lcd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest"
gov "github.com/cosmos/cosmos-sdk/x/gov/client/rest"
ibc "github.com/cosmos/cosmos-sdk/x/ibc/client/rest"
slashing "github.com/cosmos/cosmos-sdk/x/slashing/client/rest"
stake "github.com/cosmos/cosmos-sdk/x/stake/client/rest"
)

Expand Down Expand Up @@ -84,6 +85,7 @@ func createHandler(cdc *wire.Codec) http.Handler {
bank.RegisterRoutes(ctx, r, cdc, kb)
ibc.RegisterRoutes(ctx, r, cdc, kb)
stake.RegisterRoutes(ctx, r, cdc, kb)
slashing.RegisterRoutes(ctx, r, cdc, kb)
gov.RegisterRoutes(ctx, r, cdc)
return r
}
51 changes: 32 additions & 19 deletions docs/spec/slashing/end_block.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,28 @@ For some `evidence` to be valid, it must satisfy:
where `evidence.Timestamp` is the timestamp in the block at height
`evidence.Height` and `block.Timestamp` is the current block timestamp.

If valid evidence is included in a block, the validator's stake is reduced by `SLASH_PROPORTION` of
what their stake was when the equivocation occurred (rather than when the evidence was discovered):
If valid evidence is included in a block, the validator's stake is reduced by `SLASH_PROPORTION` of
what their stake was when the infraction occurred (rather than when the evidence was discovered).
We want to "follow the stake": the stake which contributed to the infraction should be
slashed, even if it has since been redelegated or started unbonding.

```
curVal := validator
oldVal := loadValidator(evidence.Height, evidence.Address)
slashAmount := SLASH_PROPORTION * oldVal.Shares
We first need to loop through the unbondings and redelegations from the slashed validator
and track how much stake has since moved:

curVal.Shares = max(0, curVal.Shares - slashAmount)
```
slashAmountUnbondings := 0
slashAmountRedelegations := 0
This ensures that offending validators are punished the same amount whether they
act as a single validator with X stake or as N validators with collectively X
stake.

We also need to loop through the unbondings and redelegations to slash them as
well:

```
unbondings := getUnbondings(validator.Address)
for unbond in unbondings {
if was not bonded before evidence.Height {
if was not bonded before evidence.Height or started unbonding before unbonding period ago {
continue
}
unbond.InitialTokens
burn := unbond.InitialTokens * SLASH_PROPORTION
slashAmountUnbondings += burn
unbond.Tokens = max(0, unbond.Tokens - burn)
}
Expand All @@ -51,17 +46,35 @@ for unbond in unbondings {
redels := getRedelegationsBySource(validator.Address)
for redel in redels {
if was not bonded before evidence.Height {
if was not bonded before evidence.Height or started redelegating before unbonding period ago {
continue
}
burn := redel.InitialTokens * SLASH_PROPORTION
slashAmountRedelegations += burn
amount := unbondFromValidator(redel.Destination, burn)
destroy(amount)
}
```

We then slash the validator:

```
curVal := validator
oldVal := loadValidator(evidence.Height, evidence.Address)
slashAmount := SLASH_PROPORTION * oldVal.Shares
slashAmount -= slashAmountUnbondings
slashAmount -= slashAmountRedelegations
curVal.Shares = max(0, curVal.Shares - slashAmount)
```

This ensures that offending validators are punished the same amount whether they
act as a single validator with X stake or as N validators with collectively X
stake.

## Automatic Unbonding

At the beginning of each block, we update the signing info for each validator and check if they should be automatically unbonded:
Expand Down
7 changes: 6 additions & 1 deletion examples/democoin/mock/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ func (v Validator) GetDelegatorShares() sdk.Rat {
return sdk.ZeroRat()
}

// Implements sdk.Validator
func (v Validator) GetRevoked() bool {
return false
}

// Implements sdk.Validator
func (v Validator) GetBondHeight() int64 {
return 0
Expand Down Expand Up @@ -107,7 +112,7 @@ func (vs *ValidatorSet) RemoveValidator(addr sdk.Address) {
}

// Implements sdk.ValidatorSet
func (vs *ValidatorSet) Slash(ctx sdk.Context, pubkey crypto.PubKey, height int64, amt sdk.Rat) {
func (vs *ValidatorSet) Slash(ctx sdk.Context, pubkey crypto.PubKey, height int64, power int64, amt sdk.Rat) {
panic("not implemented")
}

Expand Down
8 changes: 5 additions & 3 deletions types/stake.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func BondStatusToString(b BondStatus) string {

// validator for a delegated proof of stake system
type Validator interface {
GetRevoked() bool // whether the validator is revoked
GetMoniker() string // moniker of the validator
GetStatus() BondStatus // status of the validator
GetOwner() Address // owner address to receive/return validators coins
Expand Down Expand Up @@ -62,9 +63,10 @@ type ValidatorSet interface {
Validator(Context, Address) Validator // get a particular validator by owner address
TotalPower(Context) Rat // total power of the validator set

Slash(Context, crypto.PubKey, int64, Rat) // slash the validator and delegators of the validator, specifying offence height & slash fraction
Revoke(Context, crypto.PubKey) // revoke a validator
Unrevoke(Context, crypto.PubKey) // unrevoke a validator
// slash the validator and delegators of the validator, specifying offence height, offence power, and slash fraction
Slash(Context, crypto.PubKey, int64, int64, Rat)
Revoke(Context, crypto.PubKey) // revoke a validator
Unrevoke(Context, crypto.PubKey) // unrevoke a validator
}

//_______________________________________________________________________________
Expand Down
62 changes: 62 additions & 0 deletions x/slashing/client/rest/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package rest

import (
"fmt"
"net/http"

"github.com/gorilla/mux"

"github.com/cosmos/cosmos-sdk/client/context"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/slashing"
)

func registerQueryRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec) {
r.HandleFunc(
"/slashing/signing_info/{validator}",
signingInfoHandlerFn(ctx, "slashing", cdc),
).Methods("GET")
}

// http request handler to query signing info
func signingInfoHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.Codec) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {

// read parameters
vars := mux.Vars(r)
bech32validator := vars["validator"]

validatorAddr, err := sdk.GetValAddressBech32(bech32validator)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}

key := slashing.GetValidatorSigningInfoKey(validatorAddr)
res, err := ctx.QueryStore(key, storeName)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("couldn't query signing info. Error: %s", err.Error())))
return
}

var signingInfo slashing.ValidatorSigningInfo
err = cdc.UnmarshalBinary(res, &signingInfo)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("couldn't decode signing info. Error: %s", err.Error())))
return
}

output, err := cdc.MarshalJSON(signingInfo)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}

w.Write(output)
}
}
15 changes: 15 additions & 0 deletions x/slashing/client/rest/rest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package rest

import (
"github.com/gorilla/mux"

"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/crypto/keys"
"github.com/cosmos/cosmos-sdk/wire"
)

// RegisterRoutes registers staking-related REST handlers to a router
func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) {
registerQueryRoutes(ctx, r, cdc)
registerTxRoutes(ctx, r, cdc, kb)
}
Loading

0 comments on commit 3654579

Please sign in to comment.