Rebase & squash slashing

This commit is contained in:
Christopher Goes 2018-05-23 22:25:56 +02:00
parent f36eb2209e
commit 95c5baf449
No known key found for this signature in database
GPG Key ID: E828D98232D328D3
33 changed files with 400 additions and 121 deletions

32
Gopkg.lock generated
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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
```

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -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"))

View File

@ -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)

View File

@ -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
View 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
View 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
View 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
View 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(),
}
}
}

View File

@ -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

View File

@ -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")
}