Rebase & squash slashing
This commit is contained in:
parent
f36eb2209e
commit
95c5baf449
32
Gopkg.lock
generated
32
Gopkg.lock
generated
@ -253,6 +253,7 @@
|
||||
revision = "e6d6b529196422703d54ff5c40e79809ec2020b3"
|
||||
|
||||
[[projects]]
|
||||
branch = "cwgoes/timestamp-in-evidence"
|
||||
name = "github.com/tendermint/abci"
|
||||
packages = [
|
||||
"client",
|
||||
@ -261,8 +262,7 @@
|
||||
"server",
|
||||
"types"
|
||||
]
|
||||
revision = "78a8905690ef54f9d57e3b2b0ee7ad3a04ef3f1f"
|
||||
version = "v0.10.3"
|
||||
revision = "e196dacf804e3a4ab74252c27e99cb17b39bf501"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@ -293,18 +293,16 @@
|
||||
version = "v0.6.2"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tendermint/go-wire"
|
||||
packages = ["."]
|
||||
revision = "fa721242b042ecd4c6ed1a934ee740db4f74e45c"
|
||||
version = "v0.7.3"
|
||||
|
||||
[[projects]]
|
||||
branch = "develop"
|
||||
name = "github.com/tendermint/iavl"
|
||||
packages = ["."]
|
||||
revision = "fd37a0fa3a7454423233bc3d5ea828f38e0af787"
|
||||
version = "v0.7.0"
|
||||
packages = [
|
||||
".",
|
||||
"sha256truncated"
|
||||
]
|
||||
revision = "9b6f9c3f49d599cfcf43f1a56c3c22882d1f9f21"
|
||||
|
||||
[[projects]]
|
||||
branch = "cwgoes/update-abci"
|
||||
name = "github.com/tendermint/tendermint"
|
||||
packages = [
|
||||
"blockchain",
|
||||
@ -313,6 +311,9 @@
|
||||
"consensus",
|
||||
"consensus/types",
|
||||
"evidence",
|
||||
"libs/events",
|
||||
"libs/pubsub",
|
||||
"libs/pubsub/query",
|
||||
"lite",
|
||||
"lite/client",
|
||||
"lite/errors",
|
||||
@ -341,8 +342,7 @@
|
||||
"types/priv_validator",
|
||||
"version"
|
||||
]
|
||||
revision = "018e096748bafe1d2d1e69b909e4158f3b26f6b2"
|
||||
version = "v0.19.5-rc1"
|
||||
revision = "d3093426f455c4b33cee818e3bbf2d2b83e4b38d"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tendermint/tmlibs"
|
||||
@ -355,9 +355,7 @@
|
||||
"db",
|
||||
"flowrate",
|
||||
"log",
|
||||
"merkle",
|
||||
"pubsub",
|
||||
"pubsub/query"
|
||||
"merkle"
|
||||
]
|
||||
revision = "cc5f287c4798ffe88c04d02df219ecb6932080fd"
|
||||
version = "v0.8.3-rc0"
|
||||
@ -457,6 +455,6 @@
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "7540d2ecdb5d7d5084ab4e6132e929bbd501bd6add3006d8f08a6b2c127e0c7d"
|
||||
inputs-digest = "2bb789b91c383e3a861979c83e61f5f8fd71ea5661837c092353aaacf3091de1"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
||||
@ -52,9 +52,9 @@
|
||||
name = "github.com/stretchr/testify"
|
||||
version = "~1.2.1"
|
||||
|
||||
[[constraint]]
|
||||
[[override]]
|
||||
name = "github.com/tendermint/abci"
|
||||
version = "~0.10.3"
|
||||
branch = "cwgoes/timestamp-in-evidence"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/tendermint/go-crypto"
|
||||
@ -70,11 +70,11 @@
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/tendermint/iavl"
|
||||
version = "~0.7.0"
|
||||
branch = "develop"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/tendermint/tendermint"
|
||||
version = "0.19.5-rc1"
|
||||
branch = "cwgoes/update-abci"
|
||||
|
||||
[[override]]
|
||||
name = "github.com/tendermint/tmlibs"
|
||||
@ -88,4 +88,3 @@
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
||||
|
||||
@ -65,9 +65,10 @@ type BaseApp struct {
|
||||
// See methods setCheckState and setDeliverState.
|
||||
// .valUpdates accumulate in DeliverTx and are reset in BeginBlock.
|
||||
// QUESTION: should we put valUpdates in the deliverState.ctx?
|
||||
checkState *state // for CheckTx
|
||||
deliverState *state // for DeliverTx
|
||||
valUpdates []abci.Validator // cached validator changes from DeliverTx
|
||||
checkState *state // for CheckTx
|
||||
deliverState *state // for DeliverTx
|
||||
valUpdates []abci.Validator // cached validator changes from DeliverTx
|
||||
absentValidators [][]byte // absent validators from begin block
|
||||
}
|
||||
|
||||
var _ abci.Application = (*BaseApp)(nil)
|
||||
@ -234,9 +235,9 @@ func (app *BaseApp) initFromStore(mainKey sdk.StoreKey) error {
|
||||
// NewContext returns a new Context with the correct store, the given header, and nil txBytes.
|
||||
func (app *BaseApp) NewContext(isCheckTx bool, header abci.Header) sdk.Context {
|
||||
if isCheckTx {
|
||||
return sdk.NewContext(app.checkState.ms, header, true, nil, app.Logger)
|
||||
return sdk.NewContext(app.checkState.ms, header, true, nil, app.Logger, nil)
|
||||
}
|
||||
return sdk.NewContext(app.deliverState.ms, header, false, nil, app.Logger)
|
||||
return sdk.NewContext(app.deliverState.ms, header, false, nil, app.Logger, nil)
|
||||
}
|
||||
|
||||
type state struct {
|
||||
@ -252,7 +253,7 @@ func (app *BaseApp) setCheckState(header abci.Header) {
|
||||
ms := app.cms.CacheMultiStore()
|
||||
app.checkState = &state{
|
||||
ms: ms,
|
||||
ctx: sdk.NewContext(ms, header, true, nil, app.Logger),
|
||||
ctx: sdk.NewContext(ms, header, true, nil, app.Logger, nil),
|
||||
}
|
||||
}
|
||||
|
||||
@ -260,7 +261,7 @@ func (app *BaseApp) setDeliverState(header abci.Header) {
|
||||
ms := app.cms.CacheMultiStore()
|
||||
app.deliverState = &state{
|
||||
ms: ms,
|
||||
ctx: sdk.NewContext(ms, header, false, nil, app.Logger),
|
||||
ctx: sdk.NewContext(ms, header, false, nil, app.Logger, nil),
|
||||
}
|
||||
}
|
||||
|
||||
@ -384,6 +385,8 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg
|
||||
if app.beginBlocker != nil {
|
||||
res = app.beginBlocker(app.deliverState.ctx, req)
|
||||
}
|
||||
// set the absent validators for addition to context in deliverTx
|
||||
app.absentValidators = req.AbsentValidators
|
||||
return
|
||||
}
|
||||
|
||||
@ -493,6 +496,7 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
|
||||
ctx = app.checkState.ctx.WithTxBytes(txBytes)
|
||||
} else {
|
||||
ctx = app.deliverState.ctx.WithTxBytes(txBytes)
|
||||
ctx = ctx.WithAbsentValidators(app.absentValidators)
|
||||
}
|
||||
|
||||
// Simulate a DeliverTx for gas calculation
|
||||
|
||||
@ -183,7 +183,7 @@ func TestInitChainer(t *testing.T) {
|
||||
|
||||
// set initChainer and try again - should see the value
|
||||
app.SetInitChainer(initChainer)
|
||||
app.InitChain(abci.RequestInitChain{AppStateBytes: []byte("{}")}) // must have valid JSON genesis file, even if empty
|
||||
app.InitChain(abci.RequestInitChain{GenesisBytes: []byte("{}")}) // must have valid JSON genesis file, even if empty
|
||||
app.Commit()
|
||||
res = app.Query(query)
|
||||
assert.Equal(t, value, res.Value)
|
||||
|
||||
@ -15,6 +15,7 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
"github.com/cosmos/cosmos-sdk/x/ibc"
|
||||
"github.com/cosmos/cosmos-sdk/x/slashing"
|
||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||
)
|
||||
|
||||
@ -34,10 +35,11 @@ type GaiaApp struct {
|
||||
cdc *wire.Codec
|
||||
|
||||
// keys to access the substores
|
||||
keyMain *sdk.KVStoreKey
|
||||
keyAccount *sdk.KVStoreKey
|
||||
keyIBC *sdk.KVStoreKey
|
||||
keyStake *sdk.KVStoreKey
|
||||
keyMain *sdk.KVStoreKey
|
||||
keyAccount *sdk.KVStoreKey
|
||||
keyIBC *sdk.KVStoreKey
|
||||
keyStake *sdk.KVStoreKey
|
||||
keySlashing *sdk.KVStoreKey
|
||||
|
||||
// Manage getting and setting accounts
|
||||
accountMapper auth.AccountMapper
|
||||
@ -45,6 +47,7 @@ type GaiaApp struct {
|
||||
coinKeeper bank.Keeper
|
||||
ibcMapper ibc.Mapper
|
||||
stakeKeeper stake.Keeper
|
||||
slashingKeeper slashing.Keeper
|
||||
}
|
||||
|
||||
func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
|
||||
@ -52,12 +55,13 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
|
||||
|
||||
// create your application object
|
||||
var app = &GaiaApp{
|
||||
BaseApp: bam.NewBaseApp(appName, cdc, logger, db),
|
||||
cdc: cdc,
|
||||
keyMain: sdk.NewKVStoreKey("main"),
|
||||
keyAccount: sdk.NewKVStoreKey("acc"),
|
||||
keyIBC: sdk.NewKVStoreKey("ibc"),
|
||||
keyStake: sdk.NewKVStoreKey("stake"),
|
||||
BaseApp: bam.NewBaseApp(appName, cdc, logger, db),
|
||||
cdc: cdc,
|
||||
keyMain: sdk.NewKVStoreKey("main"),
|
||||
keyAccount: sdk.NewKVStoreKey("acc"),
|
||||
keyIBC: sdk.NewKVStoreKey("ibc"),
|
||||
keyStake: sdk.NewKVStoreKey("stake"),
|
||||
keySlashing: sdk.NewKVStoreKey("slashing"),
|
||||
}
|
||||
|
||||
// define the accountMapper
|
||||
@ -71,6 +75,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
|
||||
app.coinKeeper = bank.NewKeeper(app.accountMapper)
|
||||
app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace))
|
||||
app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.coinKeeper, app.RegisterCodespace(stake.DefaultCodespace))
|
||||
app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.RegisterCodespace(slashing.DefaultCodespace))
|
||||
|
||||
// register message routes
|
||||
app.Router().
|
||||
@ -80,6 +85,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
|
||||
|
||||
// initialize BaseApp
|
||||
app.SetInitChainer(app.initChainer)
|
||||
app.SetBeginBlocker(slashing.NewBeginBlocker(app.slashingKeeper))
|
||||
app.SetEndBlocker(stake.NewEndBlocker(app.stakeKeeper))
|
||||
app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake)
|
||||
app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper))
|
||||
@ -105,7 +111,8 @@ func MakeCodec() *wire.Codec {
|
||||
|
||||
// custom logic for gaia initialization
|
||||
func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
|
||||
stateJSON := req.AppStateBytes
|
||||
stateJSON := req.GenesisBytes
|
||||
// TODO is this now the whole genesis file?
|
||||
|
||||
var genesisState GenesisState
|
||||
err := app.cdc.UnmarshalJSON(stateJSON, &genesisState)
|
||||
|
||||
@ -297,12 +297,12 @@ type TxProveLive struct {
|
||||
## Delegator bond
|
||||
|
||||
Atom holders may delegate coins to validators, under this circumstance their
|
||||
funds are held in a `DelegatorBond`. It is owned by one delegator, and is
|
||||
funds are held in a `Delegation`. It is owned by one delegator, and is
|
||||
associated with the shares for one validator. The sender of the transaction is
|
||||
considered to be the owner of the bond,
|
||||
|
||||
``` golang
|
||||
type DelegatorBond struct {
|
||||
type Delegation struct {
|
||||
Candidate crypto.PubKey
|
||||
Shares rational.Rat
|
||||
AdjustmentFeePool coin.Coins
|
||||
@ -318,11 +318,11 @@ Description:
|
||||
- AdjustmentRewardPool: Adjustment factor used to passively calculate each
|
||||
bonds entitled fees from `Candidate.ProposerRewardPool``
|
||||
|
||||
Each `DelegatorBond` is individually indexed within the store by delegator
|
||||
Each `Delegation` is individually indexed within the store by delegator
|
||||
address and candidate pubkey.
|
||||
|
||||
- key: Delegator and Candidate-Pubkey
|
||||
- value: DelegatorBond
|
||||
- value: Delegation
|
||||
|
||||
|
||||
### Delegating
|
||||
@ -330,7 +330,7 @@ address and candidate pubkey.
|
||||
Delegator bonds are created using the TxDelegate transaction. Within this
|
||||
transaction the validator candidate queried with an amount of coins, whereby
|
||||
given the current exchange rate of candidate's delegator-shares-to-atoms the
|
||||
candidate will return shares which are assigned in `DelegatorBond.Shares`.
|
||||
candidate will return shares which are assigned in `Delegation.Shares`.
|
||||
|
||||
``` golang
|
||||
type TxDelegate struct {
|
||||
@ -671,5 +671,5 @@ rate, all commission on fees must be simultaneously withdrawn.
|
||||
`candidate.Adjustment` must be set to the value of `canidate.Count` for the
|
||||
height which the candidate is added on the validator set.
|
||||
- The feePool of a new delegator bond will be 0 for the height at which the bond
|
||||
was added. This is achieved by setting `DelegatorBond.FeeWithdrawalHeight` to
|
||||
was added. This is achieved by setting `Delegation.FeeWithdrawalHeight` to
|
||||
the height which the bond was added.
|
||||
|
||||
@ -34,7 +34,7 @@ The staking module persists the following to the store:
|
||||
- `GlobalState`, describing the global pools
|
||||
- a `Candidate` for each candidate validator, indexed by public key
|
||||
- a `Candidate` for each candidate validator, indexed by shares in the global pool (ie. ordered)
|
||||
- a `DelegatorBond` for each delegation to a candidate by a delegator, indexed by delegator and candidate
|
||||
- a `Delegation` for each delegation to a candidate by a delegator, indexed by delegator and candidate
|
||||
public keys
|
||||
- a `Queue` of unbonding delegations (TODO)
|
||||
|
||||
@ -146,15 +146,15 @@ When validators are kicked from the validator set they are removed from this
|
||||
list.
|
||||
|
||||
|
||||
### DelegatorBond
|
||||
### Delegation
|
||||
|
||||
Atom holders may delegate coins to validators, under this circumstance their
|
||||
funds are held in a `DelegatorBond`. It is owned by one delegator, and is
|
||||
funds are held in a `Delegation`. It is owned by one delegator, and is
|
||||
associated with the shares for one validator. The sender of the transaction is
|
||||
considered to be the owner of the bond,
|
||||
|
||||
``` golang
|
||||
type DelegatorBond struct {
|
||||
type Delegation struct {
|
||||
Candidate crypto.PubKey
|
||||
Shares rational.Rat
|
||||
AdjustmentFeePool coin.Coins
|
||||
@ -170,11 +170,11 @@ Description:
|
||||
- AdjustmentRewardPool: Adjustment factor used to passively calculate each
|
||||
bonds entitled fees from `Candidate.ProposerRewardPool``
|
||||
|
||||
Each `DelegatorBond` is individually indexed within the store by delegator
|
||||
Each `Delegation` is individually indexed within the store by delegator
|
||||
address and candidate pubkey.
|
||||
|
||||
- key: Delegator and Candidate-Pubkey
|
||||
- value: DelegatorBond
|
||||
- value: Delegation
|
||||
|
||||
|
||||
### Unbonding Queue
|
||||
@ -308,7 +308,7 @@ All bonding, whether self-bonding or delegation, is done via
|
||||
Delegator bonds are created using the TxDelegate transaction. Within this
|
||||
transaction the validator candidate queried with an amount of coins, whereby
|
||||
given the current exchange rate of candidate's delegator-shares-to-atoms the
|
||||
candidate will return shares which are assigned in `DelegatorBond.Shares`.
|
||||
candidate will return shares which are assigned in `Delegation.Shares`.
|
||||
|
||||
``` golang
|
||||
type TxDelegate struct {
|
||||
@ -616,7 +616,7 @@ synced past the height of the oldest `powerChange`. This trim procedure will
|
||||
occur on an epoch basis.
|
||||
|
||||
```golang
|
||||
type powerChange struct {
|
||||
type powerChange struct
|
||||
height int64 // block height at change
|
||||
power rational.Rat // total power at change
|
||||
prevpower rational.Rat // total power at previous height-1
|
||||
@ -694,5 +694,5 @@ rate, all commission on fees must be simultaneously withdrawn.
|
||||
`candidate.Adjustment` must be set to the value of `canidate.Count` for the
|
||||
height which the candidate is added on the validator set.
|
||||
- The feePool of a new delegator bond will be 0 for the height at which the bond
|
||||
was added. This is achieved by setting `DelegatorBond.FeeWithdrawalHeight` to
|
||||
was added. This is achieved by setting `Delegation.FeeWithdrawalHeight` to
|
||||
the height which the bond was added.
|
||||
|
||||
@ -203,7 +203,7 @@ unbond(tx TxUnbond):
|
||||
return
|
||||
|
||||
removeShares(candidate Candidate, shares rational.Rat):
|
||||
globalPoolSharesToRemove = delegatorShareExRate(candidate) * shares
|
||||
globalPoolSharesToRemove = DelegatorShareExRate(candidate) * shares
|
||||
|
||||
if candidate.Status == Bonded
|
||||
gs.BondedShares -= globalPoolSharesToRemove
|
||||
@ -218,7 +218,7 @@ removeShares(candidate Candidate, shares rational.Rat):
|
||||
candidate.IssuedDelegatorShares -= shares
|
||||
return returnedCoins
|
||||
|
||||
delegatorShareExRate(candidate Candidate):
|
||||
DelegatorShareExRate(candidate Candidate):
|
||||
if candidate.IssuedDelegatorShares.IsZero() then return rational.One
|
||||
return candidate.GlobalStakeShares / candidate.IssuedDelegatorShares
|
||||
|
||||
|
||||
@ -31,8 +31,8 @@ tick(ctx Context):
|
||||
|
||||
if time > unbondDelegationQueue.head().InitTime + UnbondingPeriod
|
||||
for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do
|
||||
transfer(unbondingQueueAddress, elem.Payout, elem.Tokens)
|
||||
unbondDelegationQueue.remove(elem)
|
||||
transfer(unbondingQueueAddress, elem.Payout, elem.Tokens)
|
||||
unbondDelegationQueue.remove(elem)
|
||||
|
||||
if time > reDelegationQueue.head().InitTime + UnbondingPeriod
|
||||
for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do
|
||||
@ -55,9 +55,9 @@ nextInflation(hrsPerYr rational.Rat):
|
||||
|
||||
inflation = gs.Inflation + inflationRateChange
|
||||
if inflation > params.InflationMax then inflation = params.InflationMax
|
||||
|
||||
|
||||
if inflation < params.InflationMin then inflation = params.InflationMin
|
||||
|
||||
|
||||
return inflation
|
||||
|
||||
UpdateValidatorSet():
|
||||
@ -71,26 +71,26 @@ UpdateValidatorSet():
|
||||
|
||||
updateVotingPower(candidates Candidates):
|
||||
foreach candidate in candidates do
|
||||
candidate.VotingPower = (candidate.IssuedDelegatorShares - candidate.RedelegatingShares) * delegatorShareExRate(candidate)
|
||||
|
||||
candidate.VotingPower = (candidate.IssuedDelegatorShares - candidate.RedelegatingShares) * DelegatorShareExRate(candidate)
|
||||
|
||||
candidates.Sort()
|
||||
|
||||
|
||||
foreach candidate in candidates do
|
||||
if candidate is not in the first params.MaxVals
|
||||
candidate.VotingPower = rational.Zero
|
||||
if candidate.Status == Bonded then bondedToUnbondedPool(candidate Candidate)
|
||||
|
||||
else if candidate.Status == UnBonded then unbondedToBondedPool(candidate)
|
||||
if candidate is not in the first params.MaxVals
|
||||
candidate.VotingPower = rational.Zero
|
||||
if candidate.Status == Bonded then bondedToUnbondedPool(candidate Candidate)
|
||||
|
||||
else if candidate.Status == UnBonded then unbondedToBondedPool(candidate)
|
||||
|
||||
saveCandidate(store, c)
|
||||
|
||||
saveCandidate(store, c)
|
||||
|
||||
return candidates
|
||||
|
||||
unbondedToBondedPool(candidate Candidate):
|
||||
removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * candidate.GlobalStakeShares
|
||||
gs.UnbondedShares -= candidate.GlobalStakeShares
|
||||
gs.UnbondedPool -= removedTokens
|
||||
|
||||
|
||||
gs.BondedPool += removedTokens
|
||||
issuedShares = removedTokens / exchangeRate(gs.BondedShares, gs.BondedPool)
|
||||
gs.BondedShares += issuedShares
|
||||
@ -155,16 +155,17 @@ The following information is stored with each validator candidate, and is only n
|
||||
|
||||
```go
|
||||
type ValidatorSigningInfo struct {
|
||||
StartHeight int64
|
||||
SignedBlocksBitArray BitArray
|
||||
StartHeight int64
|
||||
SignedBlocksBitArray BitArray
|
||||
SignedBlocksCounter int64
|
||||
}
|
||||
```
|
||||
|
||||
Where:
|
||||
* `StartHeight` is set to the height that the candidate became an active validator (with non-zero voting power).
|
||||
* `SignedBlocksBitArray` is a bit-array of size `SIGNED_BLOCKS_WINDOW` that records, for each of the last `SIGNED_BLOCKS_WINDOW` blocks,
|
||||
whether or not this validator was included in the LastCommit. It uses a `0` if the validator was included, and a `1` if it was not.
|
||||
Note it is initialized with all 0s.
|
||||
whether or not this validator was included in the LastCommit. It uses a `1` if the validator was included, and a `0` if it was not. Note it is initialized with all 0s.
|
||||
* `SignedBlocksCounter` is a counter kept to avoid unnecessary array reads. `SignedBlocksBitArray.Sum() == SignedBlocksCounter` always.
|
||||
|
||||
At the beginning of each block, we update the signing info for each validator and check if they should be automatically unbonded:
|
||||
|
||||
@ -173,18 +174,23 @@ h = block.Height
|
||||
index = h % SIGNED_BLOCKS_WINDOW
|
||||
|
||||
for val in block.Validators:
|
||||
signInfo = val.SignInfo
|
||||
if val in block.LastCommit:
|
||||
signInfo.SignedBlocksBitArray.Set(index, 0)
|
||||
else
|
||||
signInfo.SignedBlocksBitArray.Set(index, 1)
|
||||
signInfo = val.SignInfo
|
||||
previous = signInfo.SignedBlocksBitArray.Get(index)
|
||||
|
||||
// validator must be active for at least SIGNED_BLOCKS_WINDOW
|
||||
// before they can be automatically unbonded for failing to be
|
||||
// included in 50% of the recent LastCommits
|
||||
minHeight = signInfo.StartHeight + SIGNED_BLOCKS_WINDOW
|
||||
minSigned = SIGNED_BLOCKS_WINDOW / 2
|
||||
blocksSigned = signInfo.SignedBlocksBitArray.Sum()
|
||||
if h > minHeight AND blocksSigned < minSigned:
|
||||
unbond the validator
|
||||
// update counter if array has changed
|
||||
if previous and val in block.AbsentValidators:
|
||||
signInfo.SignedBlocksBitArray.Set(index, 0)
|
||||
signInfo.SignedBlocksCounter--
|
||||
else if !previous and val not in block.AbsentValidators:
|
||||
signInfo.SignedBlocksBitArray.Set(index, 1)
|
||||
signInfo.SignedBlocksCounter++
|
||||
// else previous == val not in block.AbsentValidators, no change
|
||||
|
||||
// validator must be active for at least SIGNED_BLOCKS_WINDOW
|
||||
// before they can be automatically unbonded for failing to be
|
||||
// included in 50% of the recent LastCommits
|
||||
minHeight = signInfo.StartHeight + SIGNED_BLOCKS_WINDOW
|
||||
minSigned = SIGNED_BLOCKS_WINDOW / 2
|
||||
if h > minHeight AND signInfo.SignedBlocksCounter < minSigned:
|
||||
unbond the validator
|
||||
```
|
||||
|
||||
@ -104,7 +104,7 @@ func MakeCodec() *wire.Codec {
|
||||
|
||||
// Custom logic for basecoin initialization
|
||||
func (app *BasecoinApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
|
||||
stateJSON := req.AppStateBytes
|
||||
stateJSON := req.GenesisBytes
|
||||
|
||||
genesisState := new(types.GenesisState)
|
||||
err := app.cdc.UnmarshalJSON(stateJSON, genesisState)
|
||||
|
||||
@ -118,7 +118,7 @@ func MakeCodec() *wire.Codec {
|
||||
// custom logic for democoin initialization
|
||||
func (app *DemocoinApp) initChainerFn(coolKeeper cool.Keeper, powKeeper pow.Keeper) sdk.InitChainer {
|
||||
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
|
||||
stateJSON := req.AppStateBytes
|
||||
stateJSON := req.GenesisBytes
|
||||
|
||||
genesisState := new(types.GenesisState)
|
||||
err := app.cdc.UnmarshalJSON(stateJSON, genesisState)
|
||||
|
||||
@ -30,7 +30,7 @@ func TestCoolKeeper(t *testing.T) {
|
||||
auth.RegisterBaseAccount(cdc)
|
||||
|
||||
am := auth.NewAccountMapper(cdc, capKey, &auth.BaseAccount{})
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, nil)
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, nil, nil)
|
||||
ck := bank.NewKeeper(am)
|
||||
keeper := NewKeeper(capKey, ck, DefaultCodespace)
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ func TestPowHandler(t *testing.T) {
|
||||
auth.RegisterBaseAccount(cdc)
|
||||
|
||||
am := auth.NewAccountMapper(cdc, capKey, &auth.BaseAccount{})
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger())
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger(), nil)
|
||||
config := NewConfig("pow", int64(1))
|
||||
ck := bank.NewKeeper(am)
|
||||
keeper := NewKeeper(capKey, config, ck, DefaultCodespace)
|
||||
|
||||
@ -33,7 +33,7 @@ func TestPowKeeperGetSet(t *testing.T) {
|
||||
auth.RegisterBaseAccount(cdc)
|
||||
|
||||
am := auth.NewAccountMapper(cdc, capKey, &auth.BaseAccount{})
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger())
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger(), nil)
|
||||
config := NewConfig("pow", int64(1))
|
||||
ck := bank.NewKeeper(am)
|
||||
keeper := NewKeeper(capKey, config, ck, DefaultCodespace)
|
||||
|
||||
@ -35,9 +35,9 @@ func TestKeeperGetSet(t *testing.T) {
|
||||
cdc := wire.NewCodec()
|
||||
auth.RegisterBaseAccount(cdc)
|
||||
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger())
|
||||
accountMapper := auth.NewAccountMapper(cdc, authKey, &auth.BaseAccount{})
|
||||
stakeKeeper := NewKeeper(capKey, bank.NewKeeper(accountMapper), DefaultCodespace)
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger(), nil)
|
||||
addr := sdk.Address([]byte("some-address"))
|
||||
|
||||
bi := stakeKeeper.getBondInfo(ctx, addr)
|
||||
@ -63,7 +63,7 @@ func TestBonding(t *testing.T) {
|
||||
cdc := wire.NewCodec()
|
||||
auth.RegisterBaseAccount(cdc)
|
||||
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger())
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger(), nil)
|
||||
|
||||
accountMapper := auth.NewAccountMapper(cdc, authKey, &auth.BaseAccount{})
|
||||
coinKeeper := bank.NewKeeper(accountMapper)
|
||||
|
||||
@ -88,7 +88,7 @@ type GenesisJSON struct {
|
||||
// with key/value pairs
|
||||
func InitChainer(key sdk.StoreKey) func(sdk.Context, abci.RequestInitChain) abci.ResponseInitChain {
|
||||
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
|
||||
stateJSON := req.AppStateBytes
|
||||
stateJSON := req.GenesisBytes
|
||||
|
||||
genesisState := new(GenesisJSON)
|
||||
err := json.Unmarshal(stateJSON, genesisState)
|
||||
|
||||
@ -26,7 +26,7 @@ func TestInitApp(t *testing.T) {
|
||||
|
||||
//TODO test validators in the init chain?
|
||||
req := abci.RequestInitChain{
|
||||
AppStateBytes: appState,
|
||||
GenesisBytes: appState,
|
||||
}
|
||||
app.InitChain(req)
|
||||
app.Commit()
|
||||
|
||||
@ -30,7 +30,9 @@ type Context struct {
|
||||
}
|
||||
|
||||
// create a new context
|
||||
func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, txBytes []byte, logger log.Logger) Context {
|
||||
func NewContext(ms MultiStore, header abci.Header, isCheckTx bool,
|
||||
txBytes []byte, logger log.Logger, absentValidators [][]byte) Context {
|
||||
|
||||
c := Context{
|
||||
Context: context.Background(),
|
||||
pst: newThePast(),
|
||||
@ -43,6 +45,7 @@ func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, txBytes []byt
|
||||
c = c.WithIsCheckTx(isCheckTx)
|
||||
c = c.WithTxBytes(txBytes)
|
||||
c = c.WithLogger(logger)
|
||||
c = c.WithAbsentValidators(absentValidators)
|
||||
c = c.WithGasMeter(NewInfiniteGasMeter())
|
||||
return c
|
||||
}
|
||||
@ -128,6 +131,7 @@ const (
|
||||
contextKeyIsCheckTx
|
||||
contextKeyTxBytes
|
||||
contextKeyLogger
|
||||
contextKeyAbsentValidators
|
||||
contextKeyGasMeter
|
||||
)
|
||||
|
||||
@ -157,6 +161,9 @@ func (c Context) TxBytes() []byte {
|
||||
func (c Context) Logger() log.Logger {
|
||||
return c.Value(contextKeyLogger).(log.Logger)
|
||||
}
|
||||
func (c Context) AbsentValidators() [][]byte {
|
||||
return c.Value(contextKeyAbsentValidators).([][]byte)
|
||||
}
|
||||
func (c Context) GasMeter() GasMeter {
|
||||
return c.Value(contextKeyGasMeter).(GasMeter)
|
||||
}
|
||||
@ -182,6 +189,9 @@ func (c Context) WithTxBytes(txBytes []byte) Context {
|
||||
func (c Context) WithLogger(logger log.Logger) Context {
|
||||
return c.withValue(contextKeyLogger, logger)
|
||||
}
|
||||
func (c Context) WithAbsentValidators(AbsentValidators [][]byte) Context {
|
||||
return c.withValue(contextKeyAbsentValidators, AbsentValidators)
|
||||
}
|
||||
func (c Context) WithGasMeter(meter GasMeter) Context {
|
||||
return c.withValue(contextKeyGasMeter, meter)
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ func (l MockLogger) With(kvs ...interface{}) log.Logger {
|
||||
|
||||
func TestContextGetOpShouldNeverPanic(t *testing.T) {
|
||||
var ms types.MultiStore
|
||||
ctx := types.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger())
|
||||
ctx := types.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger(), nil)
|
||||
indices := []int64{
|
||||
-10, 1, 0, 10, 20,
|
||||
}
|
||||
@ -58,7 +58,7 @@ func defaultContext(key types.StoreKey) types.Context {
|
||||
cms := store.NewCommitMultiStore(db)
|
||||
cms.MountStoreWithDB(key, types.StoreTypeIAVL, db)
|
||||
cms.LoadLatestVersion()
|
||||
ctx := types.NewContext(cms, abci.Header{}, false, nil, log.NewNopLogger())
|
||||
ctx := types.NewContext(cms, abci.Header{}, false, nil, log.NewNopLogger(), nil)
|
||||
return ctx
|
||||
}
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ func defaultComponents(key sdk.StoreKey) (sdk.Context, *wire.Codec) {
|
||||
cms := store.NewCommitMultiStore(db)
|
||||
cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db)
|
||||
cms.LoadLatestVersion()
|
||||
ctx := sdk.NewContext(cms, abci.Header{}, false, nil, log.NewNopLogger())
|
||||
ctx := sdk.NewContext(cms, abci.Header{}, false, nil, log.NewNopLogger(), nil)
|
||||
cdc := wire.NewCodec()
|
||||
return ctx, cdc
|
||||
}
|
||||
|
||||
@ -17,11 +17,14 @@ const (
|
||||
|
||||
// validator for a delegated proof of stake system
|
||||
type Validator interface {
|
||||
GetStatus() BondStatus // status of the validator
|
||||
GetOwner() Address // owner address to receive/return validators coins
|
||||
GetPubKey() crypto.PubKey // validation pubkey
|
||||
GetPower() Rat // validation power
|
||||
GetBondHeight() int64 // height in which the validator became active
|
||||
GetStatus() BondStatus // status of the validator
|
||||
GetOwner() Address // owner address to receive/return validators coins
|
||||
GetPubKey() crypto.PubKey // validation pubkey
|
||||
GetPower() Rat // validation power
|
||||
GetBondHeight() int64 // height in which the validator became active
|
||||
Slash(Context, int64, Rat) // slash the validator and delegators of the validator
|
||||
// for an offense at a specified height by a specified fraction
|
||||
ForceUnbond(Context, int64) // force unbond the validator, including a duration which must pass before they can rebond
|
||||
}
|
||||
|
||||
// validator which fulfills abci validator interface for use in Tendermint
|
||||
|
||||
@ -25,6 +25,11 @@ func (t Tags) AppendTags(a Tags) Tags {
|
||||
return append(t, a...)
|
||||
}
|
||||
|
||||
// Turn tags into KVPair list
|
||||
func (t Tags) ToKVPairs() []cmn.KVPair {
|
||||
return []cmn.KVPair(t)
|
||||
}
|
||||
|
||||
// New variadic tags, must be k string, v []byte repeating
|
||||
func NewTags(tags ...interface{}) Tags {
|
||||
var ret Tags
|
||||
|
||||
@ -75,7 +75,7 @@ func TestAnteHandlerSigErrors(t *testing.T) {
|
||||
mapper := NewAccountMapper(cdc, capKey, &BaseAccount{})
|
||||
feeCollector := NewFeeCollectionKeeper(cdc, capKey2)
|
||||
anteHandler := NewAnteHandler(mapper, feeCollector)
|
||||
ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger())
|
||||
ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger(), nil)
|
||||
|
||||
// keys and addresses
|
||||
priv1, addr1 := privAndAddr()
|
||||
@ -117,7 +117,7 @@ func TestAnteHandlerSequences(t *testing.T) {
|
||||
mapper := NewAccountMapper(cdc, capKey, &BaseAccount{})
|
||||
feeCollector := NewFeeCollectionKeeper(cdc, capKey2)
|
||||
anteHandler := NewAnteHandler(mapper, feeCollector)
|
||||
ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger())
|
||||
ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger(), nil)
|
||||
|
||||
// keys and addresses
|
||||
priv1, addr1 := privAndAddr()
|
||||
@ -184,7 +184,7 @@ func TestAnteHandlerFees(t *testing.T) {
|
||||
mapper := NewAccountMapper(cdc, capKey, &BaseAccount{})
|
||||
feeCollector := NewFeeCollectionKeeper(cdc, capKey2)
|
||||
anteHandler := NewAnteHandler(mapper, feeCollector)
|
||||
ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger())
|
||||
ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger(), nil)
|
||||
|
||||
// keys and addresses
|
||||
priv1, addr1 := privAndAddr()
|
||||
@ -226,7 +226,7 @@ func TestAnteHandlerBadSignBytes(t *testing.T) {
|
||||
mapper := NewAccountMapper(cdc, capKey, &BaseAccount{})
|
||||
feeCollector := NewFeeCollectionKeeper(cdc, capKey2)
|
||||
anteHandler := NewAnteHandler(mapper, feeCollector)
|
||||
ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger())
|
||||
ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger(), nil)
|
||||
|
||||
// keys and addresses
|
||||
priv1, addr1 := privAndAddr()
|
||||
@ -302,7 +302,7 @@ func TestAnteHandlerSetPubKey(t *testing.T) {
|
||||
mapper := NewAccountMapper(cdc, capKey, &BaseAccount{})
|
||||
feeCollector := NewFeeCollectionKeeper(cdc, capKey2)
|
||||
anteHandler := NewAnteHandler(mapper, feeCollector)
|
||||
ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger())
|
||||
ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger(), nil)
|
||||
|
||||
// keys and addresses
|
||||
priv1, addr1 := privAndAddr()
|
||||
|
||||
@ -12,8 +12,8 @@ import (
|
||||
)
|
||||
|
||||
func TestContextWithSigners(t *testing.T) {
|
||||
ms, _, _ := setupMultiStore()
|
||||
ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger())
|
||||
ms, _ := setupMultiStore()
|
||||
ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger(), nil)
|
||||
|
||||
_, _, addr1 := keyPubAddr()
|
||||
_, _, addr2 := keyPubAddr()
|
||||
|
||||
@ -31,7 +31,7 @@ func TestAccountMapperGetSet(t *testing.T) {
|
||||
RegisterBaseAccount(cdc)
|
||||
|
||||
// make context and mapper
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger())
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger(), nil)
|
||||
mapper := NewAccountMapper(cdc, capKey, &BaseAccount{})
|
||||
|
||||
addr := sdk.Address([]byte("some-address"))
|
||||
|
||||
@ -31,7 +31,7 @@ func TestKeeper(t *testing.T) {
|
||||
cdc := wire.NewCodec()
|
||||
auth.RegisterBaseAccount(cdc)
|
||||
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger())
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger(), nil)
|
||||
accountMapper := auth.NewAccountMapper(cdc, authKey, &auth.BaseAccount{})
|
||||
coinKeeper := NewKeeper(accountMapper)
|
||||
|
||||
@ -116,7 +116,7 @@ func TestSendKeeper(t *testing.T) {
|
||||
cdc := wire.NewCodec()
|
||||
auth.RegisterBaseAccount(cdc)
|
||||
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger())
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger(), nil)
|
||||
accountMapper := auth.NewAccountMapper(cdc, authKey, &auth.BaseAccount{})
|
||||
coinKeeper := NewKeeper(accountMapper)
|
||||
sendKeeper := NewSendKeeper(accountMapper)
|
||||
@ -185,7 +185,7 @@ func TestViewKeeper(t *testing.T) {
|
||||
cdc := wire.NewCodec()
|
||||
auth.RegisterBaseAccount(cdc)
|
||||
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger())
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger(), nil)
|
||||
accountMapper := auth.NewAccountMapper(cdc, authKey, &auth.BaseAccount{})
|
||||
coinKeeper := NewKeeper(accountMapper)
|
||||
viewKeeper := NewViewKeeper(accountMapper)
|
||||
|
||||
@ -24,7 +24,7 @@ func defaultContext(key sdk.StoreKey) sdk.Context {
|
||||
cms := store.NewCommitMultiStore(db)
|
||||
cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db)
|
||||
cms.LoadLatestVersion()
|
||||
ctx := sdk.NewContext(cms, abci.Header{}, false, nil, log.NewNopLogger())
|
||||
ctx := sdk.NewContext(cms, abci.Header{}, false, nil, log.NewNopLogger(), nil)
|
||||
return ctx
|
||||
}
|
||||
|
||||
|
||||
32
x/slashing/errors.go
Normal file
32
x/slashing/errors.go
Normal file
@ -0,0 +1,32 @@
|
||||
package slashing
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// Local code type
|
||||
type CodeType = sdk.CodeType
|
||||
|
||||
const (
|
||||
// Default slashing codespace
|
||||
DefaultCodespace sdk.CodespaceType = 10
|
||||
)
|
||||
|
||||
func codeToDefaultMsg(code CodeType) string {
|
||||
switch code {
|
||||
default:
|
||||
return sdk.CodeToDefaultMsg(code)
|
||||
}
|
||||
}
|
||||
|
||||
func msgOrDefaultMsg(msg string, code CodeType) string {
|
||||
if msg != "" {
|
||||
return msg
|
||||
}
|
||||
return codeToDefaultMsg(code)
|
||||
}
|
||||
|
||||
func newError(codespace sdk.CodespaceType, code CodeType, msg string) sdk.Error {
|
||||
msg = msgOrDefaultMsg(msg, code)
|
||||
return sdk.NewError(codespace, code, msg)
|
||||
}
|
||||
148
x/slashing/keeper.go
Normal file
148
x/slashing/keeper.go
Normal file
@ -0,0 +1,148 @@
|
||||
package slashing
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxEvidenceAge - Max age for evidence - 21 days (3 weeks)
|
||||
// TODO Should this be a governance parameter or just modifiable with SoftwareUpgradeProposals?
|
||||
// MaxEvidenceAge = 60 * 60 * 24 * 7 * 3
|
||||
// TODO Temporarily set to 2 minutes for testnets.
|
||||
MaxEvidenceAge int64 = 60 * 2
|
||||
|
||||
// SignedBlocksWindow - sliding window for downtime slashing
|
||||
// TODO Governance parameter?
|
||||
// TODO Temporarily set to 100 blocks for testnets
|
||||
SignedBlocksWindow int64 = 100
|
||||
|
||||
// Downtime slashing threshold - 50%
|
||||
// TODO Governance parameter?
|
||||
MinSignedPerWindow int64 = SignedBlocksWindow / 2
|
||||
|
||||
// Downtime unbond duration - 1 day
|
||||
// TODO Governance parameter?
|
||||
DowntimeUnbondDuration int64 = 86400
|
||||
)
|
||||
|
||||
var (
|
||||
// SlashFractionDoubleSign - currently 5%
|
||||
// TODO Governance parameter?
|
||||
SlashFractionDoubleSign = sdk.NewRat(1).Quo(sdk.NewRat(20))
|
||||
|
||||
// SlashFractionDowntime - currently 0
|
||||
// TODO Governance parameter?
|
||||
SlashFractionDowntime = sdk.ZeroRat()
|
||||
)
|
||||
|
||||
// Keeper of the slashing store
|
||||
type Keeper struct {
|
||||
storeKey sdk.StoreKey
|
||||
cdc *wire.Codec
|
||||
stakeKeeper stake.Keeper
|
||||
|
||||
// codespace
|
||||
codespace sdk.CodespaceType
|
||||
}
|
||||
|
||||
// NewKeeper creates a slashing keeper
|
||||
func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, sk stake.Keeper, codespace sdk.CodespaceType) Keeper {
|
||||
keeper := Keeper{
|
||||
storeKey: key,
|
||||
cdc: cdc,
|
||||
stakeKeeper: sk,
|
||||
codespace: codespace,
|
||||
}
|
||||
return keeper
|
||||
}
|
||||
|
||||
// handle a validator signing two blocks at the same height
|
||||
func (k Keeper) handleDoubleSign(ctx sdk.Context, height int64, timestamp int64, pubkey crypto.PubKey) {
|
||||
logger := ctx.Logger().With("module", "x/slashing")
|
||||
age := ctx.BlockHeader().Time - timestamp
|
||||
if age > MaxEvidenceAge {
|
||||
logger.Info(fmt.Sprintf("Ignored double sign from %v at height %d, age of %d past max age of %d", pubkey.Address(), height, age, MaxEvidenceAge))
|
||||
return
|
||||
}
|
||||
logger.Info(fmt.Sprintf("Confirmed double sign from %v at height %d, age of %d less than max age of %d", pubkey.Address(), height, age, MaxEvidenceAge))
|
||||
validator := k.stakeKeeper.Validator(ctx, pubkey.Address())
|
||||
validator.Slash(ctx, height, SlashFractionDoubleSign)
|
||||
logger.Info(fmt.Sprintf("Slashed validator %s by fraction %v for double-sign at height %d", pubkey.Address(), SlashFractionDoubleSign, height))
|
||||
}
|
||||
|
||||
// handle a validator signature, must be called once per validator per block
|
||||
func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, signed bool) {
|
||||
logger := ctx.Logger().With("module", "x/slashing")
|
||||
height := ctx.BlockHeight()
|
||||
if !signed {
|
||||
logger.Info(fmt.Sprintf("Absent validator %v at height %d", pubkey.Address(), height))
|
||||
}
|
||||
index := height % SignedBlocksWindow
|
||||
address := pubkey.Address()
|
||||
signInfo := k.getValidatorSigningInfo(ctx, address)
|
||||
previous := k.getValidatorSigningBitArray(ctx, address, index)
|
||||
if previous && !signed {
|
||||
k.setValidatorSigningBitArray(ctx, address, index, false)
|
||||
signInfo.SignedBlocksCounter--
|
||||
k.setValidatorSigningInfo(ctx, address, signInfo)
|
||||
} else if !previous && signed {
|
||||
k.setValidatorSigningBitArray(ctx, address, index, true)
|
||||
signInfo.SignedBlocksCounter++
|
||||
k.setValidatorSigningInfo(ctx, address, signInfo)
|
||||
}
|
||||
minHeight := signInfo.StartHeight + SignedBlocksWindow
|
||||
if height > minHeight && signInfo.SignedBlocksCounter < MinSignedPerWindow {
|
||||
validator := k.stakeKeeper.Validator(ctx, address)
|
||||
validator.Slash(ctx, height, SlashFractionDowntime)
|
||||
validator.ForceUnbond(ctx, DowntimeUnbondDuration)
|
||||
logger.Info(fmt.Sprintf("Slashed validator %s by fraction %v and unbonded for downtime at height %d, cannot rebond for %ds",
|
||||
address, SlashFractionDowntime, height, DowntimeUnbondDuration))
|
||||
}
|
||||
}
|
||||
|
||||
func (k Keeper) getValidatorSigningInfo(ctx sdk.Context, address sdk.Address) (info validatorSigningInfo) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
bz := store.Get(validatorSigningInfoKey(address))
|
||||
k.cdc.MustUnmarshalBinary(bz, &info)
|
||||
return
|
||||
}
|
||||
|
||||
func (k Keeper) setValidatorSigningInfo(ctx sdk.Context, address sdk.Address, info validatorSigningInfo) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
bz := k.cdc.MustMarshalBinary(info)
|
||||
store.Set(validatorSigningInfoKey(address), bz)
|
||||
}
|
||||
|
||||
func (k Keeper) getValidatorSigningBitArray(ctx sdk.Context, address sdk.Address, index int64) (signed bool) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
bz := store.Get(validatorSigningBitArrayKey(address, index))
|
||||
k.cdc.MustUnmarshalBinary(bz, &signed)
|
||||
return
|
||||
}
|
||||
|
||||
func (k Keeper) setValidatorSigningBitArray(ctx sdk.Context, address sdk.Address, index int64, signed bool) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
bz := k.cdc.MustMarshalBinary(signed)
|
||||
store.Set(validatorSigningBitArrayKey(address, index), bz)
|
||||
}
|
||||
|
||||
type validatorSigningInfo struct {
|
||||
StartHeight int64 `json:"start_height"`
|
||||
SignedBlocksCounter int64 `json:"signed_blocks_counter"`
|
||||
}
|
||||
|
||||
func validatorSigningInfoKey(v sdk.Address) []byte {
|
||||
return append([]byte{0x01}, v.Bytes()...)
|
||||
}
|
||||
|
||||
func validatorSigningBitArrayKey(v sdk.Address, i int64) []byte {
|
||||
b := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(b, uint64(i))
|
||||
return append([]byte{0x02}, append(v.Bytes(), b...)...)
|
||||
}
|
||||
17
x/slashing/keeper_test.go
Normal file
17
x/slashing/keeper_test.go
Normal file
@ -0,0 +1,17 @@
|
||||
package slashing
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
// sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
func TestHandleDoubleSign(t *testing.T) {
|
||||
require.Equal(t, true, true)
|
||||
// TODO
|
||||
}
|
||||
|
||||
func TestHandleAbsentValidator(t *testing.T) {
|
||||
// TODO
|
||||
}
|
||||
44
x/slashing/tick.go
Normal file
44
x/slashing/tick.go
Normal file
@ -0,0 +1,44 @@
|
||||
package slashing
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
abci "github.com/tendermint/abci/types"
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
)
|
||||
|
||||
func NewBeginBlocker(sk Keeper) sdk.BeginBlocker {
|
||||
return func(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock {
|
||||
heightBytes := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(heightBytes, uint64(req.Header.Height))
|
||||
tags := sdk.NewTags("height", heightBytes)
|
||||
for _, evidence := range req.ByzantineValidators {
|
||||
var pk crypto.PubKey
|
||||
sk.cdc.MustUnmarshalBinary(evidence.PubKey, &pk)
|
||||
switch {
|
||||
case bytes.Compare(evidence.Type, []byte("doubleSign")) == 0:
|
||||
sk.handleDoubleSign(ctx, evidence.Height, evidence.Time, pk)
|
||||
default:
|
||||
ctx.Logger().With("module", "x/slashing").Error(fmt.Sprintf("Ignored unknown evidence type: %s", string(evidence.Type)))
|
||||
}
|
||||
}
|
||||
absent := make(map[string]bool)
|
||||
for _, pubkey := range req.AbsentValidators {
|
||||
var pk crypto.PubKey
|
||||
sk.cdc.MustUnmarshalBinary(pubkey, &pk)
|
||||
absent[string(pk.Bytes())] = true
|
||||
}
|
||||
sk.stakeKeeper.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) (stop bool) {
|
||||
pubkey := validator.GetPubKey()
|
||||
sk.handleValidatorSignature(ctx, pubkey, !absent[string(pubkey.Bytes())])
|
||||
return false
|
||||
})
|
||||
// TODO Add some more tags so clients can track slashing
|
||||
return abci.ResponseBeginBlock{
|
||||
Tags: tags.ToKVPairs(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -103,7 +103,7 @@ func createTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context
|
||||
err := ms.LoadLatestVersion()
|
||||
require.Nil(t, err)
|
||||
|
||||
ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, nil, log.NewNopLogger())
|
||||
ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, nil, log.NewNopLogger(), nil)
|
||||
cdc := makeTestCodec()
|
||||
accountMapper := auth.NewAccountMapper(
|
||||
cdc, // amino codec
|
||||
|
||||
@ -236,3 +236,9 @@ func (v Validator) GetOwner() sdk.Address { return v.Owner }
|
||||
func (v Validator) GetPubKey() crypto.PubKey { return v.PubKey }
|
||||
func (v Validator) GetPower() sdk.Rat { return v.PoolShares.Bonded() }
|
||||
func (v Validator) GetBondHeight() int64 { return v.BondHeight }
|
||||
func (v Validator) Slash(ctx sdk.Context, height int64, fraction sdk.Rat) {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (v Validator) ForceUnbond(ctx sdk.Context, jailDuration int64) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user