diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 93bb0dfd2..9503c0ea7 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -485,7 +485,7 @@ func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res } querier := app.queryRouter.Route(path[1]) if querier == nil { - return sdk.ErrUnknownRequest("no custom querier found for route "+ path[1]).QueryResult() + return sdk.ErrUnknownRequest("no custom querier found for route " + path[1]).QueryResult() } ctx := sdk.NewContext(app.cms.CacheMultiStore(), app.CheckState.Ctx.BlockHeader(), sdk.RunTxModeCheck, app.Logger) @@ -589,7 +589,7 @@ func (app *BaseApp) CheckTx(req abci.RequestCheckTx) (res abci.ResponseCheckTx) Code: uint32(result.Code), Data: result.Data, Log: result.Log, - Events: result.Tags.ToEvents(), + Events: result.GetEvents(), } } @@ -617,7 +617,7 @@ func (app *BaseApp) PreCheckTx(req abci.RequestCheckTx) (res abci.ResponseCheckT Code: uint32(result.Code), Data: result.Data, Log: result.Log, - Events: result.Tags.ToEvents(), + Events: result.GetEvents(), } } @@ -644,7 +644,7 @@ func (app *BaseApp) ReCheckTx(req abci.RequestCheckTx) (res abci.ResponseCheckTx Code: uint32(result.Code), Data: result.Data, Log: result.Log, - Events: result.Tags.ToEvents(), + Events: result.GetEvents(), } } @@ -679,7 +679,7 @@ func (app *BaseApp) DeliverTx(req abci.RequestDeliverTx) (res abci.ResponseDeliv Code: uint32(result.Code), Data: result.Data, Log: result.Log, - Events: result.Tags.ToEvents(), + Events: result.GetEvents(), } } @@ -691,7 +691,7 @@ func (app *BaseApp) PreDeliverTx(req abci.RequestDeliverTx) (res abci.ResponseDe Code: uint32(result.Code), Data: result.Data, Log: result.Log, - Events: result.Tags.ToEvents(), + Events: result.GetEvents(), } } @@ -747,6 +747,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode sdk.RunTxMode) logs := make([]string, 0, len(msgs)) var data []byte // NOTE: we just append them all (?!) var tags sdk.Tags // also just append them all + var events sdk.Events var code sdk.ABCICodeType for msgIdx, msg := range msgs { // Match route. @@ -762,6 +763,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode sdk.RunTxMode) // Append Data and Tags data = append(data, msgResult.Data...) tags = append(tags, msgResult.Tags...) + events = append(events, msgResult.Events...) // Stop execution and return on first failed message. if !msgResult.IsOK() { @@ -771,7 +773,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode sdk.RunTxMode) } // Construct usable logs in multi-message transactions. - logs = append(logs, "Msg " + strconv.Itoa(msgIdx) + ": " + msgResult.Log) + logs = append(logs, "Msg "+strconv.Itoa(msgIdx)+": "+msgResult.Log) } // A tx must only contain one msg. If the msg execution is success, record it if code == sdk.ABCICodeOK { @@ -783,7 +785,8 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode sdk.RunTxMode) Data: data, Log: strings.Join(logs, "\n"), // TODO: FeeAmount/FeeDenom - Tags: tags, + Tags: tags, + Events: events, } return result diff --git a/x/slashing/bsc/bloom9.go b/bsc/bloom9.go similarity index 100% rename from x/slashing/bsc/bloom9.go rename to bsc/bloom9.go diff --git a/x/slashing/bsc/header.go b/bsc/header.go similarity index 99% rename from x/slashing/bsc/header.go rename to bsc/header.go index 71ea1ab61..18e4a419b 100644 --- a/x/slashing/bsc/header.go +++ b/bsc/header.go @@ -6,9 +6,10 @@ import ( "io" "math/big" - "github.com/cosmos/cosmos-sdk/x/slashing/bsc/rlp" "github.com/tendermint/tendermint/crypto/secp256k1" "golang.org/x/crypto/sha3" + + "github.com/cosmos/cosmos-sdk/bsc/rlp" ) type Header struct { diff --git a/x/slashing/bsc/header_test.go b/bsc/header_test.go similarity index 100% rename from x/slashing/bsc/header_test.go rename to bsc/header_test.go diff --git a/x/slashing/bsc/hex.go b/bsc/hex.go similarity index 100% rename from x/slashing/bsc/hex.go rename to bsc/hex.go diff --git a/x/slashing/bsc/json.go b/bsc/json.go similarity index 100% rename from x/slashing/bsc/json.go rename to bsc/json.go diff --git a/bsc/rate.go b/bsc/rate.go new file mode 100644 index 000000000..2e61a0bb8 --- /dev/null +++ b/bsc/rate.go @@ -0,0 +1,18 @@ +package bsc + +import ( + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + BNBDecimalOnBC = 8 + BNBDecimalOnBSC = 18 +) + +func ConvertBCAmountToBSCAmount(bcAmount int64) *big.Int { + decimals := sdk.NewIntWithDecimal(1, int(BNBDecimalOnBSC-BNBDecimalOnBC)) + bscAmount := sdk.NewInt(bcAmount).Mul(decimals) + return bscAmount.BigInt() +} diff --git a/x/slashing/bsc/rlp/decode.go b/bsc/rlp/decode.go similarity index 100% rename from x/slashing/bsc/rlp/decode.go rename to bsc/rlp/decode.go diff --git a/x/slashing/bsc/rlp/decode_tail_test.go b/bsc/rlp/decode_tail_test.go similarity index 100% rename from x/slashing/bsc/rlp/decode_tail_test.go rename to bsc/rlp/decode_tail_test.go diff --git a/x/slashing/bsc/rlp/decode_test.go b/bsc/rlp/decode_test.go similarity index 100% rename from x/slashing/bsc/rlp/decode_test.go rename to bsc/rlp/decode_test.go diff --git a/x/slashing/bsc/rlp/doc.go b/bsc/rlp/doc.go similarity index 100% rename from x/slashing/bsc/rlp/doc.go rename to bsc/rlp/doc.go diff --git a/x/slashing/bsc/rlp/encode.go b/bsc/rlp/encode.go similarity index 100% rename from x/slashing/bsc/rlp/encode.go rename to bsc/rlp/encode.go diff --git a/x/slashing/bsc/rlp/encode_test.go b/bsc/rlp/encode_test.go similarity index 100% rename from x/slashing/bsc/rlp/encode_test.go rename to bsc/rlp/encode_test.go diff --git a/x/slashing/bsc/rlp/encoder_example_test.go b/bsc/rlp/encoder_example_test.go similarity index 100% rename from x/slashing/bsc/rlp/encoder_example_test.go rename to bsc/rlp/encoder_example_test.go diff --git a/x/slashing/bsc/rlp/raw.go b/bsc/rlp/raw.go similarity index 100% rename from x/slashing/bsc/rlp/raw.go rename to bsc/rlp/raw.go diff --git a/x/slashing/bsc/rlp/raw_test.go b/bsc/rlp/raw_test.go similarity index 100% rename from x/slashing/bsc/rlp/raw_test.go rename to bsc/rlp/raw_test.go diff --git a/x/slashing/bsc/rlp/typecache.go b/bsc/rlp/typecache.go similarity index 100% rename from x/slashing/bsc/rlp/typecache.go rename to bsc/rlp/typecache.go diff --git a/x/slashing/bsc/types.go b/bsc/types.go similarity index 100% rename from x/slashing/bsc/types.go rename to bsc/types.go diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 8c89dd61b..52e803157 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -7,7 +7,6 @@ import ( "os" "sort" - "github.com/cosmos/cosmos-sdk/x/ibc" abci "github.com/tendermint/tendermint/abci/types" cmn "github.com/tendermint/tendermint/libs/common" dbm "github.com/tendermint/tendermint/libs/db" @@ -21,8 +20,10 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank" distr "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/ibc" "github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/sidechain" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/stake" ) @@ -60,6 +61,7 @@ type GaiaApp struct { keyParams *sdk.KVStoreKey tkeyParams *sdk.TransientStoreKey keyIbc *sdk.KVStoreKey + keySide *sdk.KVStoreKey // Manage getting and setting accounts accountKeeper auth.AccountKeeper @@ -116,7 +118,8 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio app.cdc, app.keyParams, app.tkeyParams, ) - app.ibcKeeper = ibc.NewKeeper(app.keyIbc, ibc.DefaultCodespace) + app.ibcKeeper = ibc.NewKeeper(app.keyIbc, app.paramsKeeper.Subspace(ibc.DefaultParamspace), ibc.DefaultCodespace, + sidechain.NewKeeper(app.keySide, app.paramsKeeper.Subspace(sidechain.DefaultParamspace), app.cdc)) app.stakeKeeper = stake.NewKeeper( app.cdc, app.keyStake, app.tkeyStake, @@ -223,7 +226,7 @@ func (app *GaiaApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) ab // nolint: unparam func (app *GaiaApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { ctx = ctx.WithEventManager(sdk.NewEventManager()) - tags, _, _ := gov.EndBlocker(ctx, app.govKeeper) + gov.EndBlocker(ctx, app.govKeeper) validatorUpdates, _ := stake.EndBlocker(ctx, app.stakeKeeper) ibc.EndBlocker(ctx, app.ibcKeeper) @@ -232,7 +235,7 @@ func (app *GaiaApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.R return abci.ResponseEndBlock{ ValidatorUpdates: validatorUpdates, - Events: append(tags.ToEvents(), ctx.EventManager().ABCIEvents()...), + Events: ctx.EventManager().ABCIEvents(), } } diff --git a/cmd/gaia/app/app_test.go b/cmd/gaia/app/app_test.go index 68da59ea9..219384c45 100644 --- a/cmd/gaia/app/app_test.go +++ b/cmd/gaia/app/app_test.go @@ -16,6 +16,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/ibc" "github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/sidechain" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/stake" "github.com/stretchr/testify/require" @@ -53,6 +54,7 @@ func NewMockGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppO keyParams: sdk.NewKVStoreKey("params"), tkeyParams: sdk.NewTransientStoreKey("transient_params"), keyIbc: sdk.NewKVStoreKey("ibc"), + keySide: sdk.NewKVStoreKey("side"), } var app = &MockGaiaApp{gApp} @@ -70,7 +72,9 @@ func NewMockGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppO app.cdc, app.keyParams, app.tkeyParams, ) - app.ibcKeeper = ibc.NewKeeper(app.keyIbc, ibc.DefaultCodespace) + app.ibcKeeper = ibc.NewKeeper(app.keyIbc, app.paramsKeeper.Subspace(ibc.DefaultParamspace), ibc.DefaultCodespace, + sidechain.NewKeeper(app.keySide, app.paramsKeeper.Subspace(sidechain.DefaultParamspace), app.cdc)) + app.stakeKeeper = stake.NewKeeper( app.cdc, app.keyStake, app.tkeyStake, diff --git a/cmd/gaia/cmd/gaiadebug/hack.go b/cmd/gaia/cmd/gaiadebug/hack.go index 0101bbec8..63b281606 100644 --- a/cmd/gaia/cmd/gaiadebug/hack.go +++ b/cmd/gaia/cmd/gaiadebug/hack.go @@ -26,6 +26,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/sidechain" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/stake" @@ -138,6 +139,7 @@ type GaiaApp struct { keyParams *sdk.KVStoreKey tkeyParams *sdk.TransientStoreKey keyIbc *sdk.KVStoreKey + keySide *sdk.KVStoreKey // Manage getting and setting accounts accountKeeper auth.AccountKeeper @@ -166,6 +168,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseAp keyParams: sdk.NewKVStoreKey("params"), tkeyParams: sdk.NewTransientStoreKey("transient_params"), keyIbc: sdk.NewKVStoreKey("ibc"), + keySide: sdk.NewKVStoreKey("ibc"), } // define the accountKeeper @@ -178,7 +181,8 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseAp // add handlers app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper) app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams, app.tkeyParams) - app.ibcKeeper = ibc.NewKeeper(app.keyIbc, app.RegisterCodespace(ibc.DefaultCodespace)) + app.ibcKeeper = ibc.NewKeeper(app.keyIbc, app.paramsKeeper.Subspace(ibc.DefaultParamspace), ibc.DefaultCodespace, sidechain.NewKeeper(app.keySide, app.paramsKeeper.Subspace(sidechain.DefaultParamspace), app.cdc)) + app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.tkeyStake, app.bankKeeper, nil, app.paramsKeeper.Subspace(stake.DefaultParamspace), app.RegisterCodespace(stake.DefaultCodespace)) app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.paramsKeeper.Subspace(slashing.DefaultParamspace), app.RegisterCodespace(slashing.DefaultCodespace), app.bankKeeper) diff --git a/go.mod b/go.mod index 6edb4dfa5..66a524268 100644 --- a/go.mod +++ b/go.mod @@ -9,15 +9,17 @@ require ( github.com/binance-chain/tss-lib v1.0.0 github.com/btcsuite/btcd v0.20.0-beta github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d + github.com/go-kit/kit v0.9.0 github.com/golang/protobuf v1.3.2 - github.com/gorilla/context v1.1.1 // indirect - github.com/gorilla/mux v1.6.2 + github.com/gorilla/mux v1.7.3 github.com/hashicorp/golang-lru v0.5.3 github.com/ipfs/go-log v0.0.1 + github.com/magiconair/properties v1.8.1 github.com/mattn/go-isatty v0.0.10 github.com/mitchellh/go-homedir v1.1.0 github.com/pelletier/go-toml v1.4.0 github.com/pkg/errors v0.8.1 + github.com/prometheus/client_golang v1.1.0 github.com/rakyll/statik v0.1.5 github.com/spf13/cobra v0.0.5 github.com/spf13/pflag v1.0.3 @@ -35,7 +37,7 @@ require ( replace ( github.com/tendermint/go-amino => github.com/binance-chain/bnc-go-amino v0.14.1-binance.2 github.com/tendermint/iavl => github.com/binance-chain/bnc-tendermint-iavl v0.12.0-binance.4 - github.com/tendermint/tendermint => github.com/binance-chain/bnc-tendermint v0.32.3-binance.1 + github.com/tendermint/tendermint => github.com/binance-chain/bnc-tendermint v0.32.3-binance.1.0.20200430083314-39f228dd6425 github.com/zondax/ledger-cosmos-go => github.com/binance-chain/ledger-cosmos-go v0.9.9-binance.3 golang.org/x/crypto => github.com/tendermint/crypto v0.0.0-20190823183015-45b1026d81ae ) diff --git a/go.sum b/go.sum index f565cd3f3..1722ad308 100644 --- a/go.sum +++ b/go.sum @@ -18,12 +18,14 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLM github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/binance-chain/bnc-go-amino v0.14.1-binance.2 h1:XcbcfisVItk92UKoGbtNT8nbcfadj3H3ayuM2srAfVs= github.com/binance-chain/bnc-go-amino v0.14.1-binance.2/go.mod h1:yaElUUxWtv/TC/ldGtlKAvS1vKwokxgJ1d97I+6is80= -github.com/binance-chain/bnc-tendermint v0.32.3-binance.1 h1:LDGvORYLSwsTEQM0W7yrbdgjrAZxQDe18WUTLNuFOEc= -github.com/binance-chain/bnc-tendermint v0.32.3-binance.1/go.mod h1:kN5dNxE8voFtDqx2HjbM8sBIH5cUuMtLg0XEHjqzUiY= +github.com/binance-chain/bnc-tendermint v0.32.3-binance.1.0.20200430083314-39f228dd6425 h1:cSbyGFwuRoqABbUIrYjt3PNz1IRmuagQMuHLYiWilwQ= +github.com/binance-chain/bnc-tendermint v0.32.3-binance.1.0.20200430083314-39f228dd6425/go.mod h1:kN5dNxE8voFtDqx2HjbM8sBIH5cUuMtLg0XEHjqzUiY= github.com/binance-chain/bnc-tendermint-iavl v0.12.0-binance.4 h1:BhaV2iiGWfRC6iB8HHOYJeUDwtQMB2pUA4ah+KCbBhI= github.com/binance-chain/bnc-tendermint-iavl v0.12.0-binance.4/go.mod h1:Zmh8GRdNJB8DULIOBar3JCZp6tSpcvM1NGKfE9U2EzA= github.com/binance-chain/ledger-cosmos-go v0.9.9-binance.3 h1:FFpFbkzlP2HUyxQCm0eoU6mkfgMNynfqZRbeWqlaLdQ= @@ -117,6 +119,7 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -126,10 +129,8 @@ github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ= github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= @@ -197,6 +198,8 @@ github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= @@ -337,6 +340,10 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.1/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.2 h1:ZEw4I2EgPKDJ2iEw0cNmLB3ROrEmkOtXIkaG7wZg+78= @@ -405,6 +412,9 @@ github.com/prometheus/client_golang v0.9.1 h1:K47Rk0v/fkEfwfQet2KWhscE0cJzjgCCDB github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= @@ -414,10 +424,16 @@ github.com/prometheus/common v0.0.0-20181020173914-7e9e6cabbd39/go.mod h1:daVV7q github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d h1:GoAlyOgbOEIFdaDqxJVlbOQ1DtGmZWs/Qau0hIlk+WQ= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rakyll/statik v0.1.5 h1:Ly2UjURzxnsSYS0zI50fZ+srA+Fu7EbpV5hglvJvJG0= github.com/rakyll/statik v0.1.5/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= @@ -521,6 +537,7 @@ golang.org/x/net v0.0.0-20190227160552-c95aed5357e7/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190909003024-a7b16738d86b h1:XfVGCX+0T4WOStkaOsJRllbsiImhB2jgVBGc9L0lPGc= golang.org/x/net v0.0.0-20190909003024-a7b16738d86b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -550,6 +567,7 @@ golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -565,6 +583,7 @@ golang.org/x/tools v0.0.0-20181130052023-1c3d964395ce/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA= diff --git a/types/address.go b/types/address.go index c8a58538c..36eb5ac32 100644 --- a/types/address.go +++ b/types/address.go @@ -542,4 +542,4 @@ func GetFromBech32(bech32str, prefix string) ([]byte, error) { } return bz, nil -} \ No newline at end of file +} diff --git a/types/cache.go b/types/cache.go index 222efc0df..f7e03433e 100644 --- a/types/cache.go +++ b/types/cache.go @@ -13,3 +13,26 @@ type AccountCache interface { Cache() AccountCache Write() } + +type DummyAccountCache struct { +} + +func (d *DummyAccountCache) GetAccount(addr AccAddress) Account { + return nil +} + +func (d *DummyAccountCache) SetAccount(addr AccAddress, acc Account) { +} + +func (d *DummyAccountCache) Delete(addr AccAddress) { +} + +func (d *DummyAccountCache) ClearCache() { +} + +func (d *DummyAccountCache) Cache() AccountCache { + return d +} + +func (d *DummyAccountCache) Write() { +} diff --git a/types/context.go b/types/context.go index 873936596..d31166d78 100644 --- a/types/context.go +++ b/types/context.go @@ -264,6 +264,10 @@ func (c Context) WithSideChainKeyPrefix(prefix []byte) Context { return c.withValue(contextKeySideChainKeyPrefix, prefix) } +func (c Context) DepriveSideChainKeyPrefix() Context { + return c.withValue(contextKeySideChainKeyPrefix, nil) +} + func (c Context) WithEventManager(em *EventManager) Context { return c.withValue(contextKeyEventManager, em) } @@ -272,8 +276,13 @@ func (c Context) WithEventManager(em *EventManager) Context { // written to the context when writeCache is called. func (c Context) CacheContext() (cc Context, writeCache func()) { cms := c.MultiStore().CacheMultiStore() - cc = c.WithMultiStore(cms) - return cc, cms.Write + accountCache := c.AccountCache().Cache() + + cc = c.WithMultiStore(cms).WithAccountCache(accountCache) + return cc, func() { + accountCache.Write() + cms.Write() + } } //---------------------------------------- diff --git a/types/context_test.go b/types/context_test.go index e4bdeb178..0a8aac2d0 100644 --- a/types/context_test.go +++ b/types/context_test.go @@ -71,6 +71,8 @@ func TestCacheContext(t *testing.T) { v2 := []byte("value") ctx := defaultContext(key) + ctx = ctx.WithAccountCache(&types.DummyAccountCache{}) + store := ctx.KVStore(key) store.Set(k1, v1) require.Equal(t, v1, store.Get(k1)) diff --git a/types/cross_chain.go b/types/cross_chain.go index f2815d5a6..a04d9a383 100644 --- a/types/cross_chain.go +++ b/types/cross_chain.go @@ -4,29 +4,100 @@ import ( "fmt" "math" "strconv" + + "github.com/tendermint/tendermint/crypto" +) + +const ( + pegInTagName = "peg_in_%s" + pegOutTagName = "peg_out_%s" +) + +var ( + // bnb prefix address: bnb1v8vkkymvhe2sf7gd2092ujc6hweta38xadu2pj + // tbnb prefix address: tbnb1v8vkkymvhe2sf7gd2092ujc6hweta38xnc4wpr + PegAccount = AccAddress(crypto.AddressHash([]byte("BinanceChainPegAccount"))) ) -type IbcChannelID uint8 -type IbcChainID uint16 +func GetPegInTag(symbol string, amount int64) Tag { + return MakeTag(fmt.Sprintf(pegInTagName, symbol), []byte(strconv.FormatInt(amount, 10))) +} + +func GetPegOutTag(symbol string, amount int64) Tag { + return MakeTag(fmt.Sprintf(pegOutTagName, symbol), []byte(strconv.FormatInt(amount, 10))) +} + +type CrossChainPackageType uint8 + +type ChannelID uint8 +type ChainID uint16 + +const ( + SynCrossChainPackageType CrossChainPackageType = 0x00 + AckCrossChainPackageType CrossChainPackageType = 0x01 + FailAckCrossChainPackageType CrossChainPackageType = 0x02 +) + +type ChannelPermission uint8 + +const ( + ChannelAllow ChannelPermission = 1 + ChannelForbidden ChannelPermission = 0 +) -func ParseIbcChannelID(input string) (IbcChannelID, error) { +func IsValidCrossChainPackageType(packageType CrossChainPackageType) bool { + return packageType == SynCrossChainPackageType || packageType == AckCrossChainPackageType || packageType == FailAckCrossChainPackageType +} + +func ParseChannelID(input string) (ChannelID, error) { channelID, err := strconv.Atoi(input) if err != nil { - return IbcChannelID(0), err + return ChannelID(0), err } if channelID > math.MaxInt8 || channelID < 0 { - return IbcChannelID(0), fmt.Errorf("channelID must be in [0, 255]") + return ChannelID(0), fmt.Errorf("channelID must be in [0, 255]") } - return IbcChannelID(channelID), nil + return ChannelID(channelID), nil } -func ParseIbcChainID(input string) (IbcChainID, error) { +func ParseChainID(input string) (ChainID, error) { chainID, err := strconv.Atoi(input) if err != nil { - return IbcChainID(0), err + return ChainID(0), err } if chainID > math.MaxUint16 || chainID < 0 { - return IbcChainID(0), fmt.Errorf("cross chainID must be in [0, 65535]") + return ChainID(0), fmt.Errorf("cross chainID must be in [0, 65535]") + } + return ChainID(chainID), nil +} + +type CrossChainApplication interface { + ExecuteSynPackage(ctx Context, payload []byte) ExecuteResult + ExecuteAckPackage(ctx Context, payload []byte) ExecuteResult + // When the ack application crash, payload is the payload of the origin package. + ExecuteFailAckPackage(ctx Context, payload []byte) ExecuteResult +} + +type ExecuteResult struct { + Err Error + Tags Tags + Payload []byte +} + +func (c ExecuteResult) IsOk() bool { + return c.Err == nil || c.Err.ABCICode().IsOK() +} + +func (c ExecuteResult) Code() ABCICodeType { + if c.Err == nil { + return ABCICodeOK + } + return c.Err.ABCICode() +} + +func (c ExecuteResult) Msg() string { + if c.Err == nil { + return "" } - return IbcChainID(chainID), nil + return c.Err.RawError() } diff --git a/types/cross_chain_test.go b/types/cross_chain_test.go index 566f366aa..6c72c2a86 100644 --- a/types/cross_chain_test.go +++ b/types/cross_chain_test.go @@ -8,23 +8,23 @@ import ( ) func TestParseChannelID(t *testing.T) { - channelID, err := types.ParseIbcChannelID("12") + channelID, err := types.ParseChannelID("12") require.NoError(t, err) - require.Equal(t, types.IbcChannelID(12), channelID) + require.Equal(t, types.ChannelID(12), channelID) - _, err = types.ParseIbcChannelID("1024") + _, err = types.ParseChannelID("1024") require.Error(t, err) } func TestParseCrossChainID(t *testing.T) { - chainID, err := types.ParseIbcChainID("12") + chainID, err := types.ParseChainID("12") require.NoError(t, err) - require.Equal(t, types.IbcChainID(12), chainID) + require.Equal(t, types.ChainID(12), chainID) - chainID, err = types.ParseIbcChainID("10000") + chainID, err = types.ParseChainID("10000") require.NoError(t, err) - require.Equal(t, types.IbcChainID(10000), chainID) + require.Equal(t, types.ChainID(10000), chainID) - _, err = types.ParseIbcChainID("65536") + _, err = types.ParseChainID("65536") require.Error(t, err) } diff --git a/types/errors.go b/types/errors.go index 4dc99fcbd..b7546e1bf 100644 --- a/types/errors.go +++ b/types/errors.go @@ -185,6 +185,7 @@ type Error interface { // set codespace WithDefaultCodespace(CodespaceType) Error + RawError() string // return raw error message Code() CodeType Codespace() CodespaceType ABCILog() string @@ -248,6 +249,11 @@ Message: %#v `, err.codespace, err.code, err.cmnError.Error()) } +// RawError returns raw error message of error +func (err *sdkError) RawError() string { + return err.cmnError.Error() +} + // Implements ABCIError. func (err *sdkError) ABCICode() ABCICodeType { return ToABCICode(err.codespace, err.code) diff --git a/types/fees/fee_calculator.go b/types/fees/fee_calculator.go new file mode 100644 index 000000000..446334f94 --- /dev/null +++ b/types/fees/fee_calculator.go @@ -0,0 +1,57 @@ +package fees + +import ( + "github.com/cosmos/cosmos-sdk/types" + param "github.com/cosmos/cosmos-sdk/x/paramHub/types" +) + +type FeeCalculator func(msg types.Msg) types.Fee +type FeeCalculatorGenerator func(params param.FeeParam) FeeCalculator + +var calculators = make(map[string]FeeCalculator) +var CalculatorsGen = make(map[string]FeeCalculatorGenerator) + +func RegisterCalculator(msgType string, feeCalc FeeCalculator) { + calculators[msgType] = feeCalc +} + +func GetCalculatorGenerator(msgType string) FeeCalculatorGenerator { + return CalculatorsGen[msgType] +} + +func GetCalculator(msgType string) FeeCalculator { + return calculators[msgType] +} + +func UnsetAllCalculators() { + for key := range calculators { + delete(calculators, key) + } +} + +func FixedFeeCalculator(amount int64, feeType types.FeeDistributeType) FeeCalculator { + if feeType == types.FeeFree { + return FreeFeeCalculator() + } + return func(msg types.Msg) types.Fee { + return types.NewFee(append(types.Coins{}, types.NewCoin(types.NativeTokenSymbol, amount)), feeType) + } +} + +func FreeFeeCalculator() FeeCalculator { + return func(msg types.Msg) types.Fee { + return types.NewFee(append(types.Coins{}), types.FeeFree) + } +} + +var FixedFeeCalculatorGen = func(params param.FeeParam) FeeCalculator { + if defaultParam, ok := params.(*param.FixedFeeParams); ok { + if defaultParam.Fee <= 0 || defaultParam.FeeFor == types.FeeFree { + return FreeFeeCalculator() + } else { + return FixedFeeCalculator(defaultParam.Fee, defaultParam.FeeFor) + } + } else { + panic("Generator receive unexpected param type") + } +} diff --git a/types/fees/fee_calculator_test.go b/types/fees/fee_calculator_test.go new file mode 100644 index 000000000..d3e2dd8b9 --- /dev/null +++ b/types/fees/fee_calculator_test.go @@ -0,0 +1,61 @@ +package fees + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/secp256k1" + + "github.com/cosmos/cosmos-sdk/types" +) + +func TestFixedFeeCalculator(t *testing.T) { + _, addr := privAndAddr() + msg := types.NewTestMsg(addr) + calculator := FixedFeeCalculator(10, types.FeeFree) + fee := calculator(msg) + require.Equal(t, types.FeeFree, fee.Type) + require.Equal(t, types.Coins{}, fee.Tokens) + + calculator = FixedFeeCalculator(10, types.FeeForAll) + fee = calculator(msg) + require.Equal(t, types.FeeForAll, fee.Type) + require.Equal(t, types.Coins{types.NewCoin(types.NativeTokenSymbol, 10)}, fee.Tokens) + + calculator = FixedFeeCalculator(10, types.FeeForProposer) + fee = calculator(msg) + require.Equal(t, types.FeeForProposer, fee.Type) + require.Equal(t, types.Coins{types.NewCoin(types.NativeTokenSymbol, 10)}, fee.Tokens) +} + +func TestFreeFeeCalculator(t *testing.T) { + _, addr := privAndAddr() + msg := types.NewTestMsg(addr) + + calculator := FreeFeeCalculator() + fee := calculator(msg) + require.Equal(t, types.FeeFree, fee.Type) + require.Equal(t, types.Coins{}, fee.Tokens) +} + +func TestRegisterAndGetCalculators(t *testing.T) { + _, addr := privAndAddr() + msg := types.NewTestMsg(addr) + + RegisterCalculator(msg.Type(), FixedFeeCalculator(10, types.FeeForProposer)) + calculator := GetCalculator(msg.Type()) + require.NotNil(t, calculator) + fee := calculator(msg) + require.Equal(t, types.FeeForProposer, fee.Type) + require.Equal(t, types.Coins{types.NewCoin(types.NativeTokenSymbol, 10)}, fee.Tokens) + + UnsetAllCalculators() + require.Nil(t, GetCalculator(msg.Type())) +} + +func privAndAddr() (crypto.PrivKey, types.AccAddress) { + priv := secp256k1.GenPrivKey() + addr := types.AccAddress(priv.PubKey().Address()) + return priv, addr +} diff --git a/types/oracle.go b/types/oracle.go deleted file mode 100644 index 1184b8ebe..000000000 --- a/types/oracle.go +++ /dev/null @@ -1,22 +0,0 @@ -package types - -type ClaimResult struct { - Code int - Msg string - Tags Tags -} - -type ClaimHooks interface { - CheckClaim(ctx Context, claim string) Error - ExecuteClaim(ctx Context, finalClaim string) (ClaimResult, Error) -} - -// Type that represents Claim Type as a byte -type ClaimType byte - -type OracleKeeper interface { - GetClaimTypeName(claimType ClaimType) string - GetCurrentSequence(ctx Context, claimType ClaimType) int64 - IncreaseSequence(ctx Context, claimType ClaimType) int64 - RegisterClaimType(claimType ClaimType, claimTypeName string, hooks ClaimHooks) error -} diff --git a/types/pool.go b/types/pool.go index fc8deaf6e..f3ab009bc 100644 --- a/types/pool.go +++ b/types/pool.go @@ -18,7 +18,7 @@ func (p *Pool) AddTx(tx Tx, txHash string) { p.txs.Store(txHash, tx) } -func (p Pool) GetTxs() sync.Map{ +func (p Pool) GetTxs() sync.Map { return p.txs } diff --git a/types/result.go b/types/result.go index 1b5d7cc70..cf65691f4 100644 --- a/types/result.go +++ b/types/result.go @@ -1,5 +1,7 @@ package types +import abci "github.com/tendermint/tendermint/abci/types" + // Result is the union of ResponseDeliverTx and ResponseCheckTx. type Result struct { @@ -17,10 +19,19 @@ type Result struct { FeeDenom string // Tags are used for transaction indexing and pubsub. - Tags Tags + Tags Tags + Events Events } // TODO: In the future, more codes may be OK. func (res Result) IsOK() bool { return res.Code.IsOK() } + +func (res Result) GetEvents() []abci.Event { + events := res.Tags.ToEvents() + if res.Events != nil { + events = append(events, res.Events.ToABCIEvents()...) + } + return events +} diff --git a/types/script.go b/types/script.go index ecd974a26..338b7a0e8 100644 --- a/types/script.go +++ b/types/script.go @@ -10,4 +10,4 @@ func RegisterScripts(msgType string, scripts ...Script) { func GetRegisteredScripts(msgType string) []Script { return scriptsHub[msgType] -} \ No newline at end of file +} diff --git a/types/tags.go b/types/tags.go index 6907a877c..502064eab 100644 --- a/types/tags.go +++ b/types/tags.go @@ -33,7 +33,7 @@ func (t Tags) ToKVPairs() []cmn.KVPair { // Turn tags into abci.Event list func (t Tags) ToEvents() []abci.Event { - return []abci.Event{abci.Event{Attributes: t}} + return []abci.Event{{Attributes: t}} } // New variadic tags, must be k string, v []byte repeating diff --git a/types/tokens.go b/types/tokens.go new file mode 100644 index 000000000..eea072c86 --- /dev/null +++ b/types/tokens.go @@ -0,0 +1,7 @@ +package types + +const ( + NativeTokenSymbol = "BNB" + + TokenMaxTotalSupply int64 = 9000000000000000000 // 90 billions with 8 decimal digits +) diff --git a/types/upgrade.go b/types/upgrade.go index 8a180963f..2f1125018 100644 --- a/types/upgrade.go +++ b/types/upgrade.go @@ -6,6 +6,9 @@ var UpgradeMgr = NewUpgradeManager(UpgradeConfig{}) const ( FixSignBytesOverflow = "FixSignBytesOverflow" // fix json unmarshal overflow when build SignBytes + BEP9 = "BEP9" // https://github.com/binance-chain/BEPs/pull/9 + BEP12 = "BEP12" // https://github.com/binance-chain/BEPs/pull/17 + BEP3 = "BEP3" // https://github.com/binance-chain/BEPs/pull/30 BEP8 = "BEP8" // Mini-BEP2 token LaunchBscUpgrade = "LaunchBscUpgrade" ) diff --git a/x/bank/fee_calculator.go b/x/bank/fee_calculator.go new file mode 100644 index 000000000..7d1e00147 --- /dev/null +++ b/x/bank/fee_calculator.go @@ -0,0 +1,41 @@ +package bank + +import ( + "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/fees" + param "github.com/cosmos/cosmos-sdk/x/paramHub/types" + "github.com/tendermint/tendermint/libs/common" +) + +var TransferFeeCalculatorGen = fees.FeeCalculatorGenerator(func(params param.FeeParam) fees.FeeCalculator { + transferFeeParam, ok := params.(*param.TransferFeeParam) + if !ok { + panic("Generator received unexpected param type") + } + + return fees.FeeCalculator(func(msg types.Msg) types.Fee { + transferMsg, ok := msg.(MsgSend) + if !ok { + panic("unexpected msg for TransferFeeCalculator") + } + + totalFee := transferFeeParam.Fee + var inputNum int64 = 0 + for _, input := range transferMsg.Inputs { + inputNum += int64(len(input.Coins)) + } + var outputNum int64 = 0 + for _, output := range transferMsg.Outputs { + outputNum += int64(len(output.Coins)) + } + num := common.MaxInt64(inputNum, outputNum) + if num >= transferFeeParam.LowerLimitAsMulti { + if num > types.TokenMaxTotalSupply/transferFeeParam.MultiTransferFee { + totalFee = types.TokenMaxTotalSupply + } else { + totalFee = transferFeeParam.MultiTransferFee * num + } + } + return types.NewFee(types.Coins{types.NewCoin(types.NativeTokenSymbol, totalFee)}, transferFeeParam.FeeFor) + }) +}) diff --git a/x/bank/handler.go b/x/bank/handler.go index 8454f45d7..018d80ff5 100644 --- a/x/bank/handler.go +++ b/x/bank/handler.go @@ -2,6 +2,7 @@ package bank import ( "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -34,7 +35,7 @@ func handleMsgSend(ctx sdk.Context, k Keeper, msg MsgSend) sdk.Result { if sdk.IsUpgrade(sdk.BEP8) { am := k.GetAccountKeeper() for _, in := range msg.Inputs { - if err := checkAndValidateMiniTokenCoins(ctx, am, in.Address, in.Coins); err != nil { + if err := CheckAndValidateMiniTokenCoins(ctx, am, in.Address, in.Coins); err != nil { return err.Result() } } diff --git a/x/bank/mini_token_helper.go b/x/bank/mini_token_helper.go index f3914c720..5235cfa89 100644 --- a/x/bank/mini_token_helper.go +++ b/x/bank/mini_token_helper.go @@ -14,7 +14,7 @@ const ( MiniTokenMinExecutionAmount int64 = 100000000 // 1 with 8 decimal digits ) -func checkAndValidateMiniTokenCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress, coins sdk.Coins) sdk.Error { +func CheckAndValidateMiniTokenCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress, coins sdk.Coins) sdk.Error { var err sdk.Error for _, coin := range coins { if isMiniTokenSymbol(coin.Denom) { diff --git a/x/bank/msgs_test.go b/x/bank/msgs_test.go index 38ee27f0a..f037543df 100644 --- a/x/bank/msgs_test.go +++ b/x/bank/msgs_test.go @@ -149,7 +149,7 @@ func TestMsgSendValidation(t *testing.T) { {false, MsgSend{ Inputs: []Input{NewInput(emptyAddr, atom123)}, // invalid input Outputs: []Output{output1}}, - }, + }, {false, MsgSend{ Inputs: []Input{NewInput(shortAddr, atom123)}, // invalid input Outputs: []Output{output1}}, diff --git a/x/distribution/keeper/test_common.go b/x/distribution/keeper/test_common.go index 77409da2d..3573b22c3 100644 --- a/x/distribution/keeper/test_common.go +++ b/x/distribution/keeper/test_common.go @@ -128,9 +128,9 @@ func CreateTestInputAdvanced(t *testing.T, isCheckTx bool, initCoins int64, ctx = ctx.WithAccountCache(accountCache) ck := bank.NewBaseKeeper(accountKeeper) - ibcKeeper := ibc.NewKeeper(keyIbc, ibc.DefaultCodespace) - scKeeper := sidechain.NewKeeper(keySideChain, pk.Subspace(sidechain.DefaultParamspace)) + scKeeper := sidechain.NewKeeper(keySideChain, pk.Subspace(sidechain.DefaultParamspace), cdc) scKeeper.SetParams(ctx, sidechain.DefaultParams()) + ibcKeeper := ibc.NewKeeper(keyIbc, pk.Subspace(ibc.DefaultParamspace), ibc.DefaultCodespace, scKeeper) sk := stake.NewKeeper(cdc, keyStake, tkeyStake, ck, nil, pk.Subspace(stake.DefaultParamspace), stake.DefaultCodespace) sk.SetPool(ctx, stake.InitialPool()) sk.SetParams(ctx, stake.DefaultParams()) diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index a2b1e35d8..ceeddcedc 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -19,6 +19,7 @@ import ( authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/gov/client" + "github.com/cosmos/cosmos-sdk/x/sidechain/types" ) const ( @@ -39,6 +40,7 @@ const ( flagQuoteAsset = "quote-asset-symbol" flagInitPrice = "init-price" flagExpireTime = "expire-time" + flagSideChainId = "side-chain-id" ) type proposal struct { @@ -47,6 +49,7 @@ type proposal struct { VotingPeriod int64 `json:"voting_period"` Type string `json:"type"` Deposit string `json:"deposit"` + SideChainId string `json:"side_chain_id, omitempty"` } var proposalFlags = []string{ @@ -99,6 +102,10 @@ $ CLI gov submit-proposal --title="Test Proposal" --description="My awesome prop return errors.New(fmt.Sprintf("Proposal description is longer than max length of %d", gov.MaxDescriptionLength)) } + if proposal.SideChainId != gov.NativeChainID && len(proposal.SideChainId) > types.MaxSideChainIdLength { + return errors.New(fmt.Sprintf("chain-id exceed the length limit %d", types.MaxSideChainIdLength)) + } + txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc) cliCtx := context.NewCLIContext(). WithCodec(cdc). @@ -108,6 +115,7 @@ $ CLI gov submit-proposal --title="Test Proposal" --description="My awesome prop return errors.New("voting period should be positive") } + sideChainId := proposal.SideChainId votingPeriod := time.Duration(proposal.VotingPeriod) * time.Second if votingPeriod > gov.MaxVotingPeriod { return errors.New(fmt.Sprintf("voting period should be less than %d seconds", gov.MaxVotingPeriod/time.Second)) @@ -127,8 +135,12 @@ $ CLI gov submit-proposal --title="Test Proposal" --description="My awesome prop if err != nil { return err } - - msg := gov.NewMsgSubmitProposal(proposal.Title, proposal.Description, proposalType, fromAddr, amount, votingPeriod) + var msg sdk.Msg + if sideChainId == gov.NativeChainID { + msg = gov.NewMsgSubmitProposal(proposal.Title, proposal.Description, proposalType, fromAddr, amount, votingPeriod) + } else { + msg = gov.NewMsgSideChainSubmitProposal(proposal.Title, proposal.Description, proposalType, fromAddr, amount, votingPeriod, sideChainId) + } err = msg.ValidateBasic() if err != nil { return err @@ -151,7 +163,7 @@ $ CLI gov submit-proposal --title="Test Proposal" --description="My awesome prop cmd.Flags().String(flagProposalType, "", "proposalType of proposal, types: text/parameter_change/software_upgrade") cmd.Flags().String(flagDeposit, "", "deposit of proposal") cmd.Flags().String(flagProposal, "", "proposal file path (if this path is given, other proposal flags are ignored)") - + cmd.Flags().String(flagSideChainId, gov.NativeChainID, "the id of side chain, default is native chain") return cmd } @@ -165,6 +177,7 @@ func parseSubmitProposalFlags() (*proposal, error) { proposal.VotingPeriod = viper.GetInt64(flagVotingPeriod) proposal.Type = client.NormalizeProposalType(viper.GetString(flagProposalType)) proposal.Deposit = viper.GetString(flagDeposit) + proposal.SideChainId = viper.GetString(flagSideChainId) return proposal, nil } @@ -204,13 +217,22 @@ func GetCmdDeposit(cdc *codec.Codec) *cobra.Command { } proposalID := viper.GetInt64(flagProposalID) + sideChainId := viper.GetString(flagSideChainId) + if len(sideChainId) > types.MaxSideChainIdLength { + return fmt.Errorf("side-chain-id exceed the max length %d", types.MaxSideChainIdLength) + } amount, err := sdk.ParseCoins(viper.GetString(flagDeposit)) if err != nil { return err } + var msg sdk.Msg + if sideChainId == gov.NativeChainID { + msg = gov.NewMsgDeposit(depositerAddr, proposalID, amount) + } else { + msg = gov.NewMsgSideChainDeposit(depositerAddr, proposalID, amount, sideChainId) + } - msg := gov.NewMsgDeposit(depositerAddr, proposalID, amount) err = msg.ValidateBasic() if err != nil { return err @@ -228,6 +250,7 @@ func GetCmdDeposit(cdc *codec.Codec) *cobra.Command { cmd.Flags().String(flagProposalID, "", "proposalID of proposal depositing on") cmd.Flags().String(flagDeposit, "", "amount of deposit") + cmd.Flags().String(flagSideChainId, gov.NativeChainID, "the id of side chain, default is native chain") return cmd } @@ -250,13 +273,22 @@ func GetCmdVote(cdc *codec.Codec) *cobra.Command { proposalID := viper.GetInt64(flagProposalID) option := viper.GetString(flagOption) + sideChainId := viper.GetString(flagSideChainId) + + if len(sideChainId) > types.MaxSideChainIdLength { + return fmt.Errorf("side-chain-id exceed the max length %d", types.MaxSideChainIdLength) + } byteVoteOption, err := gov.VoteOptionFromString(client.NormalizeVoteOption(option)) if err != nil { return err } - - msg := gov.NewMsgVote(voterAddr, proposalID, byteVoteOption) + var msg sdk.Msg + if sideChainId == gov.NativeChainID { + msg = gov.NewMsgVote(voterAddr, proposalID, byteVoteOption) + } else { + msg = gov.NewMsgSideChainVote(voterAddr, proposalID, byteVoteOption, sideChainId) + } err = msg.ValidateBasic() if err != nil { return err @@ -265,10 +297,15 @@ func GetCmdVote(cdc *codec.Codec) *cobra.Command { if cliCtx.GenerateOnly { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } - - fmt.Printf("Vote[Voter:%s,ProposalID:%d,Option:%s]", - voterAddr.String(), msg.ProposalID, msg.Option.String(), - ) + if sideChainId == gov.NativeChainID { + fmt.Printf("Vote[Voter:%s,ProposalID:%d,Option:%s]", + voterAddr.String(), proposalID, option, + ) + } else { + fmt.Printf("Vote[Voter:%s,ProposalID:%d,Option:%s, sideChainId:%s]", + voterAddr.String(), proposalID, option, sideChainId, + ) + } // Build and sign the transaction, then broadcast to a Tendermint // node. @@ -278,6 +315,7 @@ func GetCmdVote(cdc *codec.Codec) *cobra.Command { cmd.Flags().String(flagProposalID, "", "proposalID of proposal voting on") cmd.Flags().String(flagOption, "", "vote option {yes, no, no_with_veto, abstain}") + cmd.Flags().String(flagSideChainId, gov.NativeChainID, "the id of side chain, default is native chain") return cmd } @@ -290,8 +328,10 @@ func GetCmdQueryProposal(queryRoute string, cdc *codec.Codec) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) proposalID := viper.GetInt64(flagProposalID) + sideChainId := viper.GetString(flagSideChainId) params := gov.QueryProposalParams{ + BaseParams: gov.NewBaseParams(sideChainId), ProposalID: proposalID, } @@ -311,6 +351,7 @@ func GetCmdQueryProposal(queryRoute string, cdc *codec.Codec) *cobra.Command { } cmd.Flags().String(flagProposalID, "", "proposalID of proposal being queried") + cmd.Flags().String(flagSideChainId, "", "the id of side chain, default is native chain") return cmd } @@ -325,8 +366,10 @@ func GetCmdQueryProposals(queryRoute string, cdc *codec.Codec) *cobra.Command { bechVoterAddr := viper.GetString(flagVoter) strProposalStatus := viper.GetString(flagStatus) latestProposalsIDs := viper.GetInt64(flagLatestProposalIDs) + sideChainId := viper.GetString(flagSideChainId) params := gov.QueryProposalsParams{ + BaseParams: gov.NewBaseParams(sideChainId), NumLatestProposals: latestProposalsIDs, } @@ -389,6 +432,7 @@ func GetCmdQueryProposals(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd.Flags().String(flagDepositer, "", "(optional) filter by proposals deposited on by depositer") cmd.Flags().String(flagVoter, "", "(optional) filter by proposals voted on by voted") cmd.Flags().String(flagStatus, "", "(optional) filter proposals by proposal status, status: deposit_period/voting_period/passed/rejected") + cmd.Flags().String(flagSideChainId, "", "the id of side chain, default is native chain") return cmd } @@ -402,6 +446,7 @@ func GetCmdQueryVote(queryRoute string, cdc *codec.Codec) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) proposalID := viper.GetInt64(flagProposalID) + sideChainId := viper.GetString(flagSideChainId) voterAddr, err := sdk.AccAddressFromBech32(viper.GetString(flagVoter)) if err != nil { @@ -409,6 +454,7 @@ func GetCmdQueryVote(queryRoute string, cdc *codec.Codec) *cobra.Command { } params := gov.QueryVoteParams{ + BaseParams: gov.NewBaseParams(sideChainId), Voter: voterAddr, ProposalID: proposalID, } @@ -429,6 +475,7 @@ func GetCmdQueryVote(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd.Flags().String(flagProposalID, "", "proposalID of proposal voting on") cmd.Flags().String(flagVoter, "", "bech32 voter address") + cmd.Flags().String(flagSideChainId, "", "the id of side chain, default is native chain") return cmd } @@ -441,8 +488,10 @@ func GetCmdQueryVotes(queryRoute string, cdc *codec.Codec) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) proposalID := viper.GetInt64(flagProposalID) + sideChainId := viper.GetString(flagSideChainId) params := gov.QueryVotesParams{ + BaseParams: gov.NewBaseParams(sideChainId), ProposalID: proposalID, } bz, err := cdc.MarshalJSON(params) @@ -461,6 +510,7 @@ func GetCmdQueryVotes(queryRoute string, cdc *codec.Codec) *cobra.Command { } cmd.Flags().String(flagProposalID, "", "proposalID of which proposal's votes are being queried") + cmd.Flags().String(flagSideChainId, "", "the id of side chain, default is native chain") return cmd } @@ -474,6 +524,7 @@ func GetCmdQueryDeposit(queryRoute string, cdc *codec.Codec) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) proposalID := viper.GetInt64(flagProposalID) + sideChainId := viper.GetString(flagSideChainId) depositerAddr, err := sdk.AccAddressFromBech32(viper.GetString(flagDepositer)) if err != nil { @@ -481,6 +532,7 @@ func GetCmdQueryDeposit(queryRoute string, cdc *codec.Codec) *cobra.Command { } params := gov.QueryDepositParams{ + BaseParams: gov.NewBaseParams(sideChainId), Depositer: depositerAddr, ProposalID: proposalID, } @@ -501,7 +553,7 @@ func GetCmdQueryDeposit(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd.Flags().String(flagProposalID, "", "proposalID of proposal deposited on") cmd.Flags().String(flagDepositer, "", "bech32 depositer address") - + cmd.Flags().String(flagSideChainId, "", "the id of side chain, default is native chain") return cmd } @@ -513,8 +565,10 @@ func GetCmdQueryDeposits(queryRoute string, cdc *codec.Codec) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) proposalID := viper.GetInt64(flagProposalID) + sideChainId := viper.GetString(flagSideChainId) params := gov.QueryDepositsParams{ + BaseParams: gov.NewBaseParams(sideChainId), ProposalID: proposalID, } bz, err := cdc.MarshalJSON(params) @@ -533,6 +587,7 @@ func GetCmdQueryDeposits(queryRoute string, cdc *codec.Codec) *cobra.Command { } cmd.Flags().String(flagProposalID, "", "proposalID of which proposal's deposits are being queried") + cmd.Flags().String(flagSideChainId, "", "the id of side chain, default is native chain") return cmd } @@ -545,8 +600,10 @@ func GetCmdQueryTally(queryRoute string, cdc *codec.Codec) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) proposalID := viper.GetInt64(flagProposalID) + sideChainId := viper.GetString(flagSideChainId) params := gov.QueryTallyParams{ + BaseParams: gov.NewBaseParams(sideChainId), ProposalID: proposalID, } bz, err := cdc.MarshalJSON(params) @@ -565,6 +622,7 @@ func GetCmdQueryTally(queryRoute string, cdc *codec.Codec) *cobra.Command { } cmd.Flags().String(flagProposalID, "", "proposalID of which proposal is being tallied") + cmd.Flags().String(flagSideChainId, "", "the id of side chain, default is native chain") return cmd } diff --git a/x/gov/client/rest/rest.go b/x/gov/client/rest/rest.go index d49ffcd55..7214cf4f2 100644 --- a/x/gov/client/rest/rest.go +++ b/x/gov/client/rest/rest.go @@ -305,7 +305,7 @@ func queryDepositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Han var deposit gov.Deposit cdc.UnmarshalJSON(res, &deposit) if deposit.Empty() { - res, err := cliCtx.QueryWithData("custom/gov/proposal", cdc.MustMarshalBinaryLengthPrefixed(gov.QueryProposalParams{params.ProposalID})) + res, err := cliCtx.QueryWithData("custom/gov/proposal", cdc.MustMarshalBinaryLengthPrefixed(gov.QueryProposalParams{ProposalID: params.ProposalID})) if err != nil || len(res) == 0 { err := errors.Errorf("proposalID [%d] does not exist", proposalID) utils.WriteErrorResponse(w, http.StatusNotFound, err.Error()) @@ -368,7 +368,7 @@ func queryVoteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Handle var vote gov.Vote cdc.UnmarshalJSON(res, &vote) if vote.Empty() { - bz, err := cdc.MarshalJSON(gov.QueryProposalParams{params.ProposalID}) + bz, err := cdc.MarshalJSON(gov.QueryProposalParams{ProposalID: params.ProposalID}) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return diff --git a/x/gov/codec.go b/x/gov/codec.go index 6df535449..fdaca87c0 100644 --- a/x/gov/codec.go +++ b/x/gov/codec.go @@ -11,6 +11,10 @@ func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(MsgDeposit{}, "cosmos-sdk/MsgDeposit", nil) cdc.RegisterConcrete(MsgVote{}, "cosmos-sdk/MsgVote", nil) + cdc.RegisterConcrete(MsgSideChainSubmitProposal{}, "cosmos-sdk/MsgSideChainSubmitProposal", nil) + cdc.RegisterConcrete(MsgSideChainDeposit{}, "cosmos-sdk/MsgSideChainDeposit", nil) + cdc.RegisterConcrete(MsgSideChainVote{}, "cosmos-sdk/MsgSideChainVote", nil) + cdc.RegisterInterface((*Proposal)(nil), nil) cdc.RegisterConcrete(&TextProposal{}, "gov/TextProposal", nil) } diff --git a/x/gov/common_test.go b/x/gov/common_test.go index 851a2a3f1..f9893d2e0 100644 --- a/x/gov/common_test.go +++ b/x/gov/common_test.go @@ -37,8 +37,9 @@ func getMockApp(t *testing.T, numGenAccs int) (*mock.App, bank.BaseKeeper, gov.K pk := params.NewKeeper(mapp.Cdc, keyGlobalParams, tkeyGlobalParams) ck := bank.NewBaseKeeper(mapp.AccountKeeper) - ibcKeeper := ibc.NewKeeper(keyIbc, ibc.DefaultCodespace) - scKeeper := sidechain.NewKeeper(keySideChain, pk.Subspace(sidechain.DefaultParamspace)) + scKeeper := sidechain.NewKeeper(keySideChain, pk.Subspace(sidechain.DefaultParamspace), mapp.Cdc) + ibcKeeper := ibc.NewKeeper(keyIbc, pk.Subspace(ibc.DefaultParamspace), ibc.DefaultCodespace, scKeeper) + sk := stake.NewKeeper(mapp.Cdc, keyStake, tkeyStake, ck, nil, pk.Subspace(stake.DefaultParamspace), mapp.RegisterCodespace(stake.DefaultCodespace)) sk.SetupForSideChain(&scKeeper, &ibcKeeper) keeper := gov.NewKeeper(mapp.Cdc, keyGov, pk, pk.Subspace("testgov"), ck, sk, gov.DefaultCodespace, new(sdk.Pool)) @@ -60,9 +61,9 @@ func getMockApp(t *testing.T, numGenAccs int) (*mock.App, bank.BaseKeeper, gov.K // gov and stake endblocker func getEndBlocker(keeper gov.Keeper) sdk.EndBlocker { return func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { - tags, _, _ := gov.EndBlocker(ctx, keeper) + gov.EndBlocker(ctx, keeper) return abci.ResponseEndBlock{ - Events: tags.ToEvents(), + Events: ctx.EventManager().ABCIEvents(), } } } diff --git a/x/gov/errors.go b/x/gov/errors.go index 058fcb859..56214de30 100644 --- a/x/gov/errors.go +++ b/x/gov/errors.go @@ -24,6 +24,7 @@ const ( CodeInvalidProposalStatus sdk.CodeType = 11 CodeInvalidProposal sdk.CodeType = 12 CodeInvalidVotingPeriod sdk.CodeType = 13 + CodeInvalidSideChainId sdk.CodeType = 14 ) //---------------------------------------- @@ -76,3 +77,7 @@ func ErrInvalidGenesis(codespace sdk.CodespaceType, msg string) sdk.Error { func ErrInvalidVotingPeriod(codespace sdk.CodespaceType, votingPeriod time.Duration) sdk.Error { return sdk.NewError(codespace, CodeInvalidVotingPeriod, fmt.Sprintf("Voting period '%d' should larger than 0 and less than %s", votingPeriod, MaxVotingPeriod)) } + +func ErrInvalidSideChainId(codespace sdk.CodespaceType, sideChain string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidSideChainId, fmt.Sprintf("Invalid side chain id: %s", sideChain)) +} diff --git a/x/gov/events/events.go b/x/gov/events/events.go new file mode 100644 index 000000000..fa38b9a97 --- /dev/null +++ b/x/gov/events/events.go @@ -0,0 +1,11 @@ +package events + +var ( + EventTypeProposalDropped = "proposal-dropped" + EventTypeProposalPassed = "proposal-passed" + EventTypeProposalRejected = "proposal-rejected" + + ProposalID = "proposal-id" + VotingPeriodStart = "voting-period-start" + SideChainID = "side-chain-id" +) diff --git a/x/gov/genesis.go b/x/gov/genesis.go index 0dfee258d..beff69cae 100644 --- a/x/gov/genesis.go +++ b/x/gov/genesis.go @@ -48,8 +48,8 @@ func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { // TODO: Handle this with #870 panic(err) } - k.setDepositParams(ctx, data.DepositParams) - k.setTallyParams(ctx, data.TallyParams) + k.SetDepositParams(ctx, data.DepositParams) + k.SetTallyParams(ctx, data.TallyParams) } // WriteGenesis - output genesis parameters diff --git a/x/gov/handler.go b/x/gov/handler.go index 574653017..db6ac09d6 100644 --- a/x/gov/handler.go +++ b/x/gov/handler.go @@ -2,8 +2,10 @@ package gov import ( "fmt" + "strconv" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov/events" "github.com/cosmos/cosmos-sdk/x/gov/tags" ) @@ -17,6 +19,12 @@ func NewHandler(keeper Keeper) sdk.Handler { return handleMsgSubmitProposal(ctx, keeper, msg) case MsgVote: return handleMsgVote(ctx, keeper, msg) + case MsgSideChainDeposit: + return handleMsgSideChainDeposit(ctx, keeper, msg) + case MsgSideChainSubmitProposal: + return handleMsgSideChainSubmitProposal(ctx, keeper, msg) + case MsgSideChainVote: + return handleMsgSideChainVote(ctx, keeper, msg) default: errMsg := "Unrecognized gov msg type" return sdk.ErrUnknownRequest(errMsg).Result() @@ -110,14 +118,41 @@ func handleMsgVote(ctx sdk.Context, keeper Keeper, msg MsgVote) sdk.Result { } } -// Called every block, process inflation, update validator set -func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags, refundProposals, notRefundProposals []int64) { +type SimpleProposal struct { + Id int64 + ChainID string +} + +func EndBlocker(baseCtx sdk.Context, keeper Keeper) (refundProposals, notRefundProposals []SimpleProposal) { + events := sdk.EmptyEvents() + refundProposals = make([]SimpleProposal, 0) + notRefundProposals = make([]SimpleProposal, 0) + chainIDs := []string{NativeChainID} + contexts := []sdk.Context{baseCtx} + if sdk.IsUpgrade(sdk.LaunchBscUpgrade) && keeper.ScKeeper != nil { + tmpSideIDs, storePrefixes := keeper.ScKeeper.GetAllSideChainPrefixes(baseCtx) + chainIDs = append(chainIDs, tmpSideIDs...) + for i := range storePrefixes { + contexts = append(contexts, baseCtx.WithSideChainKeyPrefix(storePrefixes[i])) + } + } + for i := 0; i < len(chainIDs); i++ { + resEvents, refund, noRefund := settleProposals(contexts[i], keeper, chainIDs[i]) + events = events.AppendEvents(resEvents) + refundProposals = append(refundProposals, refund...) + notRefundProposals = append(notRefundProposals, noRefund...) + } + baseCtx.EventManager().EmitEvents(events) + return +} + +func settleProposals(ctx sdk.Context, keeper Keeper, chainId string) (resEvents sdk.Events, refundProposals, notRefundProposals []SimpleProposal) { logger := ctx.Logger().With("module", "x/gov") - resTags = sdk.NewTags() - refundProposals = make([]int64, 0) - notRefundProposals = make([]int64, 0) + resEvents = sdk.EmptyEvents() + refundProposals = make([]SimpleProposal, 0) + notRefundProposals = make([]SimpleProposal, 0) // Delete proposals that haven't met minDeposit for ShouldPopInactiveProposalQueue(ctx, keeper) { @@ -125,18 +160,18 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags, refundProposa if inactiveProposal.GetStatus() != StatusDepositPeriod { continue } - - proposalIDBytes := keeper.cdc.MustMarshalBinaryBare(inactiveProposal.GetProposalID()) - // distribute deposits to proposer keeper.DistributeDeposits(ctx, inactiveProposal.GetProposalID()) keeper.DeleteProposal(ctx, inactiveProposal) - notRefundProposals = append(notRefundProposals, inactiveProposal.GetProposalID()) - - resTags.AppendTag(tags.Action, tags.ActionProposalDropped) - resTags.AppendTag(tags.ProposalID, proposalIDBytes) + notRefundProposals = append(notRefundProposals, SimpleProposal{inactiveProposal.GetProposalID(), chainId}) + event := sdk.NewEvent(events.EventTypeProposalDropped, sdk.NewAttribute(events.ProposalID, + strconv.FormatInt(inactiveProposal.GetProposalID(), 10))) + if chainId != NativeChainID { + event.AppendAttributes(sdk.NewAttribute(events.SideChainID, chainId)) + } + resEvents = resEvents.AppendEvent(event) logger.Info( fmt.Sprintf("proposal %d (%s) didn't meet minimum deposit of %v (had only %v); distribute to validator", @@ -159,26 +194,25 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags, refundProposa } passes, refundDeposits, tallyResults := Tally(ctx, keeper, activeProposal) - proposalIDBytes := keeper.cdc.MustMarshalBinaryBare(activeProposal.GetProposalID()) - var action []byte + var action string if passes { activeProposal.SetStatus(StatusPassed) - action = tags.ActionProposalPassed + action = events.EventTypeProposalPassed // refund deposits keeper.RefundDeposits(ctx, activeProposal.GetProposalID()) - refundProposals = append(refundProposals, activeProposal.GetProposalID()) + refundProposals = append(refundProposals, SimpleProposal{activeProposal.GetProposalID(), chainId}) } else { activeProposal.SetStatus(StatusRejected) - action = tags.ActionProposalRejected + action = events.EventTypeProposalRejected // if votes reached quorum and not all votes are abstain, distribute deposits to validator, else refund deposits if refundDeposits { keeper.RefundDeposits(ctx, activeProposal.GetProposalID()) - refundProposals = append(refundProposals, activeProposal.GetProposalID()) + refundProposals = append(refundProposals, SimpleProposal{activeProposal.GetProposalID(), chainId}) } else { keeper.DistributeDeposits(ctx, activeProposal.GetProposalID()) - notRefundProposals = append(notRefundProposals, activeProposal.GetProposalID()) + notRefundProposals = append(notRefundProposals, SimpleProposal{activeProposal.GetProposalID(), chainId}) } } @@ -187,9 +221,12 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags, refundProposa logger.Info(fmt.Sprintf("proposal %d (%s) tallied; passed: %v", activeProposal.GetProposalID(), activeProposal.GetTitle(), passes)) - - resTags.AppendTag(tags.Action, action) - resTags.AppendTag(tags.ProposalID, proposalIDBytes) + event := sdk.NewEvent(action, sdk.NewAttribute(events.ProposalID, + strconv.FormatInt(activeProposal.GetProposalID(), 10))) + if chainId != NativeChainID { + event.AppendAttributes(sdk.NewAttribute(events.SideChainID, chainId)) + } + resEvents = resEvents.AppendEvent(event) } return diff --git a/x/gov/handler_sidechain.go b/x/gov/handler_sidechain.go new file mode 100644 index 000000000..ab6b46352 --- /dev/null +++ b/x/gov/handler_sidechain.go @@ -0,0 +1,46 @@ +package gov + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov/events" +) + +func handleMsgSideChainSubmitProposal(ctx sdk.Context, keeper Keeper, msg MsgSideChainSubmitProposal) sdk.Result { + ctx, err := keeper.ScKeeper.PrepareCtxForSideChain(ctx, msg.SideChainId) + if err != nil { + return ErrInvalidSideChainId(keeper.codespace, msg.SideChainId).Result() + } + + result := handleMsgSubmitProposal(ctx, keeper, + NewMsgSubmitProposal(msg.Title, msg.Description, msg.ProposalType, msg.Proposer, msg.InitialDeposit, + msg.VotingPeriod)) + if result.IsOK() { + result.Tags = result.Tags.AppendTag(events.SideChainID, []byte(msg.SideChainId)) + } + return result +} + +func handleMsgSideChainDeposit(ctx sdk.Context, keeper Keeper, msg MsgSideChainDeposit) sdk.Result { + ctx, err := keeper.ScKeeper.PrepareCtxForSideChain(ctx, msg.SideChainId) + if err != nil { + return ErrInvalidSideChainId(keeper.codespace, msg.SideChainId).Result() + } + + result := handleMsgDeposit(ctx, keeper, NewMsgDeposit(msg.Depositer, msg.ProposalID, msg.Amount)) + if result.IsOK() { + result.Tags = result.Tags.AppendTag(events.SideChainID, []byte(msg.SideChainId)) + } + return result +} + +func handleMsgSideChainVote(ctx sdk.Context, keeper Keeper, msg MsgSideChainVote) sdk.Result { + ctx, err := keeper.ScKeeper.PrepareCtxForSideChain(ctx, msg.SideChainId) + if err != nil { + return ErrInvalidSideChainId(keeper.codespace, msg.SideChainId).Result() + } + result := handleMsgVote(ctx, keeper, NewMsgVote(msg.Voter, msg.ProposalID, msg.Option)) + if result.IsOK() { + result.Tags = result.Tags.AppendTag(events.SideChainID, []byte(msg.SideChainId)) + } + return result +} diff --git a/x/gov/keeper.go b/x/gov/keeper.go index f7a9ecf8f..3ffc868ea 100644 --- a/x/gov/keeper.go +++ b/x/gov/keeper.go @@ -22,6 +22,7 @@ var ( ParamStoreKeyDepositParams = []byte("depositparams") ParamStoreKeyTallyParams = []byte("tallyparams") + // Will hold deposit of both BC chain and side chain. DepositedCoinsAccAddr = sdk.AccAddress(crypto.AddressHash([]byte("BinanceChainDepositedCoins"))) ) @@ -33,6 +34,11 @@ func ParamTypeTable() params.TypeTable { ) } +type SideChainKeeper interface { + PrepareCtxForSideChain(ctx sdk.Context, sideChainId string) (sdk.Context, error) + GetAllSideChainPrefixes(ctx sdk.Context) ([]string, [][]byte) +} + // Governance Keeper type Keeper struct { // The reference to the Param Keeper to get and set Global Params @@ -64,6 +70,9 @@ type Keeper struct { // shared memory for block level state pool *sdk.Pool + + // if you want to enable side chains, you need call `SetupForSideChain` + ScKeeper SideChainKeeper } // NewKeeper returns a governance keeper. It handles: @@ -86,6 +95,10 @@ func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramsKeeper params.Keeper, p } } +func (keeper *Keeper) SetupForSideChain(scKeeper SideChainKeeper) { + keeper.ScKeeper = scKeeper +} + // AddHooks add hooks for gov keeper func (keeper Keeper) AddHooks(proposalType ProposalKind, hooks GovHooks) Keeper { hs := keeper.hooks[proposalType] @@ -292,12 +305,12 @@ func (keeper Keeper) GetTallyParams(ctx sdk.Context) TallyParams { } // nolint: errcheck -func (keeper Keeper) setDepositParams(ctx sdk.Context, depositParams DepositParams) { +func (keeper Keeper) SetDepositParams(ctx sdk.Context, depositParams DepositParams) { keeper.paramSpace.Set(ctx, ParamStoreKeyDepositParams, &depositParams) } // nolint: errcheck -func (keeper Keeper) setTallyParams(ctx sdk.Context, tallyParams TallyParams) { +func (keeper Keeper) SetTallyParams(ctx sdk.Context, tallyParams TallyParams) { keeper.paramSpace.Set(ctx, ParamStoreKeyTallyParams, &tallyParams) } @@ -455,7 +468,7 @@ func (keeper Keeper) RefundDeposits(ctx sdk.Context, proposalID int64) { // DistributeDeposits distributes deposits to proposer func (keeper Keeper) DistributeDeposits(ctx sdk.Context, proposalID int64) { proposerValAddr := ctx.BlockHeader().ProposerAddress - proposerValidator := keeper.vs.ValidatorByConsAddr(ctx, proposerValAddr) + proposerValidator := keeper.vs.ValidatorByConsAddr(ctx.DepriveSideChainKeyPrefix(), proposerValAddr) proposerAccAddr := proposerValidator.GetFeeAddr() store := ctx.KVStore(keeper.storeKey) diff --git a/x/gov/msg_sidechain.go b/x/gov/msg_sidechain.go new file mode 100644 index 000000000..b3d32ee4f --- /dev/null +++ b/x/gov/msg_sidechain.go @@ -0,0 +1,232 @@ +package gov + +import ( + "fmt" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/sidechain/types" +) + +const ( + MsgTypeSideSubmitProposal = "side_submit_proposal" + MsgTypeSideDeposit = "side_deposit" + MsgTypeSideVote = "side_vote" +) + +var _, _, _ sdk.Msg = MsgSideChainSubmitProposal{}, MsgSideChainDeposit{}, MsgSideChainVote{} + +//----------------------------------------------------------- +// MsgSideChainSubmitProposal +type MsgSideChainSubmitProposal struct { + Title string `json:"title"` // Title of the proposal + Description string `json:"description"` // Description of the proposal + ProposalType ProposalKind `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} + Proposer sdk.AccAddress `json:"proposer"` // Address of the proposer + InitialDeposit sdk.Coins `json:"initial_deposit"` // Initial deposit paid by sender. Must be strictly positive. + VotingPeriod time.Duration `json:"voting_period"` // Length of the voting period (s) + SideChainId string `json:"side_chain_id"` +} + +func NewMsgSideChainSubmitProposal(title string, description string, proposalType ProposalKind, proposer sdk.AccAddress, initialDeposit sdk.Coins, votingPeriod time.Duration, sideChainId string) MsgSideChainSubmitProposal { + return MsgSideChainSubmitProposal{ + Title: title, + Description: description, + ProposalType: proposalType, + Proposer: proposer, + InitialDeposit: initialDeposit, + VotingPeriod: votingPeriod, + SideChainId: sideChainId, + } +} + +//nolint +func (msg MsgSideChainSubmitProposal) Route() string { return MsgRoute } +func (msg MsgSideChainSubmitProposal) Type() string { return MsgTypeSideSubmitProposal } + +// Implements Msg. +func (msg MsgSideChainSubmitProposal) ValidateBasic() sdk.Error { + if len(msg.SideChainId) == 0 || len(msg.SideChainId) > types.MaxSideChainIdLength { + return ErrInvalidSideChainId(DefaultCodespace, msg.SideChainId) + } + if len(msg.Title) == 0 { + return ErrInvalidTitle(DefaultCodespace, "No title present in proposal") + } + if len(msg.Title) > MaxTitleLength { + return ErrInvalidTitle(DefaultCodespace, fmt.Sprintf("Proposal title is longer than max length of %d", MaxTitleLength)) + } + if len(msg.Description) == 0 { + return ErrInvalidDescription(DefaultCodespace, "No description present in proposal") + } + if len(msg.Description) > MaxDescriptionLength { + return ErrInvalidDescription(DefaultCodespace, fmt.Sprintf("Proposal description is longer than max length of %d", MaxDescriptionLength)) + } + if !validSideProposalType(msg.ProposalType) { + return ErrInvalidProposalType(DefaultCodespace, msg.ProposalType) + } + if len(msg.Proposer) != sdk.AddrLen { + return sdk.ErrInvalidAddress(fmt.Sprintf("length of address(%s) should be %d", string(msg.Proposer), sdk.AddrLen)) + } + if !msg.InitialDeposit.IsValid() { + return sdk.ErrInvalidCoins(msg.InitialDeposit.String()) + } + if !msg.InitialDeposit.IsNotNegative() { + return sdk.ErrInvalidCoins(msg.InitialDeposit.String()) + } + if msg.VotingPeriod <= 0 || msg.VotingPeriod > MaxVotingPeriod { + return ErrInvalidVotingPeriod(DefaultCodespace, msg.VotingPeriod) + } + return nil +} + +func (msg MsgSideChainSubmitProposal) String() string { + return fmt.Sprintf("MsgSideChainSubmitProposal{%s, %s, %s, %v, %s, %s}", msg.Title, msg.Description, msg.ProposalType, msg.InitialDeposit, msg.VotingPeriod, msg.SideChainId) +} + +// Implements Msg. +func (msg MsgSideChainSubmitProposal) GetSignBytes() []byte { + b, err := msgCdc.MarshalJSON(msg) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(b) +} + +// Implements Msg. Identical to MsgSubmitProposal, keep here for code readability. +func (msg MsgSideChainSubmitProposal) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Proposer} +} + +// Implements Msg. Identical to MsgSubmitProposal, keep here for code readability. +func (msg MsgSideChainSubmitProposal) GetInvolvedAddresses() []sdk.AccAddress { + // Better include DepositedCoinsAccAddr, before further discussion, follow the old rule. + return msg.GetSigners() +} + +//----------------------------------------------------------- +// MsgSideChainDeposit +type MsgSideChainDeposit struct { + ProposalID int64 `json:"proposal_id"` // ID of the proposal + Depositer sdk.AccAddress `json:"depositer"` // Address of the depositer + Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit + SideChainId string `json:"side_chain_id"` +} + +func NewMsgSideChainDeposit(depositer sdk.AccAddress, proposalID int64, amount sdk.Coins, sideChainId string) MsgSideChainDeposit { + return MsgSideChainDeposit{ + ProposalID: proposalID, + Depositer: depositer, + Amount: amount, + SideChainId: sideChainId, + } +} + +// nolint +func (msg MsgSideChainDeposit) Route() string { return MsgRoute } +func (msg MsgSideChainDeposit) Type() string { return MsgTypeSideDeposit } + +// Implements Msg. +func (msg MsgSideChainDeposit) ValidateBasic() sdk.Error { + if len(msg.SideChainId) == 0 || len(msg.SideChainId) > types.MaxSideChainIdLength { + return ErrInvalidSideChainId(DefaultCodespace, msg.SideChainId) + } + if len(msg.Depositer) != sdk.AddrLen { + return sdk.ErrInvalidAddress(fmt.Sprintf("length of address(%s) should be %d", string(msg.Depositer), sdk.AddrLen)) + } + if !msg.Amount.IsValid() { + return sdk.ErrInvalidCoins(msg.Amount.String()) + } + if !msg.Amount.IsNotNegative() { + return sdk.ErrInvalidCoins(msg.Amount.String()) + } + if msg.ProposalID < 0 { + return ErrUnknownProposal(DefaultCodespace, msg.ProposalID) + } + return nil +} + +func (msg MsgSideChainDeposit) String() string { + return fmt.Sprintf("MsgSideChainDeposit{%s=>%v: %v, %s}", msg.Depositer, msg.ProposalID, msg.Amount, msg.SideChainId) +} + +// Implements Msg. +func (msg MsgSideChainDeposit) GetSignBytes() []byte { + b, err := msgCdc.MarshalJSON(msg) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(b) +} + +// Implements Msg. Identical to MsgDeposit, keep here for code readability. +func (msg MsgSideChainDeposit) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Depositer} +} + +// Implements Msg. Identical to MsgDeposit, keep here for code readability. +func (msg MsgSideChainDeposit) GetInvolvedAddresses() []sdk.AccAddress { + // Better include DepositedCoinsAccAddr, before further discussion, follow the old rule. + return msg.GetSigners() +} + +//----------------------------------------------------------- +// MsgSideChainVote + +type MsgSideChainVote struct { + ProposalID int64 `json:"proposal_id"` // ID of the proposal + Voter sdk.AccAddress `json:"voter"` // address of the voter + Option VoteOption `json:"option"` // option from OptionSet chosen by the voter + SideChainId string `json:"side_chain_id"` +} + +func NewMsgSideChainVote(voter sdk.AccAddress, proposalID int64, option VoteOption, sideChainId string) MsgSideChainVote { + return MsgSideChainVote{ + ProposalID: proposalID, + Voter: voter, + Option: option, + SideChainId: sideChainId, + } +} + +func (msg MsgSideChainVote) Route() string { return MsgRoute } +func (msg MsgSideChainVote) Type() string { return MsgTypeSideVote } + +// Implements Msg. +func (msg MsgSideChainVote) ValidateBasic() sdk.Error { + if len(msg.SideChainId) == 0 || len(msg.SideChainId) > types.MaxSideChainIdLength { + return ErrInvalidSideChainId(DefaultCodespace, msg.SideChainId) + } + if len(msg.Voter) != sdk.AddrLen { + return sdk.ErrInvalidAddress(fmt.Sprintf("length of address(%s) should be %d", string(msg.Voter), sdk.AddrLen)) + } + if msg.ProposalID < 0 { + return ErrUnknownProposal(DefaultCodespace, msg.ProposalID) + } + if !validVoteOption(msg.Option) { + return ErrInvalidVote(DefaultCodespace, msg.Option) + } + return nil +} + +func (msg MsgSideChainVote) String() string { + return fmt.Sprintf("MsgSideChainVote{%v - %s, %s}", msg.ProposalID, msg.Option, msg.SideChainId) +} + +// Implements Msg. +func (msg MsgSideChainVote) GetSignBytes() []byte { + b, err := msgCdc.MarshalJSON(msg) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(b) +} + +// Implements Msg. Identical to MsgVote, keep here for code readability. +func (msg MsgSideChainVote) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Voter} +} + +// Implements Msg. Identical to MsgVote, keep here for code readability. +func (msg MsgSideChainVote) GetInvolvedAddresses() []sdk.AccAddress { + return msg.GetSigners() +} diff --git a/x/gov/msgs_test.go b/x/gov/msgs_test.go index d0d58166e..25a8459d1 100644 --- a/x/gov/msgs_test.go +++ b/x/gov/msgs_test.go @@ -114,3 +114,77 @@ func TestMsgVote(t *testing.T) { } } } + +func TestMsgSideChainSubmitProposal(t *testing.T) { + _, addrs, _, _ := mock.CreateGenAccounts(1, sdk.Coins{}) + tests := []struct { + title, description string + proposalType gov.ProposalKind + proposerAddr sdk.AccAddress + initialDeposit sdk.Coins + votingPeriod time.Duration + sideChainId string + expectPass bool + }{ + {"Test Proposal", "the purpose of this proposal is to test", gov.ProposalTypeSCParamsChange, addrs[0], coinsPos, 1000 * time.Second, "bsc", true}, + {"Test Proposal", "the purpose of this proposal is to test", gov.ProposalTypeCSCParamsChange, addrs[0], coinsPos, 1000 * time.Second, "rialto", true}, + {"Test Proposal", "the purpose of this proposal is to test", gov.ProposalTypeSCParamsChange, addrs[0], coinsPos, 1000 * time.Second, "", false}, + {"Test Proposal", "the purpose of this proposal is to test", gov.ProposalTypeParameterChange, addrs[0], coinsPos, 1000 * time.Second, "", false}, + } + + for i, tc := range tests { + msg := gov.NewMsgSideChainSubmitProposal(tc.title, tc.description, tc.proposalType, tc.proposerAddr, tc.initialDeposit, tc.votingPeriod, tc.sideChainId) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", i) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", i) + } + } +} + +func TestMsgSideChainDeposit(t *testing.T) { + _, addrs, _, _ := mock.CreateGenAccounts(1, sdk.Coins{}) + tests := []struct { + proposalID int64 + depositerAddr sdk.AccAddress + depositAmount sdk.Coins + sideChain string + expectPass bool + }{ + {0, addrs[0], coinsPos, "bsc", true}, + {0, addrs[0], coinsPos, "", false}, + } + + for i, tc := range tests { + msg := gov.NewMsgSideChainDeposit(tc.depositerAddr, tc.proposalID, tc.depositAmount, tc.sideChain) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", i) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", i) + } + } +} + +// test ValidateBasic for MsgDeposit +func TestMsgSideChainVote(t *testing.T) { + _, addrs, _, _ := mock.CreateGenAccounts(1, sdk.Coins{}) + tests := []struct { + proposalID int64 + voterAddr sdk.AccAddress + option gov.VoteOption + sideChain string + expectPass bool + }{ + {0, addrs[0], gov.OptionYes, "bsc", true}, + {0, addrs[0], gov.OptionYes, "", false}, + } + + for i, tc := range tests { + msg := gov.NewMsgSideChainVote(tc.voterAddr, tc.proposalID, tc.option, tc.sideChain) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", i) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", i) + } + } +} diff --git a/x/gov/proposals.go b/x/gov/proposals.go index a28f0383f..32130cc2d 100644 --- a/x/gov/proposals.go +++ b/x/gov/proposals.go @@ -126,10 +126,11 @@ const ( ProposalTypeSoftwareUpgrade ProposalKind = 0x03 ProposalTypeListTradingPair ProposalKind = 0x04 // ProposalTypeFeeChange belongs to ProposalTypeParameterChange. We use this to make it easily to distinguish。 - ProposalTypeFeeChange ProposalKind = 0x05 - ProposalTypeCreateValidator ProposalKind = 0x06 - ProposalTypeRemoveValidator ProposalKind = 0x07 - ProposalTypeDelistTradingPair ProposalKind = 0x08 + ProposalTypeFeeChange ProposalKind = 0x05 + ProposalTypeCreateValidator ProposalKind = 0x06 + ProposalTypeRemoveValidator ProposalKind = 0x07 + ProposalTypeDelistTradingPair ProposalKind = 0x08 + ProposalTypeManageChanPermission ProposalKind = 0x09 ) // String to proposalType byte. Returns ff if invalid. @@ -151,6 +152,12 @@ func ProposalTypeFromString(str string) (ProposalKind, error) { return ProposalTypeRemoveValidator, nil case "DelistTradingPair": return ProposalTypeDelistTradingPair, nil + case "SCParamsChange": + return ProposalTypeSCParamsChange, nil + case "CSCParamsChange": + return ProposalTypeCSCParamsChange, nil + case "ManageChanPermission": + return ProposalTypeManageChanPermission, nil default: return ProposalKind(0xff), errors.Errorf("'%s' is not a valid proposal type", str) } @@ -165,7 +172,8 @@ func validProposalType(pt ProposalKind) bool { pt == ProposalTypeFeeChange || pt == ProposalTypeCreateValidator || pt == ProposalTypeRemoveValidator || - pt == ProposalTypeDelistTradingPair { + pt == ProposalTypeDelistTradingPair || + pt == ProposalTypeManageChanPermission { return true } return false @@ -222,6 +230,12 @@ func (pt ProposalKind) String() string { return "RemoveValidator" case ProposalTypeDelistTradingPair: return "DelistTradingPair" + case ProposalTypeSCParamsChange: + return "SCParamsChange" + case ProposalTypeCSCParamsChange: + return "CSCParamsChange" + case ProposalTypeManageChanPermission: + return "ManageChanPermission" default: return "" } @@ -252,6 +266,7 @@ const ( StatusVotingPeriod ProposalStatus = 0x02 StatusPassed ProposalStatus = 0x03 StatusRejected ProposalStatus = 0x04 + StatusExecuted ProposalStatus = 0x05 ) // ProposalStatusToString turns a string into a ProposalStatus @@ -265,6 +280,8 @@ func ProposalStatusFromString(str string) (ProposalStatus, error) { return StatusPassed, nil case "Rejected": return StatusRejected, nil + case "Executed": + return StatusExecuted, nil case "": return StatusNil, nil default: @@ -277,7 +294,8 @@ func validProposalStatus(status ProposalStatus) bool { if status == StatusDepositPeriod || status == StatusVotingPeriod || status == StatusPassed || - status == StatusRejected { + status == StatusRejected || + status == StatusExecuted { return true } return false @@ -326,6 +344,8 @@ func (status ProposalStatus) String() string { return "Passed" case StatusRejected: return "Rejected" + case StatusExecuted: + return "Executed" default: return "" } diff --git a/x/gov/proposals_sidechain.go b/x/gov/proposals_sidechain.go new file mode 100644 index 000000000..a2c9a99a7 --- /dev/null +++ b/x/gov/proposals_sidechain.go @@ -0,0 +1,17 @@ +package gov + +//nolint +const ( + // side chain params change + ProposalTypeSCParamsChange ProposalKind = 0x81 + // cross side chain param change + ProposalTypeCSCParamsChange ProposalKind = 0x82 +) + +func validSideProposalType(pt ProposalKind) bool { + if pt == ProposalTypeSCParamsChange || + pt == ProposalTypeCSCParamsChange { + return true + } + return false +} diff --git a/x/gov/queryable.go b/x/gov/queryable.go index ffdae92c0..91087f7f4 100644 --- a/x/gov/queryable.go +++ b/x/gov/queryable.go @@ -5,6 +5,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" ) // query endpoints supported by the governance Querier @@ -22,19 +23,54 @@ func NewQuerier(keeper Keeper) sdk.Querier { return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) { switch path[0] { case QueryProposals: - return queryProposals(ctx, path[1:], req, keeper) + p := new(QueryProposalsParams) + ctx, err = RequestPrepare(ctx, keeper, req, p) + if err != nil { + return res, err + } + return queryProposals(ctx, path[1:], req, p, keeper) case QueryProposal: - return queryProposal(ctx, path[1:], req, keeper) + p := new(QueryProposalParams) + ctx, err = RequestPrepare(ctx, keeper, req, p) + if err != nil { + return res, err + } + return queryProposal(ctx, path[1:], req, p, keeper) case QueryDeposits: - return queryDeposits(ctx, path[1:], req, keeper) + p := new(QueryDepositsParams) + ctx, err = RequestPrepare(ctx, keeper, req, p) + if err != nil { + return res, err + } + return queryDeposits(ctx, path[1:], req, p, keeper) case QueryDeposit: - return queryDeposit(ctx, path[1:], req, keeper) + p := new(QueryDepositParams) + ctx, err = RequestPrepare(ctx, keeper, req, p) + if err != nil { + return res, err + } + return queryDeposit(ctx, path[1:], req, p, keeper) case QueryVotes: - return queryVotes(ctx, path[1:], req, keeper) + p := new(QueryVotesParams) + ctx, err = RequestPrepare(ctx, keeper, req, p) + if err != nil { + return res, err + } + return queryVotes(ctx, path[1:], req, p, keeper) case QueryVote: - return queryVote(ctx, path[1:], req, keeper) + p := new(QueryVoteParams) + ctx, err = RequestPrepare(ctx, keeper, req, p) + if err != nil { + return res, err + } + return queryVote(ctx, path[1:], req, p, keeper) case QueryTally: - return queryTally(ctx, path[1:], req, keeper) + p := new(QueryTallyParams) + ctx, err = RequestPrepare(ctx, keeper, req, p) + if err != nil { + return res, err + } + return queryTally(ctx, path[1:], req, p, keeper) default: return nil, sdk.ErrUnknownRequest("unknown gov query endpoint") } @@ -43,17 +79,12 @@ func NewQuerier(keeper Keeper) sdk.Querier { // Params for query 'custom/gov/proposal' type QueryProposalParams struct { + BaseParams ProposalID int64 } // nolint: unparam -func queryProposal(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { - var params QueryProposalParams - err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) - if err2 != nil { - return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) - } - +func queryProposal(ctx sdk.Context, path []string, req abci.RequestQuery, params *QueryProposalParams, keeper Keeper) (res []byte, err sdk.Error) { proposal := keeper.GetProposal(ctx, params.ProposalID) if proposal == nil { return nil, ErrUnknownProposal(DefaultCodespace, params.ProposalID) @@ -68,17 +99,13 @@ func queryProposal(ctx sdk.Context, path []string, req abci.RequestQuery, keeper // Params for query 'custom/gov/deposit' type QueryDepositParams struct { + BaseParams ProposalID int64 Depositer sdk.AccAddress } // nolint: unparam -func queryDeposit(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { - var params QueryDepositParams - err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) - if err2 != nil { - return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) - } +func queryDeposit(ctx sdk.Context, path []string, req abci.RequestQuery, params *QueryDepositParams, keeper Keeper) (res []byte, err sdk.Error) { deposit, _ := keeper.GetDeposit(ctx, params.ProposalID, params.Depositer) bz, err2 := codec.MarshalJSONIndent(keeper.cdc, deposit) @@ -90,18 +117,13 @@ func queryDeposit(ctx sdk.Context, path []string, req abci.RequestQuery, keeper // Params for query 'custom/gov/vote' type QueryVoteParams struct { + BaseParams ProposalID int64 Voter sdk.AccAddress } // nolint: unparam -func queryVote(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { - var params QueryVoteParams - err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) - if err2 != nil { - return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) - } - +func queryVote(ctx sdk.Context, path []string, req abci.RequestQuery, params *QueryVoteParams, keeper Keeper) (res []byte, err sdk.Error) { vote, _ := keeper.GetVote(ctx, params.ProposalID, params.Voter) bz, err2 := codec.MarshalJSONIndent(keeper.cdc, vote) if err2 != nil { @@ -112,17 +134,12 @@ func queryVote(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Kee // Params for query 'custom/gov/deposits' type QueryDepositsParams struct { + BaseParams ProposalID int64 } // nolint: unparam -func queryDeposits(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { - var params QueryDepositsParams - err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) - if err2 != nil { - return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) - } - +func queryDeposits(ctx sdk.Context, path []string, req abci.RequestQuery, params *QueryDepositsParams, keeper Keeper) (res []byte, err sdk.Error) { var deposits []Deposit depositsIterator := keeper.GetDeposits(ctx, params.ProposalID) defer depositsIterator.Close() @@ -141,18 +158,12 @@ func queryDeposits(ctx sdk.Context, path []string, req abci.RequestQuery, keeper // Params for query 'custom/gov/votes' type QueryVotesParams struct { + BaseParams ProposalID int64 } // nolint: unparam -func queryVotes(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { - var params QueryVotesParams - err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) - - if err2 != nil { - return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) - } - +func queryVotes(ctx sdk.Context, path []string, req abci.RequestQuery, params *QueryVotesParams, keeper Keeper) (res []byte, err sdk.Error) { var votes []Vote votesIterator := keeper.GetVotes(ctx, params.ProposalID) defer votesIterator.Close() @@ -171,6 +182,7 @@ func queryVotes(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Ke // Params for query 'custom/gov/proposals' type QueryProposalsParams struct { + BaseParams Voter sdk.AccAddress Depositer sdk.AccAddress ProposalStatus ProposalStatus @@ -178,12 +190,7 @@ type QueryProposalsParams struct { } // nolint: unparam -func queryProposals(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { - var params QueryProposalsParams - err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) - if err2 != nil { - return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) - } +func queryProposals(ctx sdk.Context, path []string, req abci.RequestQuery, params *QueryProposalsParams, keeper Keeper) (res []byte, err sdk.Error) { proposals := keeper.GetProposalsFiltered(ctx, params.Voter, params.Depositer, params.ProposalStatus, params.NumLatestProposals) @@ -196,22 +203,16 @@ func queryProposals(ctx sdk.Context, path []string, req abci.RequestQuery, keepe // Params for query 'custom/gov/tally' type QueryTallyParams struct { + BaseParams ProposalID int64 } // nolint: unparam -func queryTally(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { - // TODO: Dependant on #1914 - - var proposalID int64 - err2 := keeper.cdc.UnmarshalJSON(req.Data, proposalID) - if err2 != nil { - return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) - } +func queryTally(ctx sdk.Context, path []string, req abci.RequestQuery, params *QueryTallyParams, keeper Keeper) (res []byte, err sdk.Error) { - proposal := keeper.GetProposal(ctx, proposalID) + proposal := keeper.GetProposal(ctx, params.ProposalID) if proposal == nil { - return nil, ErrUnknownProposal(DefaultCodespace, proposalID) + return nil, ErrUnknownProposal(DefaultCodespace, params.ProposalID) } var tallyResult TallyResult @@ -230,3 +231,47 @@ func queryTally(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Ke } return bz, nil } + +func RequestPrepare(ctx sdk.Context, k Keeper, req abci.RequestQuery, p SideChainIder) (newCtx sdk.Context, err sdk.Error) { + if req.Data == nil || len(req.Data) == 0 { + return ctx, nil + } + errRes := k.cdc.UnmarshalJSON(req.Data, p) + if errRes != nil { + return ctx, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("can not unmarshal request", errRes.Error())) + } + newCtx = ctx + if len(p.GetSideChainId()) != 0 { + newCtx, err = prepareSideChainCtx(newCtx, k, p.GetSideChainId()) + if err != nil { + return newCtx, err + } + } + return newCtx, nil +} + +func prepareSideChainCtx(ctx sdk.Context, k Keeper, sideChainId string) (sdk.Context, sdk.Error) { + scCtx, err := k.ScKeeper.PrepareCtxForSideChain(ctx, sideChainId) + if err != nil { + return sdk.Context{}, types.ErrInvalidSideChainId(k.codespace) + } + return scCtx, nil +} + +type BaseParams struct { + SideChainId string +} + +func (p BaseParams) GetSideChainId() string { + return p.SideChainId +} + +type SideChainIder interface { + GetSideChainId() string +} + +func NewBaseParams(sideChainId string) BaseParams { + return BaseParams{ + SideChainId: sideChainId, + } +} diff --git a/x/gov/simulation/sim_test.go b/x/gov/simulation/sim_test.go index b85cfed85..410b5f58a 100644 --- a/x/gov/simulation/sim_test.go +++ b/x/gov/simulation/sim_test.go @@ -34,8 +34,8 @@ func TestGovWithRandomMessages(t *testing.T) { paramKeeper := params.NewKeeper(mapp.Cdc, paramKey, paramTKey) keyIbc := sdk.NewKVStoreKey("ibc") keySideChain := sdk.NewKVStoreKey("sc") - ibcKeeper := ibc.NewKeeper(keyIbc, ibc.DefaultCodespace) - scKeeper := sidechain.NewKeeper(keySideChain, paramKeeper.Subspace(sidechain.DefaultParamspace)) + scKeeper := sidechain.NewKeeper(keySideChain, paramKeeper.Subspace(sidechain.DefaultParamspace), mapp.Cdc) + ibcKeeper := ibc.NewKeeper(keyIbc, paramKeeper.Subspace(ibc.DefaultParamspace), ibc.DefaultCodespace, scKeeper) stakeKeeper := stake.NewKeeper(mapp.Cdc, stakeKey, stakeTKey, bankKeeper, nil, paramKeeper.Subspace(stake.DefaultParamspace), stake.DefaultCodespace) stakeKeeper.SetupForSideChain(&scKeeper, &ibcKeeper) govKey := sdk.NewKVStoreKey("gov") diff --git a/x/gov/tags/tags.go b/x/gov/tags/tags.go index 2eded1901..ef4d8884a 100644 --- a/x/gov/tags/tags.go +++ b/x/gov/tags/tags.go @@ -1,4 +1,3 @@ -// nolint package tags import ( @@ -6,12 +5,9 @@ import ( ) var ( - ActionSubmitProposal = []byte("submit-proposal") - ActionDeposit = []byte("deposit") - ActionVote = []byte("vote") - ActionProposalDropped = []byte("proposal-dropped") - ActionProposalPassed = []byte("proposal-passed") - ActionProposalRejected = []byte("proposal-rejected") + ActionSubmitProposal = []byte("submit-proposal") + ActionDeposit = []byte("deposit") + ActionVote = []byte("vote") Action = sdk.TagAction Proposer = "proposer" diff --git a/x/gov/types.go b/x/gov/types.go new file mode 100644 index 000000000..893eb1bb3 --- /dev/null +++ b/x/gov/types.go @@ -0,0 +1,6 @@ +package gov + +const ( + // Place holder for query and cli + NativeChainID = "" +) diff --git a/x/ibc/config.go b/x/ibc/config.go deleted file mode 100644 index e5de2771e..000000000 --- a/x/ibc/config.go +++ /dev/null @@ -1,26 +0,0 @@ -package ibc - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -type crossChainConfig struct { - srcIbcChainID sdk.IbcChainID - - nameToChannelID map[string]sdk.IbcChannelID - channelIDToName map[sdk.IbcChannelID]string - - destChainNameToID map[string]sdk.IbcChainID - destChainIDToName map[sdk.IbcChainID]string -} - -func newCrossChainCfg() *crossChainConfig { - config := &crossChainConfig{ - srcIbcChainID: 0, - nameToChannelID: make(map[string]sdk.IbcChannelID), - channelIDToName: make(map[sdk.IbcChannelID]string), - destChainNameToID: make(map[string]sdk.IbcChainID), - destChainIDToName: make(map[sdk.IbcChainID]string), - } - return config -} diff --git a/x/ibc/config_test.go b/x/ibc/config_test.go deleted file mode 100644 index 9233424f9..000000000 --- a/x/ibc/config_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package ibc - -import ( - "testing" - - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func TestInitCrossChainID(t *testing.T) { - sourceChainID := sdk.IbcChainID(0x0001) - _, keeper := createTestInput(t, true) - keeper.SetSrcIbcChainID(sourceChainID) - - require.Equal(t, sourceChainID, keeper.GetSrcIbcChainID()) -} - -func TestRegisterCrossChainChannel(t *testing.T) { - _, keeper := createTestInput(t, true) - require.NoError(t, keeper.RegisterChannel("bind", sdk.IbcChannelID(1))) - require.NoError(t, keeper.RegisterChannel("transfer", sdk.IbcChannelID(2))) - require.NoError(t, keeper.RegisterChannel("timeout", sdk.IbcChannelID(3))) - require.NoError(t, keeper.RegisterChannel("staking", sdk.IbcChannelID(4))) - require.Error(t, keeper.RegisterChannel("staking", sdk.IbcChannelID(5))) - require.Error(t, keeper.RegisterChannel("staking-new", sdk.IbcChannelID(4))) - - channeID, err := keeper.GetChannelID("transfer") - require.NoError(t, err) - require.Equal(t, sdk.IbcChannelID(2), channeID) - - channeID, err = keeper.GetChannelID("staking") - require.NoError(t, err) - require.Equal(t, sdk.IbcChannelID(4), channeID) -} - -func TestRegisterDestChainID(t *testing.T) { - _, keeper := createTestInput(t, true) - require.NoError(t, keeper.RegisterDestChain("bsc", sdk.IbcChainID(1))) - require.NoError(t, keeper.RegisterDestChain("ethereum", sdk.IbcChainID(2))) - require.NoError(t, keeper.RegisterDestChain("btc", sdk.IbcChainID(3))) - require.NoError(t, keeper.RegisterDestChain("cosmos", sdk.IbcChainID(4))) - require.Error(t, keeper.RegisterDestChain("cosmos", sdk.IbcChainID(5))) - require.Error(t, keeper.RegisterDestChain("mock", sdk.IbcChainID(4))) - require.Error(t, keeper.RegisterDestChain("cosmos::", sdk.IbcChainID(5))) - - destChainID, err := keeper.GetDestIbcChainID("bsc") - require.NoError(t, err) - require.Equal(t, sdk.IbcChainID(1), destChainID) - - destChainID, err = keeper.GetDestIbcChainID("btc") - require.NoError(t, err) - require.Equal(t, sdk.IbcChainID(3), destChainID) -} - -func TestCrossChainID(t *testing.T) { - chainID, err := sdk.ParseIbcChainID("123") - require.NoError(t, err) - require.Equal(t, sdk.IbcChainID(123), chainID) - - _, err = sdk.ParseIbcChainID("65537") - require.Error(t, err) -} diff --git a/x/ibc/endblock.go b/x/ibc/endblock.go index c0f1927a6..3bae6ae65 100644 --- a/x/ibc/endblock.go +++ b/x/ibc/endblock.go @@ -12,7 +12,7 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) { for _, ibcPackageRecord := range keeper.packageCollector.collectedPackages { attributes = append(attributes, sdk.NewAttribute(ibcPackageInfoAttributeKey, - buildIBCPackageAttributeValue(ibcPackageRecord.destChainName, ibcPackageRecord.destChainID, ibcPackageRecord.channelID, ibcPackageRecord.sequence))) + buildIBCPackageAttributeValue(ibcPackageRecord.destChainID, ibcPackageRecord.channelID, ibcPackageRecord.sequence))) } keeper.packageCollector.collectedPackages = keeper.packageCollector.collectedPackages[:0] event := sdk.NewEvent(ibcEventType, attributes...) diff --git a/x/ibc/errors.go b/x/ibc/errors.go index 2b9c42bcc..40331fa59 100644 --- a/x/ibc/errors.go +++ b/x/ibc/errors.go @@ -8,9 +8,24 @@ import ( const ( DefaultCodespace sdk.CodespaceType = 3 - CodeDuplicatedSequence sdk.CodeType = 101 + CodeDuplicatedSequence sdk.CodeType = 101 + CodeFeeParamMismatch sdk.CodeType = 102 + CodeInvalidChainId sdk.CodeType = 103 + CodeWritePackageForbidden sdk.CodeType = 104 ) func ErrDuplicatedSequence(codespace sdk.CodespaceType, msg string) sdk.Error { return sdk.NewError(codespace, CodeDuplicatedSequence, msg) } + +func ErrFeeParamMismatch(codespace sdk.CodespaceType, msg string) sdk.Error { + return sdk.NewError(codespace, CodeFeeParamMismatch, msg) +} + +func ErrInvalidChainId(codespace sdk.CodespaceType, msg string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidChainId, msg) +} + +func ErrWritePackageForbidden(codespace sdk.CodespaceType, msg string) sdk.Error { + return sdk.NewError(codespace, CodeWritePackageForbidden, msg) +} diff --git a/x/ibc/events.go b/x/ibc/events.go index 0c1c004e7..30601f14f 100644 --- a/x/ibc/events.go +++ b/x/ibc/events.go @@ -10,9 +10,9 @@ const ( separator = "::" ibcEventType = "IBCPackage" ibcPackageInfoAttributeKey = "IBCPackageInfo" - ibcPackageInfoAttributeValue = "%s" + separator + "%d" + separator + "%d" + separator + "%d" //destChainName destChainID channelID sequence + ibcPackageInfoAttributeValue = "%d" + separator + "%d" + separator + "%d" // destChainID channelID sequence ) -func buildIBCPackageAttributeValue(sideChainName string, sideChainID sdk.IbcChainID, channelID sdk.IbcChannelID, sequence uint64) string { - return fmt.Sprintf(ibcPackageInfoAttributeValue, sideChainName, sideChainID, channelID, sequence) +func buildIBCPackageAttributeValue(sideChainID sdk.ChainID, channelID sdk.ChannelID, sequence uint64) string { + return fmt.Sprintf(ibcPackageInfoAttributeValue, sideChainID, channelID, sequence) } diff --git a/x/ibc/keeper.go b/x/ibc/keeper.go index ae20fafa4..70491f099 100644 --- a/x/ibc/keeper.go +++ b/x/ibc/keeper.go @@ -3,9 +3,14 @@ package ibc import ( "encoding/binary" "fmt" - "strings" + "math/big" + "github.com/cosmos/cosmos-sdk/bsc" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/paramHub/types" + param "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/sidechain" + sTypes "github.com/cosmos/cosmos-sdk/x/sidechain/types" ) // IBC Keeper @@ -13,44 +18,88 @@ type Keeper struct { storeKey sdk.StoreKey codespace sdk.CodespaceType - cfg *crossChainConfig + paramSpace param.Subspace packageCollector *packageCollector + sideKeeper sidechain.Keeper } -func NewKeeper(storeKey sdk.StoreKey, codespace sdk.CodespaceType) Keeper { +func ParamTypeTable() param.TypeTable { + return param.NewTypeTable().RegisterParamSet(&Params{}) +} + +func NewKeeper(storeKey sdk.StoreKey, paramSpace param.Subspace, codespace sdk.CodespaceType, sideKeeper sidechain.Keeper) Keeper { return Keeper{ storeKey: storeKey, codespace: codespace, - cfg: newCrossChainCfg(), packageCollector: newPackageCollector(), + paramSpace: paramSpace.WithTypeTable(ParamTypeTable()), + sideKeeper: sideKeeper, } } -func (k *Keeper) CreateIBCPackage(ctx sdk.Context, destChainName string, channelName string, value []byte) (uint64, sdk.Error) { - destIbcChainID, err := k.GetDestIbcChainID(destChainName) +func (k *Keeper) CreateIBCSyncPackage(ctx sdk.Context, destChainName string, channelName string, packageLoad []byte) (uint64, sdk.Error) { + relayerFee, err := k.GetRelayerFeeParam(ctx, destChainName) + if err != nil { + return 0, ErrFeeParamMismatch(DefaultCodespace, fmt.Sprintf("fail to load relayerFee, %v", err)) + } + return k.CreateRawIBCPackage(ctx, destChainName, channelName, sdk.SynCrossChainPackageType, packageLoad, *relayerFee) +} + +func (k *Keeper) CreateRawIBCPackage(ctx sdk.Context, destChainName string, channelName string, + packageType sdk.CrossChainPackageType, packageLoad []byte, relayerFee big.Int) (uint64, sdk.Error) { + + destChainID, err := k.sideKeeper.GetDestChainID(destChainName) if err != nil { return 0, sdk.ErrInternal(err.Error()) } - channelID, err := k.GetChannelID(channelName) + channelID, err := k.sideKeeper.GetChannelID(channelName) if err != nil { return 0, sdk.ErrInternal(err.Error()) } - sequence := k.getSequence(ctx, destIbcChainID, channelID) - key := buildIBCPackageKey(k.GetSrcIbcChainID(), destIbcChainID, channelID, sequence) + return k.CreateRawIBCPackageByIdWithFee(ctx, destChainID, channelID, packageType, packageLoad, relayerFee) +} + +func (k *Keeper) CreateRawIBCPackageById(ctx sdk.Context, destChainID sdk.ChainID, channelID sdk.ChannelID, + packageType sdk.CrossChainPackageType, packageLoad []byte) (uint64, sdk.Error) { + + destChainName, err := k.sideKeeper.GetDestChainName(destChainID) + if err != nil { + return 0, ErrInvalidChainId(DefaultCodespace, "can not find dest chain id") + } + relayerFee, err := k.GetRelayerFeeParam(ctx, destChainName) + if err != nil { + return 0, ErrFeeParamMismatch(DefaultCodespace, fmt.Sprintf("fail to load relayerFee, %v", err)) + } + + return k.CreateRawIBCPackageByIdWithFee(ctx, destChainID, channelID, packageType, packageLoad, *relayerFee) +} + +func (k *Keeper) CreateRawIBCPackageByIdWithFee(ctx sdk.Context, destChainID sdk.ChainID, channelID sdk.ChannelID, + packageType sdk.CrossChainPackageType, packageLoad []byte, relayerFee big.Int) (uint64, sdk.Error) { + + if packageType == sdk.SynCrossChainPackageType && k.sideKeeper.GetChannelSendPermission(ctx, destChainID, channelID) != sdk.ChannelAllow { + return 0, ErrWritePackageForbidden(DefaultCodespace, fmt.Sprintf("channel %d is not allowed to write syn package", channelID)) + } + + sequence := k.sideKeeper.GetSendSequence(ctx, destChainID, channelID) + key := buildIBCPackageKey(k.sideKeeper.GetSrcChainID(), destChainID, channelID, sequence) kvStore := ctx.KVStore(k.storeKey) if kvStore.Has(key) { return 0, ErrDuplicatedSequence(DefaultCodespace, "duplicated sequence") } - kvStore.Set(key, value) - k.incrSequence(ctx, destIbcChainID, channelID) + + // Assemble the package header + packageHeader := sTypes.EncodePackageHeader(packageType, relayerFee) + + kvStore.Set(key, append(packageHeader, packageLoad...)) + k.sideKeeper.IncrSendSequence(ctx, destChainID, channelID) if ctx.IsDeliverTx() { k.packageCollector.collectedPackages = append(k.packageCollector.collectedPackages, packageRecord{ - destChainName: destChainName, - destChainID: destIbcChainID, - channelID: channelID, - sequence: sequence, + destChainID: destChainID, + channelID: channelID, + sequence: sequence, }) } @@ -58,30 +107,33 @@ func (k *Keeper) CreateIBCPackage(ctx sdk.Context, destChainName string, channel } func (k *Keeper) GetIBCPackage(ctx sdk.Context, destChainName string, channelName string, sequence uint64) ([]byte, error) { - destChainID, err := k.GetDestIbcChainID(destChainName) + destChainID, err := k.sideKeeper.GetDestChainID(destChainName) if err != nil { return nil, err } - channelID, err := k.GetChannelID(channelName) + channelID, err := k.sideKeeper.GetChannelID(channelName) if err != nil { return nil, err } + return k.GetIBCPackageById(ctx, destChainID, channelID, sequence) +} +func (k *Keeper) GetIBCPackageById(ctx sdk.Context, destChainID sdk.ChainID, channelId sdk.ChannelID, sequence uint64) ([]byte, error) { kvStore := ctx.KVStore(k.storeKey) - key := buildIBCPackageKey(k.GetSrcIbcChainID(), destChainID, channelID, sequence) + key := buildIBCPackageKey(k.sideKeeper.GetSrcChainID(), destChainID, channelId, sequence) return kvStore.Get(key), nil } func (k *Keeper) CleanupIBCPackage(ctx sdk.Context, destChainName string, channelName string, confirmedSequence uint64) { - destChainID, err := k.GetDestIbcChainID(destChainName) + destChainID, err := k.sideKeeper.GetDestChainID(destChainName) if err != nil { return } - channelID, err := k.GetChannelID(channelName) + channelID, err := k.sideKeeper.GetChannelID(channelName) if err != nil { return } - prefixKey := buildIBCPackageKeyPrefix(k.GetSrcIbcChainID(), destChainID, channelID) + prefixKey := buildIBCPackageKeyPrefix(k.sideKeeper.GetSrcChainID(), destChainID, channelID) kvStore := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(kvStore, prefixKey) defer iterator.Close() @@ -99,82 +151,42 @@ func (k *Keeper) CleanupIBCPackage(ctx sdk.Context, destChainName string, channe } } -func (k *Keeper) RegisterChannel(name string, id sdk.IbcChannelID) error { - _, ok := k.cfg.nameToChannelID[name] - if ok { - return fmt.Errorf("duplicated channel name") - } - _, ok = k.cfg.channelIDToName[id] - if ok { - return fmt.Errorf("duplicated channel id") - } - k.cfg.nameToChannelID[name] = id - k.cfg.channelIDToName[id] = name - return nil -} - -// internally, we use name as the id of the chain, must be unique -func (k *Keeper) RegisterDestChain(name string, ibcChainID sdk.IbcChainID) error { - if strings.Contains(name, separator) { - return fmt.Errorf("destination chain name should not contains %s", separator) - } - _, ok := k.cfg.destChainNameToID[name] - if ok { - return fmt.Errorf("duplicated destination chain name") - } - _, ok = k.cfg.destChainIDToName[ibcChainID] - if ok { - return fmt.Errorf("duplicated destination chain ibcChainID") - } - k.cfg.destChainNameToID[name] = ibcChainID - k.cfg.destChainIDToName[ibcChainID] = name - return nil -} - -func (k *Keeper) GetChannelID(channelName string) (sdk.IbcChannelID, error) { - id, ok := k.cfg.nameToChannelID[channelName] - if !ok { - return sdk.IbcChannelID(0), fmt.Errorf("non-existing channel") - } - return id, nil -} - -func (k *Keeper) SetSrcIbcChainID(srcIbcChainID sdk.IbcChainID) { - k.cfg.srcIbcChainID = srcIbcChainID -} - -func (k *Keeper) GetSrcIbcChainID() sdk.IbcChainID { - return k.cfg.srcIbcChainID -} - -func (k *Keeper) GetDestIbcChainID(name string) (sdk.IbcChainID, error) { - destChainID, exist := k.cfg.destChainNameToID[name] - if !exist { - return sdk.IbcChainID(0), fmt.Errorf("non-existing destination ibcChainID") +func (k Keeper) GetRelayerFeeParam(ctx sdk.Context, destChainName string) (relaterFee *big.Int, err error) { + storePrefix := k.sideKeeper.GetSideChainStorePrefix(ctx, destChainName) + if storePrefix == nil { + return nil, fmt.Errorf("invalid sideChainId: %s", destChainName) } - return destChainID, nil + sideChainCtx := ctx.WithSideChainKeyPrefix(storePrefix) + var relayerFeeParam int64 + k.paramSpace.Get(sideChainCtx, ParamRelayerFee, &relayerFeeParam) + relaterFee = bsc.ConvertBCAmountToBSCAmount(relayerFeeParam) + return } -func (k *Keeper) getSequence(ctx sdk.Context, destChainID sdk.IbcChainID, channelID sdk.IbcChannelID) uint64 { - kvStore := ctx.KVStore(k.storeKey) - bz := kvStore.Get(buildChannelSequenceKey(destChainID, channelID)) - if bz == nil { - return 0 - } - return binary.BigEndian.Uint64(bz) +func (k Keeper) SetParams(ctx sdk.Context, params Params) { + k.paramSpace.SetParamSet(ctx, ¶ms) } -func (k *Keeper) incrSequence(ctx sdk.Context, destChainID sdk.IbcChainID, channelID sdk.IbcChannelID) { - var sequence uint64 - kvStore := ctx.KVStore(k.storeKey) - bz := kvStore.Get(buildChannelSequenceKey(destChainID, channelID)) - if bz == nil { - sequence = 0 - } else { - sequence = binary.BigEndian.Uint64(bz) - } - - sequenceBytes := make([]byte, sequenceLength) - binary.BigEndian.PutUint64(sequenceBytes, sequence+1) - kvStore.Set(buildChannelSequenceKey(destChainID, channelID), sequenceBytes) +func (k *Keeper) SubscribeParamChange(hub types.ParamChangePublisher) { + hub.SubscribeParamChange( + func(context sdk.Context, iChange interface{}) { + switch change := iChange.(type) { + case *Params: + err := change.UpdateCheck() + if err != nil { + context.Logger().Error("skip invalid param change", "err", err, "param", change) + } else { + k.SetParams(context, *change) + break + } + default: + context.Logger().Debug("skip unknown param change") + } + }, + &types.ParamSpaceProto{ParamSpace: k.paramSpace, Proto: func() types.SCParam { + return new(Params) + }}, + nil, + nil, + ) } diff --git a/x/ibc/keeper_test.go b/x/ibc/keeper_test.go index 4446e1ce5..7dc857e7e 100644 --- a/x/ibc/keeper_test.go +++ b/x/ibc/keeper_test.go @@ -1,21 +1,28 @@ package ibc import ( + "math/big" "testing" - "github.com/cosmos/cosmos-sdk/store" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/sidechain" ) func createTestInput(t *testing.T, isCheckTx bool) (sdk.Context, Keeper) { keyIBC := sdk.NewKVStoreKey("ibc") + keySideChain := sdk.NewKVStoreKey("sc") db := dbm.NewMemDB() ms := store.NewCommitMultiStore(db) ms.MountStoreWithDB(keyIBC, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keySideChain, sdk.StoreTypeIAVL, db) err := ms.LoadLatestVersion() require.Nil(t, err) @@ -23,50 +30,59 @@ func createTestInput(t *testing.T, isCheckTx bool) (sdk.Context, Keeper) { if isCheckTx { mode = sdk.RunTxModeCheck } + keyParams := sdk.NewKVStoreKey("params") + tkeyParams := sdk.NewTransientStoreKey("transient_params") + + cdc := createTestCodec() + pk := params.NewKeeper(cdc, keyParams, tkeyParams) ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, mode, log.NewNopLogger()) - ibcKeeper := NewKeeper(keyIBC, DefaultCodespace) + scKeeper := sidechain.NewKeeper(keySideChain, pk.Subspace(sidechain.DefaultParamspace), cdc) + ibcKeeper := NewKeeper(keyIBC, pk.Subspace(DefaultParamspace), DefaultCodespace, scKeeper) return ctx, ibcKeeper } func TestKeeper(t *testing.T) { - sourceChainID := sdk.IbcChainID(0x0001) + sourceChainID := sdk.ChainID(0x0001) destChainName := "bsc" - destChainID := sdk.IbcChainID(0x000f) + destChainID := sdk.ChainID(0x000f) channelName := "transfer" - channelID := sdk.IbcChannelID(0x01) + channelID := sdk.ChannelID(0x01) ctx, keeper := createTestInput(t, true) - keeper.SetSrcIbcChainID(sourceChainID) - require.NoError(t, keeper.RegisterDestChain(destChainName, destChainID)) - require.NoError(t, keeper.RegisterChannel(channelName, channelID)) + + keeper.sideKeeper.SetChannelSendPermission(ctx, destChainID, channelID, sdk.ChannelAllow) + + keeper.sideKeeper.SetSrcChainID(sourceChainID) + require.NoError(t, keeper.sideKeeper.RegisterDestChain(destChainName, destChainID)) + require.NoError(t, keeper.sideKeeper.RegisterChannel(channelName, channelID, nil)) + testSynFee := big.NewInt(100) value := []byte{0x00} - sequence, err := keeper.CreateIBCPackage(ctx, destChainName, channelName, value) + sequence, err := keeper.CreateRawIBCPackage(ctx, destChainName, channelName, sdk.SynCrossChainPackageType, value, *testSynFee) require.NoError(t, err) require.Equal(t, uint64(0), sequence) value = []byte{0x00, 0x01} - sequence, err = keeper.CreateIBCPackage(ctx, destChainName, channelName, value) + sequence, err = keeper.CreateRawIBCPackage(ctx, destChainName, channelName, sdk.SynCrossChainPackageType, value, *testSynFee) require.NoError(t, err) require.Equal(t, uint64(1), sequence) value = []byte{0x00, 0x01, 0x02} - sequence, err = keeper.CreateIBCPackage(ctx, destChainName, channelName, value) + sequence, err = keeper.CreateRawIBCPackage(ctx, destChainName, channelName, sdk.SynCrossChainPackageType, value, *testSynFee) require.NoError(t, err) require.Equal(t, uint64(2), sequence) value = []byte{0x00, 0x01, 0x02, 0x03} - sequence, err = keeper.CreateIBCPackage(ctx, destChainName, channelName, value) + sequence, err = keeper.CreateRawIBCPackage(ctx, destChainName, channelName, sdk.SynCrossChainPackageType, value, *testSynFee) require.NoError(t, err) require.Equal(t, uint64(3), sequence) value = []byte{0x00, 0x01, 0x02, 0x03, 0x04} - sequence, err = keeper.CreateIBCPackage(ctx, destChainName, channelName, value) + sequence, err = keeper.CreateRawIBCPackage(ctx, destChainName, channelName, sdk.SynCrossChainPackageType, value, *testSynFee) require.NoError(t, err) require.Equal(t, uint64(4), sequence) - keeper.CleanupIBCPackage(ctx, destChainName, channelName, 3) ibcPackage, sdkErr := keeper.GetIBCPackage(ctx, destChainName, channelName, 0) @@ -85,15 +101,25 @@ func TestKeeper(t *testing.T) { require.NoError(t, sdkErr) require.NotNil(t, ibcPackage) - require.NoError(t, keeper.RegisterDestChain("btc", sdk.IbcChainID(0x0002))) - sequence, err = keeper.CreateIBCPackage(ctx, "btc", channelName, value) + require.NoError(t, keeper.sideKeeper.RegisterDestChain("btc", sdk.ChainID(0x0002))) + keeper.sideKeeper.SetChannelSendPermission(ctx, sdk.ChainID(0x0002), channelID, sdk.ChannelAllow) + + sequence, err = keeper.CreateRawIBCPackage(ctx, "btc", channelName, sdk.SynCrossChainPackageType, value, *testSynFee) require.NoError(t, err) require.Equal(t, uint64(0), sequence) - require.NoError(t, keeper.RegisterChannel("mockChannel", sdk.IbcChannelID(2))) - sequence, err = keeper.CreateIBCPackage(ctx, destChainName, "mockChannel", value) + require.NoError(t, keeper.sideKeeper.RegisterChannel("mockChannel", sdk.ChannelID(2), nil)) + keeper.sideKeeper.SetChannelSendPermission(ctx, destChainID, sdk.ChannelID(2), sdk.ChannelAllow) + sequence, err = keeper.CreateRawIBCPackage(ctx, destChainName, "mockChannel", sdk.SynCrossChainPackageType, value, *testSynFee) require.NoError(t, err) require.Equal(t, uint64(0), sequence) require.Equal(t, uint64(0), sequence) } + +func createTestCodec() *codec.Codec { + cdc := codec.New() + sdk.RegisterCodec(cdc) + codec.RegisterCrypto(cdc) + return cdc +} diff --git a/x/ibc/key.go b/x/ibc/key.go index 61317a785..af640de8e 100644 --- a/x/ibc/key.go +++ b/x/ibc/key.go @@ -8,11 +8,11 @@ import ( const ( prefixLength = 1 - srcIbcChainIdLength = 2 - destIbcChainIDLength = 2 + srcChainIdLength = 2 + destChainIDLength = 2 channelIDLength = 1 sequenceLength = 8 - totalPackageKeyLength = prefixLength + srcIbcChainIdLength + destIbcChainIDLength + channelIDLength + sequenceLength + totalPackageKeyLength = prefixLength + srcChainIdLength + destChainIDLength + channelIDLength + sequenceLength ) var ( @@ -20,35 +20,25 @@ var ( PrefixForSequenceKey = []byte{0x01} ) -func buildIBCPackageKey(srcIbcChainID, destIbcChainID sdk.IbcChainID, channelID sdk.IbcChannelID, sequence uint64) []byte { +func buildIBCPackageKey(srcChainID, destChainID sdk.ChainID, channelID sdk.ChannelID, sequence uint64) []byte { key := make([]byte, totalPackageKeyLength) copy(key[:prefixLength], PrefixForIbcPackageKey) - binary.BigEndian.PutUint16(key[prefixLength:srcIbcChainIdLength+prefixLength], uint16(srcIbcChainID)) - binary.BigEndian.PutUint16(key[prefixLength+srcIbcChainIdLength:prefixLength+srcIbcChainIdLength+destIbcChainIDLength], uint16(destIbcChainID)) - copy(key[prefixLength+srcIbcChainIdLength+destIbcChainIDLength:], []byte{byte(channelID)}) - binary.BigEndian.PutUint64(key[prefixLength+srcIbcChainIdLength+destIbcChainIDLength+channelIDLength:], sequence) + binary.BigEndian.PutUint16(key[prefixLength:srcChainIdLength+prefixLength], uint16(srcChainID)) + binary.BigEndian.PutUint16(key[prefixLength+srcChainIdLength:prefixLength+srcChainIdLength+destChainIDLength], uint16(destChainID)) + copy(key[prefixLength+srcChainIdLength+destChainIDLength:], []byte{byte(channelID)}) + binary.BigEndian.PutUint64(key[prefixLength+srcChainIdLength+destChainIDLength+channelIDLength:], sequence) return key } -func buildIBCPackageKeyPrefix(srcIbcChainID, destIbcChainID sdk.IbcChainID, channelID sdk.IbcChannelID) []byte { +func buildIBCPackageKeyPrefix(srcChainID, destChainID sdk.ChainID, channelID sdk.ChannelID) []byte { key := make([]byte, totalPackageKeyLength-sequenceLength) copy(key[:prefixLength], PrefixForIbcPackageKey) - binary.BigEndian.PutUint16(key[prefixLength:prefixLength+srcIbcChainIdLength], uint16(srcIbcChainID)) - binary.BigEndian.PutUint16(key[prefixLength+srcIbcChainIdLength:prefixLength+srcIbcChainIdLength+destIbcChainIDLength], uint16(destIbcChainID)) - copy(key[prefixLength+srcIbcChainIdLength+destIbcChainIDLength:], []byte{byte(channelID)}) + binary.BigEndian.PutUint16(key[prefixLength:prefixLength+srcChainIdLength], uint16(srcChainID)) + binary.BigEndian.PutUint16(key[prefixLength+srcChainIdLength:prefixLength+srcChainIdLength+destChainIDLength], uint16(destChainID)) + copy(key[prefixLength+srcChainIdLength+destChainIDLength:], []byte{byte(channelID)}) return key -} - -func buildChannelSequenceKey(destIbcChainID sdk.IbcChainID, channelID sdk.IbcChannelID) []byte { - key := make([]byte, prefixLength+destIbcChainIDLength+channelIDLength) - - copy(key[:prefixLength], PrefixForSequenceKey) - binary.BigEndian.PutUint16(key[prefixLength:prefixLength+destIbcChainIDLength], uint16(destIbcChainID)) - copy(key[prefixLength+destIbcChainIDLength:], []byte{byte(channelID)}) - - return key -} +} \ No newline at end of file diff --git a/x/ibc/params.go b/x/ibc/params.go new file mode 100644 index 000000000..a9a251ee1 --- /dev/null +++ b/x/ibc/params.go @@ -0,0 +1,37 @@ +package ibc + +import ( + "fmt" + "github.com/cosmos/cosmos-sdk/x/params" +) + +const ( + DefaultRelayerFeeParam int64 = 1e6 // decimal is 8 + // Default parameter namespace + DefaultParamspace = "ibc" +) + +var ( + ParamRelayerFee = []byte("relayerFee") +) + +type Params struct { + RelayerFee int64 `json:"relayer_fee"` +} + +func (p *Params) KeyValuePairs() params.KeyValuePairs { + return params.KeyValuePairs{ + {ParamRelayerFee, &p.RelayerFee}, + } +} + +func (p *Params) UpdateCheck() error { + if p.RelayerFee <= 0 { + return fmt.Errorf("the syn_package_fee should be greater than 0") + } + return nil +} + +func (p *Params) GetParamAttribute() (string, bool) { + return "ibc", false +} diff --git a/x/ibc/types.go b/x/ibc/types.go index 98a9e5ef5..de83e47de 100644 --- a/x/ibc/types.go +++ b/x/ibc/types.go @@ -5,10 +5,9 @@ import ( ) type packageRecord struct { - destChainName string - destChainID sdk.IbcChainID - channelID sdk.IbcChannelID - sequence uint64 + destChainID sdk.ChainID + channelID sdk.ChannelID + sequence uint64 } type packageCollector struct { diff --git a/x/ibc/wire.go b/x/ibc/wire.go new file mode 100644 index 000000000..e521632f5 --- /dev/null +++ b/x/ibc/wire.go @@ -0,0 +1,9 @@ +package ibc + +import ( + "github.com/cosmos/cosmos-sdk/codec" +) + +func RegisterWire(cdc *codec.Codec) { + cdc.RegisterConcrete(&Params{}, "params/IbcParamSet", nil) +} diff --git a/x/oracle/alias.go b/x/oracle/alias.go index 1d194ccb4..3f90a2e6b 100644 --- a/x/oracle/alias.go +++ b/x/oracle/alias.go @@ -26,7 +26,6 @@ var ( ErrInvalidClaim = types.ErrInvalidClaim ErrInvalidValidator = types.ErrInvalidValidator ErrInternalDB = types.ErrInternalDB - ErrInvalidClaimType = types.ErrInvalidClaimType NewProphecy = types.NewProphecy NewStatus = types.NewStatus diff --git a/x/oracle/handler.go b/x/oracle/handler.go index cc37d7aba..81d075d86 100644 --- a/x/oracle/handler.go +++ b/x/oracle/handler.go @@ -1,17 +1,22 @@ package oracle import ( + "encoding/hex" "fmt" + "runtime/debug" "strconv" + "github.com/cosmos/cosmos-sdk/bsc/rlp" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/fees" "github.com/cosmos/cosmos-sdk/x/oracle/types" + sTypes "github.com/cosmos/cosmos-sdk/x/sidechain/types" ) func NewHandler(keeper Keeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { switch msg := msg.(type) { - case ClaimMsg: + case types.ClaimMsg: return handleClaimMsg(ctx, keeper, msg) default: errMsg := "Unrecognized oracle msg type" @@ -21,30 +26,12 @@ func NewHandler(keeper Keeper) sdk.Handler { } func handleClaimMsg(ctx sdk.Context, oracleKeeper Keeper, msg ClaimMsg) sdk.Result { - claimTypeName := oracleKeeper.GetClaimTypeName(msg.ClaimType) - if claimTypeName == "" { - return types.ErrInvalidClaimType(fmt.Sprintf("claim type %d does not exist", msg.ClaimType)).Result() - } - - claimHooks := oracleKeeper.GetClaimHooks(msg.ClaimType) - if claimHooks == nil { - return types.ErrInvalidClaimType(fmt.Sprintf("hooks of claim type %s does not exist", claimTypeName)).Result() - } - - sdkErr := claimHooks.CheckClaim(ctx, msg.Claim) - if sdkErr != nil { - return sdkErr.Result() - } + claim := NewClaim(types.GetClaimId(msg.ChainId, types.RelayPackagesChannelId, msg.Sequence), + sdk.ValAddress(msg.ValidatorAddress), hex.EncodeToString(msg.Payload)) - currentSequence := oracleKeeper.GetCurrentSequence(ctx, msg.ClaimType) - if msg.Sequence != currentSequence { - return types.ErrInvalidSequence(fmt.Sprintf("current sequence of claim type %s is %d", claimTypeName, currentSequence)).Result() - } - - claim := types.Claim{ - ID: types.GetClaimId(msg.ClaimType, msg.Sequence), - ValidatorAddress: sdk.ValAddress(msg.ValidatorAddress), - Content: msg.Claim, + sequence := oracleKeeper.ScKeeper.GetReceiveSequence(ctx, msg.ChainId, types.RelayPackagesChannelId) + if sequence != msg.Sequence { + return types.ErrInvalidSequence(fmt.Sprintf("current sequence of channel %d is %d", types.RelayPackagesChannelId, sequence)).Result() } prophecy, sdkErr := oracleKeeper.ProcessClaim(ctx, claim) @@ -61,25 +48,172 @@ func handleClaimMsg(ctx sdk.Context, oracleKeeper Keeper, msg ClaimMsg) sdk.Resu return sdk.Result{} } - result, sdkErr := claimHooks.ExecuteClaim(ctx, prophecy.Status.FinalClaim) - if sdkErr != nil { - return sdkErr.Result() + packages := types.Packages{} + err := rlp.DecodeBytes(msg.Payload, &packages) + if err != nil { + return types.ErrInvalidPayload("decode packages error").Result() + } + + events := make([]sdk.Event, 0, len(packages)) + for _, pack := range packages { + event, sdkErr := handlePackage(ctx, oracleKeeper, msg.ChainId, &pack) + if sdkErr != nil { + // only do log, but let reset package get chance to execute. + ctx.Logger().With("module", "oracle").Error(fmt.Sprintf("process package failed, channel=%d, sequence=%d, error=%v", pack.ChannelId, pack.Sequence, sdkErr)) + return sdkErr.Result() + } else { + ctx.Logger().With("module", "oracle").Info(fmt.Sprintf("process package success, channel=%d, sequence=%d", pack.ChannelId, pack.Sequence)) + } + events = append(events, event) + + // increase channel sequence + oracleKeeper.ScKeeper.IncrReceiveSequence(ctx, msg.ChainId, pack.ChannelId) } // delete prophecy when execute claim success oracleKeeper.DeleteProphecy(ctx, prophecy.ID) + oracleKeeper.ScKeeper.IncrReceiveSequence(ctx, msg.ChainId, types.RelayPackagesChannelId) + + return sdk.Result{ + Events: events, + } +} + +func handlePackage(ctx sdk.Context, oracleKeeper Keeper, chainId sdk.ChainID, pack *types.Package) (sdk.Event, sdk.Error) { + logger := ctx.Logger().With("module", "x/oracle") + + crossChainApp := oracleKeeper.ScKeeper.GetCrossChainApp(ctx, pack.ChannelId) + if crossChainApp == nil { + return sdk.Event{}, types.ErrChannelNotRegistered(fmt.Sprintf("channel %d not registered", pack.ChannelId)) + } + + sequence := oracleKeeper.ScKeeper.GetReceiveSequence(ctx, chainId, pack.ChannelId) + if sequence != pack.Sequence { + return sdk.Event{}, types.ErrInvalidSequence(fmt.Sprintf("current sequence of channel %d is %d", pack.ChannelId, sequence)) + } + + packageType, relayFee, err := sTypes.DecodePackageHeader(pack.Payload) + if err != nil { + return sdk.Event{}, types.ErrInvalidPayloadHeader(err.Error()) + } + + if !sdk.IsValidCrossChainPackageType(packageType) { + return sdk.Event{}, types.ErrInvalidPackageType() + } + + feeAmount := relayFee.Int64() + if feeAmount < 0 { + return sdk.Event{}, types.ErrFeeOverflow("relayFee overflow") + } + + fee := sdk.Coins{sdk.Coin{Denom: sdk.NativeTokenSymbol, Amount: feeAmount}} + _, _, sdkErr := oracleKeeper.BkKeeper.SubtractCoins(ctx, sdk.PegAccount, fee) + if sdkErr != nil { + return sdk.Event{}, sdkErr + } + + if ctx.IsDeliverTx() { + // add changed accounts + oracleKeeper.Pool.AddAddrs([]sdk.AccAddress{sdk.PegAccount}) + + // add fee + fees.Pool.AddAndCommitFee( + fmt.Sprintf("cross_communication:%d:%d:%v", pack.ChannelId, pack.Sequence, packageType), + sdk.Fee{ + Tokens: fee, + Type: sdk.FeeForProposer, + }, + ) + } + + cacheCtx, write := ctx.CacheContext() + crash, result := executeClaim(cacheCtx, crossChainApp, pack.Payload, packageType) + if result.IsOk() { + write() + } else if ctx.IsDeliverTx() { + oracleKeeper.Metrics.ErrNumOfChannels.With("channel_id", fmt.Sprintf("%d", pack.ChannelId)).Add(1) + } + + // write ack package + var sendSequence int64 = -1 + if packageType == sdk.SynCrossChainPackageType { + if crash { + sendSeq, err := oracleKeeper.IbcKeeper.CreateRawIBCPackageById(ctx, chainId, + pack.ChannelId, sdk.FailAckCrossChainPackageType, pack.Payload) + if err != nil { + logger.Error("failed to write FailAckCrossChainPackage", "err", err) + return sdk.Event{}, err + } + sendSequence = int64(sendSeq) + } else { + if len(result.Payload) != 0 { + sendSeq, err := oracleKeeper.IbcKeeper.CreateRawIBCPackageById(ctx, chainId, + pack.ChannelId, sdk.AckCrossChainPackageType, result.Payload) + if err != nil { + logger.Error("failed to write AckCrossChainPackage", "err", err) + return sdk.Event{}, err + } + sendSequence = int64(sendSeq) + } + } + } resultTags := sdk.NewTags( - types.ClaimResultCode, []byte(strconv.FormatInt(int64(result.Code), 10)), - types.ClaimResultMsg, []byte(result.Msg), + types.ClaimResultCode, []byte(strconv.FormatInt(int64(result.Code()), 10)), + types.ClaimResultMsg, []byte(result.Msg()), + types.ClaimPackageType, []byte(strconv.FormatInt(int64(packageType), 10)), + // The following tags are for index + types.ClaimChannel, []byte{uint8(pack.ChannelId)}, + types.ClaimReceiveSequence, []byte(strconv.FormatUint(pack.Sequence, 10)), ) + if sendSequence >= 0 { + resultTags = append(resultTags, sdk.MakeTag(types.ClaimSendSequence, []byte(strconv.FormatInt(sendSequence, 10)))) + } + + if crash { + resultTags = append(resultTags, sdk.MakeTag(types.ClaimCrash, []byte{1})) + } + + // emit event if feeAmount is larger than 0 + if feeAmount > 0 { + resultTags = append(resultTags, sdk.GetPegOutTag(sdk.NativeTokenSymbol, feeAmount)) + } + if result.Tags != nil { resultTags = resultTags.AppendTags(result.Tags) } - // increase claim type sequence - oracleKeeper.IncreaseSequence(ctx, msg.ClaimType) + event := sdk.Event{ + Type: types.EventTypeClaim, + Attributes: resultTags, + } + + return event, nil +} - return sdk.Result{Tags: resultTags} +func executeClaim(ctx sdk.Context, app sdk.CrossChainApplication, payload []byte, packageType sdk.CrossChainPackageType) (crash bool, result sdk.ExecuteResult) { + defer func() { + if r := recover(); r != nil { + log := fmt.Sprintf("recovered: %v\nstack:\n%v", r, string(debug.Stack())) + logger := ctx.Logger().With("module", "oracle") + logger.Error("execute claim panic", "err_log", log) + crash = true + result = sdk.ExecuteResult{ + Err: sdk.ErrInternal(fmt.Sprintf("execute claim failed: %v", r)), + } + } + }() + + switch packageType { + case sdk.SynCrossChainPackageType: + result = app.ExecuteSynPackage(ctx, payload[sTypes.PackageHeaderLength:]) + case sdk.AckCrossChainPackageType: + result = app.ExecuteAckPackage(ctx, payload[sTypes.PackageHeaderLength:]) + case sdk.FailAckCrossChainPackageType: + result = app.ExecuteFailAckPackage(ctx, payload[sTypes.PackageHeaderLength:]) + default: + panic(fmt.Sprintf("receive unexpected package type %d", packageType)) + } + return } diff --git a/x/oracle/keeper/keeper.go b/x/oracle/keeper/keeper.go index 0e6af3b0a..677140467 100644 --- a/x/oracle/keeper/keeper.go +++ b/x/oracle/keeper/keeper.go @@ -1,13 +1,16 @@ package keeper import ( - "encoding/binary" - "fmt" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/ibc" + "github.com/cosmos/cosmos-sdk/x/oracle/metrics" "github.com/cosmos/cosmos-sdk/x/oracle/types" - "github.com/cosmos/cosmos-sdk/x/params" + pTypes "github.com/cosmos/cosmos-sdk/x/paramHub/types" + param "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/sidechain" ) // Keeper maintains the link to data storage and @@ -17,13 +20,16 @@ type Keeper struct { storeKey sdk.StoreKey // The reference to the Paramstore to get and set gov specific params - paramSpace params.Subspace + paramSpace param.Subspace + + Pool *sdk.Pool stakeKeeper types.StakingKeeper + ScKeeper sidechain.Keeper + IbcKeeper ibc.Keeper + BkKeeper bank.Keeper - claimTypeToName map[sdk.ClaimType]string - nameToClaimType map[string]sdk.ClaimType - claimHooksMap map[sdk.ClaimType]sdk.ClaimHooks + Metrics *metrics.Metrics } // Parameter store @@ -31,39 +37,38 @@ const ( DefaultParamSpace = "oracle" ) -var ( - ParamStoreKeyProphecyParams = []byte("prophecyParams") -) - -func ParamTypeTable() params.TypeTable { - return params.NewTypeTable( - ParamStoreKeyProphecyParams, types.ProphecyParams{}, - ) +func ParamTypeTable() param.TypeTable { + return param.NewTypeTable().RegisterParamSet(&types.Params{}) } // NewKeeper creates new instances of the oracle Keeper -func NewKeeper( - cdc *codec.Codec, storeKey sdk.StoreKey, paramSpace params.Subspace, stakeKeeper types.StakingKeeper, +func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, paramSpace param.Subspace, stakeKeeper types.StakingKeeper, + scKeeper sidechain.Keeper, ibcKeeper ibc.Keeper, bkKeeper bank.Keeper, pool *sdk.Pool, ) Keeper { return Keeper{ - cdc: cdc, - storeKey: storeKey, - paramSpace: paramSpace.WithTypeTable(ParamTypeTable()), - stakeKeeper: stakeKeeper, - claimTypeToName: make(map[sdk.ClaimType]string), - nameToClaimType: make(map[string]sdk.ClaimType), - claimHooksMap: make(map[sdk.ClaimType]sdk.ClaimHooks), + cdc: cdc, + storeKey: storeKey, + paramSpace: paramSpace.WithTypeTable(ParamTypeTable()), + stakeKeeper: stakeKeeper, + ScKeeper: scKeeper, + IbcKeeper: ibcKeeper, + BkKeeper: bkKeeper, + Metrics: metrics.NopMetrics(), + Pool: pool, } } -func (k Keeper) GetProphecyParams(ctx sdk.Context) types.ProphecyParams { - var depositParams types.ProphecyParams - k.paramSpace.Get(ctx, ParamStoreKeyProphecyParams, &depositParams) - return depositParams +func (k Keeper) GetConsensusNeeded(ctx sdk.Context) (consensusNeeded sdk.Dec) { + k.paramSpace.Get(ctx, types.ParamStoreKeyProphecyParams, &consensusNeeded) + return } -func (k Keeper) SetProphecyParams(ctx sdk.Context, params types.ProphecyParams) { - k.paramSpace.Set(ctx, ParamStoreKeyProphecyParams, ¶ms) +func (k Keeper) EnablePrometheusMetrics() { + k.Metrics = metrics.PrometheusMetrics() +} + +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { + k.paramSpace.SetParamSet(ctx, ¶ms) } // GetProphecy gets the entire prophecy data struct for a given id @@ -102,59 +107,6 @@ func (k Keeper) setProphecy(ctx sdk.Context, prophecy types.Prophecy) { store.Set([]byte(prophecy.ID), k.cdc.MustMarshalBinaryBare(serializedProphecy)) } -func (k Keeper) RegisterClaimType(claimType sdk.ClaimType, claimTypeName string, hooks sdk.ClaimHooks) error { - if claimTypeName == "" { - return fmt.Errorf("claim type name should not be empty") - } - - if _, ok := k.claimTypeToName[claimType]; ok { - return fmt.Errorf("claim type %d already exists", claimType) - } - if _, ok := k.nameToClaimType[claimTypeName]; ok { - return fmt.Errorf("claim type name %s already exists", claimTypeName) - } - if _, ok := k.claimHooksMap[claimType]; ok { - return fmt.Errorf("hooks of claim type %d already exists", claimType) - } - - k.claimTypeToName[claimType] = claimTypeName - k.nameToClaimType[claimTypeName] = claimType - k.claimHooksMap[claimType] = hooks - return nil -} - -func (k Keeper) GetClaimHooks(claimType sdk.ClaimType) sdk.ClaimHooks { - return k.claimHooksMap[claimType] -} - -func (k Keeper) GetClaimTypeName(claimType sdk.ClaimType) string { - return k.claimTypeToName[claimType] -} - -func (k Keeper) IncreaseSequence(ctx sdk.Context, claimType sdk.ClaimType) int64 { - currentSequence := k.GetCurrentSequence(ctx, claimType) - - kvStore := ctx.KVStore(k.storeKey) - nextSeq := currentSequence + 1 - - bz := make([]byte, 8) - binary.BigEndian.PutUint64(bz, uint64(nextSeq)) - - kvStore.Set(types.GetClaimTypeSequence(claimType), bz) - return nextSeq -} - -func (k Keeper) GetCurrentSequence(ctx sdk.Context, claimType sdk.ClaimType) int64 { - kvStore := ctx.KVStore(k.storeKey) - bz := kvStore.Get(types.GetClaimTypeSequence(claimType)) - if bz == nil { - return types.StartSequence - } - - sequence := binary.BigEndian.Uint64(bz) - return int64(sequence) -} - // ProcessClaim ... func (k Keeper) ProcessClaim(ctx sdk.Context, claim types.Claim) (types.Prophecy, sdk.Error) { activeValidator := k.checkActiveValidator(ctx, claim.ValidatorAddress) @@ -166,7 +118,7 @@ func (k Keeper) ProcessClaim(ctx sdk.Context, claim types.Claim) (types.Prophecy return types.Prophecy{}, types.ErrInvalidIdentifier() } - if claim.Content == "" { + if len(claim.Payload) == 0 { return types.Prophecy{}, types.ErrInvalidClaim() } @@ -182,7 +134,7 @@ func (k Keeper) ProcessClaim(ctx sdk.Context, claim types.Claim) (types.Prophecy return types.Prophecy{}, types.ErrProphecyFinalized() } - prophecy.AddClaim(claim.ValidatorAddress, claim.Content) + prophecy.AddClaim(claim.ValidatorAddress, claim.Payload) prophecy = k.processCompletion(ctx, prophecy) k.setProphecy(ctx, prophecy) @@ -213,13 +165,39 @@ func (k Keeper) processCompletion(ctx sdk.Context, prophecy types.Prophecy) type highestPossibleConsensusRatio := sdk.NewDec(highestPossibleClaimPower).Quo(sdk.NewDec(totalPower)) - prophecyParams := k.GetProphecyParams(ctx) + consensusNeeded := k.GetConsensusNeeded(ctx) - if highestConsensusRatio.GTE(prophecyParams.ConsensusNeeded) { + if highestConsensusRatio.GTE(consensusNeeded) { prophecy.Status.Text = types.SuccessStatusText prophecy.Status.FinalClaim = highestClaim - } else if highestPossibleConsensusRatio.LT(prophecyParams.ConsensusNeeded) { + } else if highestPossibleConsensusRatio.LT(consensusNeeded) { prophecy.Status.Text = types.FailedStatusText } return prophecy } + +func (k *Keeper) SubscribeParamChange(hub pTypes.ParamChangePublisher) { + hub.SubscribeParamChange( + func(context sdk.Context, iChange interface{}) { + switch change := iChange.(type) { + case *types.Params: + // do double check + err := change.UpdateCheck() + if err != nil { + context.Logger().Error("skip invalid param change", "err", err, "param", change) + } else { + newCtx := context.DepriveSideChainKeyPrefix() + k.SetParams(newCtx, *change) + break + } + default: + context.Logger().Debug("skip unknown param change") + } + }, + &pTypes.ParamSpaceProto{ParamSpace: k.paramSpace, Proto: func() pTypes.SCParam { + return new(types.Params) + }}, + nil, + nil, + ) +} diff --git a/x/oracle/keeper/keeper_test.go b/x/oracle/keeper/keeper_test.go index 9383e1ac5..278ec30d3 100644 --- a/x/oracle/keeper/keeper_test.go +++ b/x/oracle/keeper/keeper_test.go @@ -57,7 +57,7 @@ func TestCreateGetProphecy(t *testing.T) { createValidators(t, stakeHandler, ctx, valAddrs, []int64{5, 5, 5}) stake.EndBlocker(ctx, sk) - keeper.SetProphecyParams(ctx, types.ProphecyParams{ConsensusNeeded: sdk.NewDecWithPrec(6, 1)}) + keeper.SetParams(ctx, types.Params{ConsensusNeeded: sdk.NewDecWithPrec(6, 1)}) validator1 := valAddrs[0] oracleClaim := types.NewClaim(TestID, validator1, TestString) @@ -98,7 +98,7 @@ func TestBadMsgs(t *testing.T) { } createValidators(t, stakeHandler, ctx, valAddrs, []int64{5, 5, 5}) stake.EndBlocker(ctx, sk) - keeper.SetProphecyParams(ctx, types.ProphecyParams{ConsensusNeeded: sdk.NewDecWithPrec(6, 1)}) + keeper.SetParams(ctx, types.Params{ConsensusNeeded: sdk.NewDecWithPrec(6, 1)}) validator1Pow3 := valAddrs[0] @@ -129,7 +129,7 @@ func TestSuccessfulProphecy(t *testing.T) { } createValidators(t, stakeHandler, ctx, valAddrs, []int64{5, 5, 5}) stake.EndBlocker(ctx, sk) - keeper.SetProphecyParams(ctx, types.ProphecyParams{ConsensusNeeded: sdk.NewDecWithPrec(6, 1)}) + keeper.SetParams(ctx, types.Params{ConsensusNeeded: sdk.NewDecWithPrec(6, 1)}) validator1Pow3 := valAddrs[0] validator2Pow3 := valAddrs[1] @@ -168,7 +168,7 @@ func TestSuccessfulProphecyWithDisagreement(t *testing.T) { } createValidators(t, stakeHandler, ctx, valAddrs, []int64{5, 5, 5}) stake.EndBlocker(ctx, sk) - keeper.SetProphecyParams(ctx, types.ProphecyParams{ConsensusNeeded: sdk.NewDecWithPrec(6, 1)}) + keeper.SetParams(ctx, types.Params{ConsensusNeeded: sdk.NewDecWithPrec(6, 1)}) validator1Pow3 := valAddrs[0] validator2Pow3 := valAddrs[1] @@ -207,7 +207,7 @@ func TestFailedProphecy(t *testing.T) { } createValidators(t, stakeHandler, ctx, valAddrs, []int64{5, 5, 5}) stake.EndBlocker(ctx, sk) - keeper.SetProphecyParams(ctx, types.ProphecyParams{ConsensusNeeded: sdk.NewDecWithPrec(6, 1)}) + keeper.SetParams(ctx, types.Params{ConsensusNeeded: sdk.NewDecWithPrec(6, 1)}) validator1Pow3 := valAddrs[0] validator2Pow3 := valAddrs[1] @@ -247,7 +247,7 @@ func TestPowerOverrule(t *testing.T) { } createValidators(t, stakeHandler, ctx, valAddrs, []int64{5, 20, 5}) stake.EndBlocker(ctx, sk) - keeper.SetProphecyParams(ctx, types.ProphecyParams{ConsensusNeeded: sdk.NewDecWithPrec(6, 1)}) + keeper.SetParams(ctx, types.Params{ConsensusNeeded: sdk.NewDecWithPrec(6, 1)}) validator1Pow3 := valAddrs[0] validator2Pow7 := valAddrs[1] diff --git a/x/oracle/keeper/test_common.go b/x/oracle/keeper/test_common.go index dbc15fbf1..3c85e5aad 100644 --- a/x/oracle/keeper/test_common.go +++ b/x/oracle/keeper/test_common.go @@ -1,6 +1,8 @@ package keeper import ( + "github.com/cosmos/cosmos-sdk/x/ibc" + "github.com/cosmos/cosmos-sdk/x/sidechain" "testing" "github.com/stretchr/testify/require" @@ -26,11 +28,14 @@ func getMockApp(t *testing.T, numGenAccs int) (*mock.App, bank.BaseKeeper, Keepe keyStake := sdk.NewKVStoreKey("stake") tkeyStake := sdk.NewTransientStoreKey("transient_stake") keyOracle := sdk.NewKVStoreKey("oracle") - //keyIbc := sdk.NewKVStoreKey("ibc") + keyIbc := sdk.NewKVStoreKey("ibc") + keySideChain := sdk.NewKVStoreKey("side") pk := params.NewKeeper(mapp.Cdc, keyGlobalParams, tkeyGlobalParams) ck := bank.NewBaseKeeper(mapp.AccountKeeper) sk := stake.NewKeeper(mapp.Cdc, keyStake, tkeyStake, ck, nil, pk.Subspace(stake.DefaultParamspace), mapp.RegisterCodespace(stake.DefaultCodespace)) + scK := sidechain.NewKeeper(keySideChain, pk.Subspace(sidechain.DefaultParamspace), mapp.Cdc) + ibcKeeper := ibc.NewKeeper(keyIbc, pk.Subspace(ibc.DefaultParamspace), ibc.DefaultCodespace, scK) mapp.SetInitChainer(getInitChainer(mapp, sk)) @@ -38,8 +43,7 @@ func getMockApp(t *testing.T, numGenAccs int) (*mock.App, bank.BaseKeeper, Keepe genAccs, addrs, pubKeys, privKeys := mock.CreateGenAccounts(numGenAccs, sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 5000e8)}) mock.SetGenesis(mapp, genAccs) - - oracleKeeper := NewKeeper(mapp.Cdc, keyOracle, pk.Subspace("testoracle"), sk) + oracleKeeper := NewKeeper(mapp.Cdc, keyOracle, pk.Subspace("testoracle"), sk, scK, ibcKeeper, bank.NewBaseKeeper(mapp.AccountKeeper), &sdk.Pool{}) return mapp, ck, oracleKeeper, sk, addrs, pubKeys, privKeys } diff --git a/x/oracle/metrics/metrics.go b/x/oracle/metrics/metrics.go new file mode 100644 index 000000000..f0f649ab8 --- /dev/null +++ b/x/oracle/metrics/metrics.go @@ -0,0 +1,31 @@ +package metrics + +import ( + metricsPkg "github.com/go-kit/kit/metrics" + "github.com/go-kit/kit/metrics/discard" + "github.com/go-kit/kit/metrics/prometheus" + stdprometheus "github.com/prometheus/client_golang/prometheus" +) + +// Metrics contains Metrics exposed by this package. +type Metrics struct { + ErrNumOfChannels metricsPkg.Counter +} + +// PrometheusMetrics returns Metrics build using Prometheus client library. +func PrometheusMetrics() *Metrics { + return &Metrics{ + ErrNumOfChannels: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Subsystem: "oracle", + Name: "err_num_of_channels", + Help: "The error numbers of channel happened from boot", + }, []string{"channel_id"}), + } +} + +// NopMetrics returns no-op Metrics. +func NopMetrics() *Metrics { + return &Metrics{ + ErrNumOfChannels: discard.NewCounter(), + } +} diff --git a/x/oracle/plugin.go b/x/oracle/plugin.go index 1c7abe2ba..1350e4ac5 100644 --- a/x/oracle/plugin.go +++ b/x/oracle/plugin.go @@ -7,6 +7,11 @@ import ( func RegisterUpgradeBeginBlocker(keeper Keeper) { sdk.UpgradeMgr.RegisterBeginBlocker(sdk.LaunchBscUpgrade, func(ctx sdk.Context) { - keeper.SetProphecyParams(ctx, types.ProphecyParams{ConsensusNeeded: types.DefaultConsensusNeeded}) + keeper.SetParams(ctx, types.Params{ConsensusNeeded: types.DefaultConsensusNeeded}) }) + + err := keeper.ScKeeper.RegisterChannel(types.RelayPackagesChannelName, types.RelayPackagesChannelId, nil) + if err != nil { + panic("register relay packages channel error") + } } diff --git a/x/oracle/types/claim.go b/x/oracle/types/claim.go index c4c6d639b..253428770 100644 --- a/x/oracle/types/claim.go +++ b/x/oracle/types/claim.go @@ -6,22 +6,28 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -func GetClaimId(claimType sdk.ClaimType, sequence int64) string { - return fmt.Sprintf("%d:%d", claimType, sequence) +const ( + // RelayPackagesChannelId is not a communication channel actually, we just use it to record sequence. + RelayPackagesChannelName = "relayPackages" + RelayPackagesChannelId sdk.ChannelID = 0x00 +) + +func GetClaimId(chainId sdk.ChainID, channelId sdk.ChannelID, sequence uint64) string { + return fmt.Sprintf("%d:%d:%d", chainId, channelId, sequence) } // Claim contains an arbitrary claim with arbitrary content made by a given validator type Claim struct { ID string `json:"id"` ValidatorAddress sdk.ValAddress `json:"validator_address"` - Content string `json:"content"` + Payload string `json:"payload"` } // NewClaim returns a new Claim -func NewClaim(id string, validatorAddress sdk.ValAddress, content string) Claim { +func NewClaim(id string, validatorAddress sdk.ValAddress, payload string) Claim { return Claim{ ID: id, ValidatorAddress: validatorAddress, - Content: content, + Payload: payload, } } diff --git a/x/oracle/types/errors.go b/x/oracle/types/errors.go index 3c22842e9..6c532d9f8 100644 --- a/x/oracle/types/errors.go +++ b/x/oracle/types/errors.go @@ -20,7 +20,10 @@ const ( CodeInvalidValidator sdk.CodeType = 1007 CodeInternalDB sdk.CodeType = 1008 CodeInvalidSequence sdk.CodeType = 1009 - CodeInvalidClaimType sdk.CodeType = 1010 + CodeChannelNotRegistered sdk.CodeType = 1010 + CodeInvalidLengthOfPayload sdk.CodeType = 1011 + CodeFeeOverflow sdk.CodeType = 1012 + CodeInvalidPayload sdk.CodeType = 1013 ) func ErrProphecyNotFound() sdk.Error { @@ -49,8 +52,12 @@ func ErrDuplicateMessage() sdk.Error { func ErrInvalidClaim() sdk.Error { return sdk.NewError(DefaultCodespace, CodeInvalidClaim, fmt.Sprintf("claim cannot be empty string")) +} +func ErrInvalidPackageType() sdk.Error { + return sdk.NewError(DefaultCodespace, CodeInvalidClaim, fmt.Sprintf("package type is invalid")) } + func ErrInvalidValidator() sdk.Error { return sdk.NewError(DefaultCodespace, CodeInvalidValidator, fmt.Sprintf("claim must be made by actively bonded validator")) } @@ -63,6 +70,18 @@ func ErrInvalidSequence(msg string) sdk.Error { return sdk.NewError(DefaultCodespace, CodeInvalidSequence, msg) } -func ErrInvalidClaimType(msg string) sdk.Error { - return sdk.NewError(DefaultCodespace, CodeInvalidClaimType, msg) +func ErrChannelNotRegistered(msg string) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeChannelNotRegistered, msg) +} + +func ErrInvalidPayloadHeader(msg string) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeInvalidLengthOfPayload, msg) +} + +func ErrFeeOverflow(msg string) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeFeeOverflow, msg) +} + +func ErrInvalidPayload(msg string) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeInvalidPayload, msg) } diff --git a/x/oracle/types/keys.go b/x/oracle/types/keys.go index 31392ea2b..8e6702926 100644 --- a/x/oracle/types/keys.go +++ b/x/oracle/types/keys.go @@ -1,16 +1,13 @@ package types -import sdk "github.com/cosmos/cosmos-sdk/types" - -const StartSequence = 0 - -var claimTypeSequencePrefix = []byte("claimTypeSeq:") - -func GetClaimTypeSequence(claimType sdk.ClaimType) []byte { - return append(claimTypeSequencePrefix, byte(claimType)) -} - const ( - ClaimResultCode = "ClaimResultCode" - ClaimResultMsg = "ClaimResultMsg" + EventTypeClaim = "claim" + + ClaimResultCode = "ClaimResultCode" + ClaimResultMsg = "ClaimResultMsg" + ClaimChannel = "ClaimChannel" + ClaimReceiveSequence = "ClaimReceiveSequence" + ClaimSendSequence = "ClaimSendSequence" + ClaimCrash = "ClaimCrash" + ClaimPackageType = "ClaimPackageType" ) diff --git a/x/oracle/types/msgs.go b/x/oracle/types/msgs.go index 132ef9667..043f5b4be 100644 --- a/x/oracle/types/msgs.go +++ b/x/oracle/types/msgs.go @@ -5,6 +5,7 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/sidechain/types" ) const ( @@ -15,18 +16,26 @@ const ( var _ sdk.Msg = ClaimMsg{} +type Packages []Package + +type Package struct { + ChannelId sdk.ChannelID + Sequence uint64 + Payload []byte +} + type ClaimMsg struct { - ClaimType sdk.ClaimType `json:"claim_type"` - Sequence int64 `json:"sequence"` - Claim string `json:"claim"` + ChainId sdk.ChainID `json:"chain_id"` + Sequence uint64 `json:"sequence"` + Payload []byte `json:"payload"` ValidatorAddress sdk.AccAddress `json:"validator_address"` } -func NewClaimMsg(claimType sdk.ClaimType, sequence int64, claim string, validatorAddr sdk.AccAddress) ClaimMsg { +func NewClaimMsg(ChainId sdk.ChainID, sequence uint64, payload []byte, validatorAddr sdk.AccAddress) ClaimMsg { return ClaimMsg{ - ClaimType: claimType, + ChainId: ChainId, Sequence: sequence, - Claim: claim, + Payload: payload, ValidatorAddress: validatorAddr, } } @@ -39,8 +48,8 @@ func (msg ClaimMsg) GetSigners() []sdk.AccAddress { } func (msg ClaimMsg) String() string { - return fmt.Sprintf("Claim{%v#%v#%v#%v}", - msg.ClaimType, msg.Sequence, msg.Claim, msg.ValidatorAddress.String()) + return fmt.Sprintf("Claim{%v#%v#%v#%x}", + msg.ChainId, msg.Sequence, msg.ValidatorAddress.String(), msg.Payload) } // GetSignBytes - Get the bytes for the message signer to sign on @@ -58,14 +67,9 @@ func (msg ClaimMsg) GetInvolvedAddresses() []sdk.AccAddress { // ValidateBasic is used to quickly disqualify obviously invalid messages quickly func (msg ClaimMsg) ValidateBasic() sdk.Error { - if msg.Sequence < 0 { - return ErrInvalidSequence("sequence should not be less than 0") + if len(msg.Payload) < types.PackageHeaderLength { + return ErrInvalidPayloadHeader(fmt.Sprintf("length of payload is less than %d", types.PackageHeaderLength)) } - - if len(msg.Claim) == 0 { - return ErrInvalidClaim() - } - if len(msg.ValidatorAddress) != sdk.AddrLen { return sdk.ErrInvalidAddress(msg.ValidatorAddress.String()) } diff --git a/x/oracle/types/msgs_test.go b/x/oracle/types/msgs_test.go index a6ad1588a..ccd3281f2 100644 --- a/x/oracle/types/msgs_test.go +++ b/x/oracle/types/msgs_test.go @@ -4,9 +4,11 @@ import ( "testing" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/libs/common" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/mock" + "github.com/cosmos/cosmos-sdk/x/sidechain/types" ) func TestClaimMsg(t *testing.T) { @@ -17,16 +19,13 @@ func TestClaimMsg(t *testing.T) { expectedPass bool }{ { - NewClaimMsg(sdk.ClaimType(0x8), 1, "test", addrs[0]), + NewClaimMsg(1, 1, common.RandBytes(types.PackageHeaderLength), addrs[0]), true, }, { - NewClaimMsg(sdk.ClaimType(0x8), -1, "test", addrs[0]), + NewClaimMsg(1, 1, []byte("test"), addrs[0]), false, }, { - NewClaimMsg(sdk.ClaimType(0x8), 1, "", addrs[0]), - false, - }, { - NewClaimMsg(sdk.ClaimType(0x8), 1, "test", sdk.AccAddress{1}), + NewClaimMsg(1, 1, common.RandBytes(types.PackageHeaderLength), sdk.AccAddress{1}), false, }, } diff --git a/x/oracle/types/prophecy.go b/x/oracle/types/prophecy.go index f927579b0..f6f975ee6 100644 --- a/x/oracle/types/prophecy.go +++ b/x/oracle/types/prophecy.go @@ -5,16 +5,37 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/params" ) -// DefaultConsensusNeeded defines the default consensus value required for a -// prophecy to be finalized -var DefaultConsensusNeeded sdk.Dec = sdk.NewDecWithPrec(7, 1) +var ( + // DefaultConsensusNeeded defines the default consensus value required for a + // prophecy to be finalized + DefaultConsensusNeeded sdk.Dec = sdk.NewDecWithPrec(7, 1) + ParamStoreKeyProphecyParams = []byte("prophecyParams") +) -type ProphecyParams struct { +type Params struct { ConsensusNeeded sdk.Dec `json:"ConsensusNeeded"` // Minimum deposit for a proposal to enter voting period. } +func (p *Params) UpdateCheck() error { + if p.ConsensusNeeded.IsNil() || p.ConsensusNeeded.GT(sdk.OneDec()) || p.ConsensusNeeded.LT(sdk.NewDecWithPrec(5, 1)) { + return fmt.Errorf("the value should be in range 0.5 to 1") + } + return nil +} + +func (p *Params) GetParamAttribute() (string, bool) { + return "oracle", true +} + +func (p *Params) KeyValuePairs() params.KeyValuePairs { + return params.KeyValuePairs{ + {ParamStoreKeyProphecyParams, &p.ConsensusNeeded}, + } +} + // Prophecy is a struct that contains all the metadata of an oracle ritual. // Claims are indexed by the claim's validator bech32 address and by the claim's json value to allow // for constant lookup times for any validation/verifiation checks of duplicate claims diff --git a/x/oracle/wire.go b/x/oracle/wire.go index 0bdf2fbf9..0d82a268a 100644 --- a/x/oracle/wire.go +++ b/x/oracle/wire.go @@ -2,6 +2,7 @@ package oracle import ( "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/x/oracle/types" ) // Register concrete types on codec codec @@ -11,4 +12,5 @@ func RegisterWire(cdc *codec.Codec) { cdc.RegisterConcrete(Status{}, "oracle/Status", nil) cdc.RegisterConcrete(DBProphecy{}, "oracle/DBProphecy", nil) cdc.RegisterConcrete(ClaimMsg{}, "oracle/ClaimMsg", nil) + cdc.RegisterConcrete(&types.Params{}, "params/OracleParamSet", nil) } diff --git a/x/paramHub/aliases.go b/x/paramHub/aliases.go new file mode 100644 index 000000000..6cfe85ea9 --- /dev/null +++ b/x/paramHub/aliases.go @@ -0,0 +1,7 @@ +package paramHub + +import ( + "github.com/cosmos/cosmos-sdk/x/paramHub/keeper" +) + +type ParamHub = keeper.Keeper diff --git a/x/paramHub/client/cli/command.go b/x/paramHub/client/cli/command.go new file mode 100644 index 000000000..826255f39 --- /dev/null +++ b/x/paramHub/client/cli/command.go @@ -0,0 +1,40 @@ +package cli + +import ( + "github.com/spf13/cobra" + "github.com/tendermint/go-amino" + + "github.com/cosmos/cosmos-sdk/client" +) + +const ( + flagTitle = "title" + flagDescription = "description" + flagDeposit = "deposit" + flagVotingPeriod = "voting-period" + flagSideChainId = "side-chain-id" +) + +func AddCommands(cmd *cobra.Command, cdc *amino.Codec) { + + dexCmd := &cobra.Command{ + Use: "params", + Short: "params commands", + } + dexCmd.AddCommand( + client.PostCommands( + SubmitFeeChangeProposalCmd(cdc))...) + dexCmd.AddCommand( + client.PostCommands( + SubmitCSCParamChangeProposalCmd(cdc))...) + dexCmd.AddCommand( + client.PostCommands( + SubmitSCParamChangeProposalCmd(cdc))...) + dexCmd.AddCommand( + client.GetCommands( + ShowFeeParamsCmd(cdc))...) + dexCmd.AddCommand( + client.GetCommands( + ShowSideChainParamsCmd(cdc))...) + cmd.AddCommand(dexCmd) +} diff --git a/x/paramHub/client/cli/cscParams.go b/x/paramHub/client/cli/cscParams.go new file mode 100644 index 000000000..5d7869f3c --- /dev/null +++ b/x/paramHub/client/cli/cscParams.go @@ -0,0 +1,107 @@ +package cli + +import ( + "errors" + "fmt" + "strings" + "time" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/utils" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/paramHub/types" +) + +const ( + + //CSC flag + flagKey = "key" + flagValue = "value" + flagTaregt = "target" +) + +func SubmitCSCParamChangeProposalCmd(cdc *codec.Codec) *cobra.Command { + var cscParam types.CSCParamChange + cmd := &cobra.Command{ + Use: "submit-cscParam-change-proposal", + Short: "Submit a cross side chain parameter change proposal", + RunE: func(cmd *cobra.Command, args []string) error { + txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) + title := viper.GetString(flagTitle) + initialDeposit := viper.GetString(flagDeposit) + votingPeriodInSeconds := viper.GetInt64(flagVotingPeriod) + cscParam.Key = viper.GetString(flagKey) + value := viper.GetString(flagValue) + sideChainId := viper.GetString(flagSideChainId) + if sideChainId == "" { + return fmt.Errorf("missing side-chain-id") + } + if strings.HasPrefix(value, "0x") { + value = value[2:] + } + cscParam.Value = value + + target := viper.GetString(flagTaregt) + if strings.HasPrefix(target, "0x") { + target = target[2:] + } + cscParam.Target = target + + err := cscParam.Check() + if err != nil { + return err + } + fromAddr, err := cliCtx.GetFromAddress() + if err != nil { + return err + } + amount, err := sdk.ParseCoins(initialDeposit) + if err != nil { + return err + } + // cscParam get interface field, use amino + cscParamsBz, err := cdc.MarshalJSON(cscParam) + if err != nil { + return err + } + + if votingPeriodInSeconds <= 0 { + return errors.New("voting period should be positive") + } + + votingPeriod := time.Duration(votingPeriodInSeconds) * time.Second + if votingPeriod > gov.MaxVotingPeriod { + return fmt.Errorf("voting period should less than %d seconds", gov.MaxVotingPeriod/time.Second) + } + + msg := gov.NewMsgSideChainSubmitProposal(title, string(cscParamsBz), gov.ProposalTypeCSCParamsChange, fromAddr, amount, votingPeriod, sideChainId) + err = msg.ValidateBasic() + if err != nil { + return err + } + if cliCtx.GenerateOnly { + return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) + } + cliCtx.PrintResponse = true + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) + }, + } + cmd.Flags().String(flagKey, "", "the parameter name on the side chain") + cmd.Flags().String(flagValue, "", "the specified value of the parameter on side chain, should encoded in hex") + cmd.Flags().String(flagTaregt, "", "the address of the contract on side chain") + cmd.Flags().String(flagTitle, "", "title of proposal") + cmd.Flags().Int64(flagVotingPeriod, 7*24*60*60, "voting period in seconds") + cmd.Flags().String(flagDeposit, "", "deposit of proposal") + cmd.Flags().String(flagSideChainId, "", "the id of side chain") + return cmd +} diff --git a/x/paramHub/client/cli/fees.go b/x/paramHub/client/cli/fees.go new file mode 100644 index 000000000..0774a9221 --- /dev/null +++ b/x/paramHub/client/cli/fees.go @@ -0,0 +1,145 @@ +package cli + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "time" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/tendermint/go-amino" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/utils" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/paramHub" + "github.com/cosmos/cosmos-sdk/x/paramHub/types" +) + +const ( + //Fee flag + flagFeeParamFile = "fee-param-file" + flagFormat = "format" +) + +func SubmitFeeChangeProposalCmd(cdc *codec.Codec) *cobra.Command { + feeParam := types.FeeChangeParams{FeeParams: make([]types.FeeParam, 0)} + cmd := &cobra.Command{ + Use: "submit-fee-change-proposal", + Short: "Submit a fee or fee rate change proposal", + RunE: func(cmd *cobra.Command, args []string) error { + txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) + title := viper.GetString(flagTitle) + initialDeposit := viper.GetString(flagDeposit) + feeParamFile := viper.GetString(flagFeeParamFile) + feeParam.Description = viper.GetString(flagDescription) + votingPeriodInSeconds := viper.GetInt64(flagVotingPeriod) + if feeParamFile == "" { + return errors.New("fee-param-file is missing") + } + + bz, err := ioutil.ReadFile(feeParamFile) + if err != nil { + return err + } + err = cdc.UnmarshalJSON(bz, &(feeParam.FeeParams)) + if err != nil { + return err + } + err = feeParam.Check() + if err != nil { + return err + } + fromAddr, err := cliCtx.GetFromAddress() + if err != nil { + return err + } + amount, err := sdk.ParseCoins(initialDeposit) + if err != nil { + return err + } + // feeParam get interface field, use amino + feeParamsBz, err := cdc.MarshalJSON(feeParam) + if err != nil { + return err + } + + if votingPeriodInSeconds <= 0 { + return errors.New("voting period should be positive") + } + + votingPeriod := time.Duration(votingPeriodInSeconds) * time.Second + if votingPeriod > gov.MaxVotingPeriod { + return fmt.Errorf("voting period should less than %d seconds", gov.MaxVotingPeriod/time.Second) + } + + msg := gov.NewMsgSubmitProposal(title, string(feeParamsBz), gov.ProposalTypeFeeChange, fromAddr, amount, votingPeriod) + err = msg.ValidateBasic() + if err != nil { + return err + } + if cliCtx.GenerateOnly { + return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) + } + cliCtx.PrintResponse = true + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) + }, + } + cmd.Flags().String(flagFeeParamFile, "", "the file of fee params (json format)") + cmd.Flags().String(flagTitle, "", "title of proposal") + cmd.Flags().Int64(flagVotingPeriod, 7*24*60*60, "voting period in seconds") + cmd.Flags().String(flagDescription, "", "description of proposal") + cmd.Flags().String(flagDeposit, "", "deposit of proposal") + return cmd +} + +func ShowFeeParamsCmd(cdc *amino.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "show-fees", + Short: "Show order book of the listed currency pair", + RunE: func(cmd *cobra.Command, args []string) error { + + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) + format := viper.GetString(flagFormat) + if format != types.JSONFORMAT && format != types.AMINOFORMAT { + return fmt.Errorf("format %s is not supported, options [%s, %s] ", format, types.JSONFORMAT, types.AMINOFORMAT) + } + + bz, err := cliCtx.Query(fmt.Sprintf("%s/fees", paramHub.AbciQueryPrefix), nil) + if err != nil { + return err + } + var fees []types.FeeParam + err = cdc.UnmarshalBinaryLengthPrefixed(bz, &fees) + if err != nil { + return err + } + + var output []byte + if format == types.JSONFORMAT { + output, err = json.MarshalIndent(fees, "", "\t") + } else if format == types.AMINOFORMAT { + output, err = cdc.MarshalJSONIndent(fees, "", "\t") + } + if err != nil { + return err + } + fmt.Println(string(output)) + return nil + }, + } + + cmd.Flags().String(flagFormat, types.AMINOFORMAT, fmt.Sprintf("the response format, options: [%s, %s]", types.AMINOFORMAT, types.JSONFORMAT)) + return cmd +} diff --git a/x/paramHub/client/cli/scParams.go b/x/paramHub/client/cli/scParams.go new file mode 100644 index 000000000..d38d85b9e --- /dev/null +++ b/x/paramHub/client/cli/scParams.go @@ -0,0 +1,156 @@ +package cli + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "time" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/tendermint/go-amino" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/utils" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/paramHub" + "github.com/cosmos/cosmos-sdk/x/paramHub/types" +) + +const ( + flagSCParamFile = "sc-param-file" +) + +func SubmitSCParamChangeProposalCmd(cdc *codec.Codec) *cobra.Command { + scParams := types.SCChangeParams{} + cmd := &cobra.Command{ + Use: "submit-sc-change-proposal", + Short: "Submit a side chain param change proposal", + RunE: func(cmd *cobra.Command, args []string) error { + txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) + title := viper.GetString(flagTitle) + initialDeposit := viper.GetString(flagDeposit) + scParamFile := viper.GetString(flagSCParamFile) + scParams.Description = viper.GetString(flagDescription) + votingPeriodInSeconds := viper.GetInt64(flagVotingPeriod) + sideChainId := viper.GetString(flagSideChainId) + if sideChainId == "" { + return fmt.Errorf("missing side-chain-id") + } + if scParamFile == "" { + return errors.New("sc-param-file is missing") + } + + bz, err := ioutil.ReadFile(scParamFile) + if err != nil { + return err + } + err = cdc.UnmarshalJSON(bz, &(scParams.SCParams)) + if err != nil { + return err + } + err = scParams.Check() + if err != nil { + return err + } + fromAddr, err := cliCtx.GetFromAddress() + if err != nil { + return err + } + amount, err := sdk.ParseCoins(initialDeposit) + if err != nil { + return err + } + // scParams get interface field, use amino + scParamsBz, err := cdc.MarshalJSON(scParams) + if err != nil { + return err + } + + if votingPeriodInSeconds <= 0 { + return errors.New("voting period should be positive") + } + + votingPeriod := time.Duration(votingPeriodInSeconds) * time.Second + if votingPeriod > gov.MaxVotingPeriod { + return fmt.Errorf("voting period should less than %d seconds", gov.MaxVotingPeriod/time.Second) + } + + msg := gov.NewMsgSideChainSubmitProposal(title, string(scParamsBz), gov.ProposalTypeSCParamsChange, fromAddr, amount, votingPeriod, sideChainId) + err = msg.ValidateBasic() + if err != nil { + return err + } + if cliCtx.GenerateOnly { + return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) + } + cliCtx.PrintResponse = true + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) + }, + } + cmd.Flags().String(flagSCParamFile, "", "the file of Side Chain params (json format)") + cmd.Flags().String(flagTitle, "", "title of proposal") + cmd.Flags().Int64(flagVotingPeriod, 7*24*60*60, "voting period in seconds") + cmd.Flags().String(flagDescription, "", "description of proposal") + cmd.Flags().String(flagDeposit, "", "deposit of proposal") + cmd.Flags().String(flagSideChainId, "", "the id of side chain") + return cmd +} + +func ShowSideChainParamsCmd(cdc *amino.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "side-params", + Short: "Show the params of the side chain", + RunE: func(cmd *cobra.Command, args []string) error { + sideChainId := viper.GetString(flagSideChainId) + if sideChainId == "" { + return fmt.Errorf("missing side-chain-id") + } + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) + format := viper.GetString(flagFormat) + if format != types.JSONFORMAT && format != types.AMINOFORMAT { + return fmt.Errorf("format %s is not supported, options [%s, %s] ", format, types.JSONFORMAT, types.AMINOFORMAT) + } + + data, err := cdc.MarshalJSON(sideChainId) + if err != nil { + return err + } + bz, err := cliCtx.Query(fmt.Sprintf("%s/sideParams", paramHub.AbciQueryPrefix), data) + if err != nil { + return err + } + var params []types.SCParam + err = cdc.UnmarshalJSON(bz, ¶ms) + if err != nil { + return err + } + + var output []byte + if format == types.JSONFORMAT { + output, err = json.MarshalIndent(params, "", "\t") + } else if format == types.AMINOFORMAT { + output, err = cdc.MarshalJSONIndent(params, "", "\t") + } + if err != nil { + return err + } + fmt.Println(string(output)) + return nil + }, + } + + cmd.Flags().String(flagFormat, types.AMINOFORMAT, fmt.Sprintf("the response format, options: [%s, %s]", types.AMINOFORMAT, types.JSONFORMAT)) + cmd.Flags().String(flagSideChainId, "", "the id of side chain") + return cmd +} diff --git a/x/paramHub/client/rest/getfees.go b/x/paramHub/client/rest/getfees.go new file mode 100644 index 000000000..fdd8943fa --- /dev/null +++ b/x/paramHub/client/rest/getfees.go @@ -0,0 +1,68 @@ +package rest + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + + "github.com/tendermint/go-amino" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/x/paramHub" + "github.com/cosmos/cosmos-sdk/x/paramHub/types" +) + +func GetFeesParamHandler(cdc *amino.Codec, ctx context.CLIContext) http.HandlerFunc { + + responseType := "application/json" + + throw := func(w http.ResponseWriter, status int, err error) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(status) + w.Write([]byte(err.Error())) + return + } + + return func(w http.ResponseWriter, r *http.Request) { + + bz, err := ctx.Query(fmt.Sprintf("%s/fees", paramHub.AbciQueryPrefix), nil) + if err != nil { + throw(w, http.StatusInternalServerError, err) + return + } + var fees []types.FeeParam + err = cdc.UnmarshalBinaryLengthPrefixed(bz, &fees) + if err != nil { + throw(w, http.StatusInternalServerError, err) + return + } + formats, exist := r.URL.Query()["format"] + format := types.JSONFORMAT + if exist { + if len(formats) < 1 { + throw(w, http.StatusBadRequest, errors.New(fmt.Sprintf("Format parameter is invalid"))) + return + } + format = formats[0] + if format != types.JSONFORMAT && format != types.AMINOFORMAT { + throw(w, http.StatusBadRequest, errors.New(fmt.Sprintf("Format %s is not supported, options [%s, %s]", format, types.JSONFORMAT, types.AMINOFORMAT))) + return + } + } + var output []byte + if format == types.JSONFORMAT { + output, err = json.Marshal(fees) + } else if format == types.AMINOFORMAT { + output, err = cdc.MarshalJSON(fees) + } + if err != nil { + throw(w, http.StatusInternalServerError, err) + return + } + + w.Header().Set("Content-Type", responseType) + w.WriteHeader(http.StatusOK) + w.Write(output) + } +} diff --git a/x/paramHub/genesis.go b/x/paramHub/genesis.go new file mode 100644 index 000000000..f13b0d039 --- /dev/null +++ b/x/paramHub/genesis.go @@ -0,0 +1,122 @@ +package paramHub + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + param "github.com/cosmos/cosmos-sdk/x/paramHub/types" +) + +const ( + // Operate fee + ProposeFee = 10e8 + DepositFee = 125e3 + SideProposeFee = 10e8 + SideDepositFee = 125e3 + SideVoteFee = 1e8 + ListingFee = 2000e8 + IssueFee = 1000e8 + MintFee = 200e8 + BurnFee = 1e8 + FreezeFee = 1e6 + TimeLockFee = 1e6 + TimeUnlockFee = 1e6 + TimeRelockFee = 1e6 + + SetAccountFlagsFee = 1e8 + + HTLTFee = 37500 + DepositHTLTFee = 37500 + ClaimHTLTFee = 37500 + RefundHTLTFee = 37500 + + // stake fee + CreateValidatorFee = 10e8 + RemoveValidatorFee = 1e8 + CreateSideChainValidatorFee = 10e8 + EditSideChainValidatorFee = 1e8 + SideChainDelegateFee = 1e5 + SideChainRedelegateFee = 3e5 + SideChainUndelegateFee = 2e5 + + // slashing fee + BscSubmitEvidenceFee = 10e8 + SideChainUnjail = 1e8 + + // Transfer fee + TransferFee = 62500 + MultiTransferFee = 50000 // discount 80% + LowerLimitAsMulti = 2 + + // Dex fee + ExpireFee = 5e4 + ExpireFeeNative = 1e4 + CancelFee = 5e4 + CancelFeeNative = 1e4 + FeeRate = 1000 + FeeRateNative = 400 + IOCExpireFee = 25e3 + IOCExpireFeeNative = 5e3 + + // cross chain + CrossBindFee = 1e6 + CrossUnbindFee = 1e6 + CrossTransferOutFee = 1e6 + + CrossTransferOutRelayFee = 2e6 + CrossBindRelayFee = 2e6 + CrossUnbindRelayFee = 2e6 + + //MiniToken fee + TinyIssueFee = 2e8 + MiniIssueFee = 4e8 + MiniSetUriFee = 37500 + MiniListingFee = 10e8 +) + +var DefaultGenesisState = param.GenesisState{ + FeeGenesis: FeeGenesisState, + + //Add other param genesis here +} + +// --------- Definition about fee prams ------------------- // +var FeeGenesisState = []param.FeeParam{ + // Operate + ¶m.FixedFeeParams{"submit_proposal", ProposeFee, sdk.FeeForProposer}, + ¶m.FixedFeeParams{"deposit", DepositFee, sdk.FeeForProposer}, + ¶m.FixedFeeParams{"vote", sdk.ZeroFee, sdk.FeeFree}, + ¶m.FixedFeeParams{"create_validator", CreateValidatorFee, sdk.FeeForProposer}, + ¶m.FixedFeeParams{"remove_validator", RemoveValidatorFee, sdk.FeeForProposer}, + ¶m.FixedFeeParams{"dexList", ListingFee, sdk.FeeForAll}, + ¶m.FixedFeeParams{"orderNew", sdk.ZeroFee, sdk.FeeFree}, + ¶m.FixedFeeParams{"orderCancel", sdk.ZeroFee, sdk.FeeFree}, + ¶m.FixedFeeParams{"issueMsg", IssueFee, sdk.FeeForAll}, + ¶m.FixedFeeParams{"mintMsg", MintFee, sdk.FeeForAll}, + ¶m.FixedFeeParams{"tokensBurn", BurnFee, sdk.FeeForProposer}, + ¶m.FixedFeeParams{"tokensFreeze", FreezeFee, sdk.FeeForProposer}, + + // Transfer + ¶m.TransferFeeParam{ + FixedFeeParams: param.FixedFeeParams{ + MsgType: "send", + Fee: TransferFee, + FeeFor: sdk.FeeForProposer}, + MultiTransferFee: MultiTransferFee, + LowerLimitAsMulti: LowerLimitAsMulti, + }, + + // Dex + ¶m.DexFeeParam{ + DexFeeFields: []param.DexFeeField{ + {"ExpireFee", ExpireFee}, + {"ExpireFeeNative", ExpireFeeNative}, + {"CancelFee", CancelFee}, + {"CancelFeeNative", CancelFeeNative}, + {"FeeRate", FeeRate}, + {"FeeRateNative", FeeRateNative}, + {"IOCExpireFee", IOCExpireFee}, + {"IOCExpireFeeNative", IOCExpireFeeNative}, + }, + }, +} + +//---------- End definition about fee param ---------------- // diff --git a/x/paramHub/hooks.go b/x/paramHub/hooks.go new file mode 100644 index 000000000..ca5b099c5 --- /dev/null +++ b/x/paramHub/hooks.go @@ -0,0 +1,86 @@ +package paramHub + +import ( + "fmt" + + "github.com/tendermint/go-amino" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/paramHub/types" +) + +//--------------------- FeeChangeHooks ----------------- +type FeeChangeHooks struct { + cdc *amino.Codec +} + +func NewFeeChangeHooks(cdc *amino.Codec) FeeChangeHooks { + return FeeChangeHooks{cdc} +} + +var _ gov.GovHooks = FeeChangeHooks{} + +func (hooks FeeChangeHooks) OnProposalSubmitted(ctx sdk.Context, proposal gov.Proposal) error { + if proposal.GetProposalType() != gov.ProposalTypeFeeChange { + panic(fmt.Sprintf("received wrong type of proposal %x", proposal.GetProposalType())) + } + + feeParams := types.FeeChangeParams{} + err := hooks.cdc.UnmarshalJSON([]byte(proposal.GetDescription()), &feeParams) + if err != nil { + return fmt.Errorf("unmarshal feeParam error, err=%s", err.Error()) + } + + return feeParams.Check() +} + +//--------------------- CSCParamsChangeHook ----------------- +type CSCParamsChangeHooks struct { + cdc *amino.Codec +} + +func NewCSCParamsChangeHook(cdc *amino.Codec) CSCParamsChangeHooks { + return CSCParamsChangeHooks{cdc} +} + +var _ gov.GovHooks = CSCParamsChangeHooks{} + +func (hooks CSCParamsChangeHooks) OnProposalSubmitted(ctx sdk.Context, proposal gov.Proposal) error { + if proposal.GetProposalType() != gov.ProposalTypeCSCParamsChange { + panic(fmt.Sprintf("received wrong type of proposal %x", proposal.GetProposalType())) + } + + var changeParam types.CSCParamChange + strProposal := proposal.GetDescription() + err := hooks.cdc.UnmarshalJSON([]byte(strProposal), &changeParam) + if err != nil { + return fmt.Errorf("get broken data when unmarshal CSCParamChange msg. proposalId %d, err %v", proposal.GetProposalID(), err) + } + return changeParam.Check() +} + +//--------------------- SCParamsChangeHook ----------------- +type SCParamsChangeHooks struct { + cdc *amino.Codec +} + +func NewSCParamsChangeHook(cdc *amino.Codec) SCParamsChangeHooks { + return SCParamsChangeHooks{cdc} +} + +var _ gov.GovHooks = SCParamsChangeHooks{} + +func (hooks SCParamsChangeHooks) OnProposalSubmitted(ctx sdk.Context, proposal gov.Proposal) error { + if proposal.GetProposalType() != gov.ProposalTypeSCParamsChange { + panic(fmt.Sprintf("received wrong type of proposal %x", proposal.GetProposalType())) + } + + var changeParam types.SCChangeParams + strProposal := proposal.GetDescription() + err := hooks.cdc.UnmarshalJSON([]byte(strProposal), &changeParam) + if err != nil { + fmt.Errorf("get broken data when unmarshal SCParamsChange msg. proposalId %d, err %v", proposal.GetProposalID(), err) + } + return changeParam.Check() +} diff --git a/x/paramHub/hub.go b/x/paramHub/hub.go new file mode 100644 index 000000000..4a48196d2 --- /dev/null +++ b/x/paramHub/hub.go @@ -0,0 +1,132 @@ +package paramHub + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/fees" + "github.com/cosmos/cosmos-sdk/x/bank" + param "github.com/cosmos/cosmos-sdk/x/paramHub/types" +) + +const AbciQueryPrefix = "param" + +func RegisterUpgradeBeginBlocker(paramHub *ParamHub) { + sdk.UpgradeMgr.RegisterBeginBlocker(sdk.BEP9, func(ctx sdk.Context) { + timeLockFeeParams := []param.FeeParam{ + ¶m.FixedFeeParams{MsgType: "timeLock", Fee: TimeLockFee, FeeFor: sdk.FeeForProposer}, + ¶m.FixedFeeParams{MsgType: "timeUnlock", Fee: TimeUnlockFee, FeeFor: sdk.FeeForProposer}, + ¶m.FixedFeeParams{MsgType: "timeRelock", Fee: TimeRelockFee, FeeFor: sdk.FeeForProposer}, + } + paramHub.UpdateFeeParams(ctx, timeLockFeeParams) + }) + sdk.UpgradeMgr.RegisterBeginBlocker(sdk.BEP12, func(ctx sdk.Context) { + accountFlagsFeeParams := []param.FeeParam{ + ¶m.FixedFeeParams{MsgType: "setAccountFlags", Fee: SetAccountFlagsFee, FeeFor: sdk.FeeForProposer}, + } + paramHub.UpdateFeeParams(ctx, accountFlagsFeeParams) + }) + sdk.UpgradeMgr.RegisterBeginBlocker(sdk.BEP3, func(ctx sdk.Context) { + swapFeeParams := []param.FeeParam{ + ¶m.FixedFeeParams{MsgType: "HTLT", Fee: HTLTFee, FeeFor: sdk.FeeForProposer}, + ¶m.FixedFeeParams{MsgType: "depositHTLT", Fee: DepositHTLTFee, FeeFor: sdk.FeeForProposer}, + ¶m.FixedFeeParams{MsgType: "claimHTLT", Fee: ClaimHTLTFee, FeeFor: sdk.FeeForProposer}, + ¶m.FixedFeeParams{MsgType: "refundHTLT", Fee: RefundHTLTFee, FeeFor: sdk.FeeForProposer}, + } + paramHub.UpdateFeeParams(ctx, swapFeeParams) + }) + sdk.UpgradeMgr.RegisterBeginBlocker(sdk.LaunchBscUpgrade, func(ctx sdk.Context) { + updateFeeParams := []param.FeeParam{ + ¶m.FixedFeeParams{MsgType: "side_create_validator", Fee: CreateSideChainValidatorFee, FeeFor: sdk.FeeForProposer}, + ¶m.FixedFeeParams{MsgType: "side_edit_validator", Fee: EditSideChainValidatorFee, FeeFor: sdk.FeeForProposer}, + ¶m.FixedFeeParams{MsgType: "side_delegate", Fee: SideChainDelegateFee, FeeFor: sdk.FeeForProposer}, + ¶m.FixedFeeParams{MsgType: "side_redelegate", Fee: SideChainRedelegateFee, FeeFor: sdk.FeeForProposer}, + ¶m.FixedFeeParams{MsgType: "side_undelegate", Fee: SideChainUndelegateFee, FeeFor: sdk.FeeForProposer}, + + ¶m.FixedFeeParams{MsgType: "bsc_submit_evidence", Fee: BscSubmitEvidenceFee, FeeFor: sdk.FeeForProposer}, + ¶m.FixedFeeParams{MsgType: "side_chain_unjail", Fee: SideChainUnjail, FeeFor: sdk.FeeForProposer}, + + ¶m.FixedFeeParams{MsgType: "side_submit_proposal", Fee: SideProposeFee, FeeFor: sdk.FeeForProposer}, + ¶m.FixedFeeParams{MsgType: "side_deposit", Fee: SideDepositFee, FeeFor: sdk.FeeForProposer}, + ¶m.FixedFeeParams{MsgType: "side_vote", Fee: SideVoteFee, FeeFor: sdk.FeeForProposer}, + + ¶m.FixedFeeParams{MsgType: "crossBind", Fee: CrossBindFee, FeeFor: sdk.FeeForProposer}, + ¶m.FixedFeeParams{MsgType: "crossUnbind", Fee: CrossUnbindFee, FeeFor: sdk.FeeForProposer}, + ¶m.FixedFeeParams{MsgType: "crossTransferOut", Fee: CrossTransferOutFee, FeeFor: sdk.FeeForProposer}, + ¶m.FixedFeeParams{MsgType: "oracleClaim", Fee: sdk.ZeroFee, FeeFor: sdk.FeeFree}, + + // Following fees are charged on BC, and received at BSC, they are still fees in a broad sense, so still + // decide to put it here, rather than in paramset. + ¶m.FixedFeeParams{MsgType: "crossBindRelayFee", Fee: CrossBindRelayFee, FeeFor: sdk.FeeForProposer}, + ¶m.FixedFeeParams{MsgType: "crossUnbindRelayFee", Fee: CrossUnbindRelayFee, FeeFor: sdk.FeeForProposer}, + ¶m.FixedFeeParams{MsgType: "crossTransferOutRelayFee", Fee: CrossTransferOutRelayFee, FeeFor: sdk.FeeForProposer}, + } + paramHub.UpdateFeeParams(ctx, updateFeeParams) + }) + sdk.UpgradeMgr.RegisterBeginBlocker(sdk.BEP8, func(ctx sdk.Context) { + miniTokenFeeParams := []param.FeeParam{ + ¶m.FixedFeeParams{MsgType: "tinyIssueMsg", Fee: TinyIssueFee, FeeFor: sdk.FeeForAll}, + ¶m.FixedFeeParams{MsgType: "miniIssueMsg", Fee: MiniIssueFee, FeeFor: sdk.FeeForAll}, + ¶m.FixedFeeParams{MsgType: "miniTokensSetURI", Fee: MiniSetUriFee, FeeFor: sdk.FeeForProposer}, + ¶m.FixedFeeParams{MsgType: "dexListMini", Fee: MiniListingFee, FeeFor: sdk.FeeForAll}, + } + paramHub.UpdateFeeParams(ctx, miniTokenFeeParams) + }) +} + +func EndBreatheBlock(ctx sdk.Context, paramHub *ParamHub) { + paramHub.EndBreatheBlock(ctx) + return +} + +func EndBlock(ctx sdk.Context, paramHub *ParamHub) { + paramHub.EndBlock(ctx) + return +} + +func init() { + // CalculatorsGen is defined in a common package which can't import app package. + // Reasonable to init here, since fee param drive the calculator. + fees.CalculatorsGen = map[string]fees.FeeCalculatorGenerator{ + "submit_proposal": fees.FixedFeeCalculatorGen, + "deposit": fees.FixedFeeCalculatorGen, + "vote": fees.FixedFeeCalculatorGen, + "side_submit_proposal": fees.FixedFeeCalculatorGen, + "side_deposit": fees.FixedFeeCalculatorGen, + "side_vote": fees.FixedFeeCalculatorGen, + "create_validator": fees.FixedFeeCalculatorGen, + "remove_validator": fees.FixedFeeCalculatorGen, + "side_create_validator": fees.FixedFeeCalculatorGen, + "side_edit_validator": fees.FixedFeeCalculatorGen, + "side_delegate": fees.FixedFeeCalculatorGen, + "side_redelegate": fees.FixedFeeCalculatorGen, + "side_undelegate": fees.FixedFeeCalculatorGen, + "bsc_submit_evidence": fees.FixedFeeCalculatorGen, + "side_chain_unjail": fees.FixedFeeCalculatorGen, + "dexList": fees.FixedFeeCalculatorGen, + "orderNew": fees.FixedFeeCalculatorGen, + "orderCancel": fees.FixedFeeCalculatorGen, + "issueMsg": fees.FixedFeeCalculatorGen, + "mintMsg": fees.FixedFeeCalculatorGen, + "tokensBurn": fees.FixedFeeCalculatorGen, + "setAccountFlags": fees.FixedFeeCalculatorGen, + "tokensFreeze": fees.FixedFeeCalculatorGen, + "timeLock": fees.FixedFeeCalculatorGen, + "timeUnlock": fees.FixedFeeCalculatorGen, + "timeRelock": fees.FixedFeeCalculatorGen, + "send": bank.TransferFeeCalculatorGen, + "HTLT": fees.FixedFeeCalculatorGen, + "depositHTLT": fees.FixedFeeCalculatorGen, + "claimHTLT": fees.FixedFeeCalculatorGen, + "refundHTLT": fees.FixedFeeCalculatorGen, + "crossBind": fees.FixedFeeCalculatorGen, + "crossUnbind": fees.FixedFeeCalculatorGen, + "crossTransferOut": fees.FixedFeeCalculatorGen, + "crossBindRelayFee": fees.FixedFeeCalculatorGen, + "crossUnbindRelayFee": fees.FixedFeeCalculatorGen, + "crossTransferOutRelayFee": fees.FixedFeeCalculatorGen, + "oracleClaim": fees.FixedFeeCalculatorGen, + "miniTokensSetURI": fees.FixedFeeCalculatorGen, + "dexListMini": fees.FixedFeeCalculatorGen, + "tinyIssueMsg": fees.FixedFeeCalculatorGen, + "miniIssueMsg": fees.FixedFeeCalculatorGen, + } +} diff --git a/x/paramHub/keeper/cscParams.go b/x/paramHub/keeper/cscParams.go new file mode 100644 index 000000000..34ddced4b --- /dev/null +++ b/x/paramHub/keeper/cscParams.go @@ -0,0 +1,66 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/paramHub/types" +) + +func (keeper *Keeper) registerCSCParamsCallBack() { + keeper.SubscribeParamChange( + func(context sdk.Context, iChange interface{}) { + switch change := iChange.(type) { + case types.CSCParamChanges: + nativeCtx := context.DepriveSideChainKeyPrefix() + keeper.updateCSCParams(nativeCtx, change) + default: + keeper.Logger(context).Debug("Receive param change that not interested.") + } + }, nil, nil, nil, + ) +} + +func (keeper *Keeper) updateCSCParams(ctx sdk.Context, updates types.CSCParamChanges) { + // write package in reverse order + for j := len(updates.Changes) - 1; j >= 0; j-- { + change := updates.Changes[j] + _, err := keeper.SaveParamChangeToIbc(ctx, updates.ChainID, change) + if err != nil { + keeper.Logger(ctx).Error("failed to save param change to ibc", "err", err, "change", change) + } + } +} + +func (keeper *Keeper) getLastCSCParamChanges(ctx sdk.Context) []types.CSCParamChange { + changes := make([]types.CSCParamChange, 0) + // It can still find the valid proposal if the block chain stop for SafeToleratePeriod time + backPeriod := SafeToleratePeriod + gov.MaxVotingPeriod + keeper.govKeeper.Iterate(ctx, nil, nil, gov.StatusNil, 0, true, func(proposal gov.Proposal) bool { + if proposal.GetProposalType() == gov.ProposalTypeCSCParamsChange { + if ctx.BlockHeader().Time.Sub(proposal.GetVotingStartTime()) > backPeriod { + return true + } + if proposal.GetStatus() != gov.StatusPassed { + return false + } + + proposal.SetStatus(gov.StatusExecuted) + keeper.govKeeper.SetProposal(ctx, proposal) + + var changeParam types.CSCParamChange + strProposal := proposal.GetDescription() + err := keeper.cdc.UnmarshalJSON([]byte(strProposal), &changeParam) + if err != nil { + keeper.Logger(ctx).Error("Get broken data when unmarshal CSCParamChange msg, will skip.", "proposalId", proposal.GetProposalID(), "err", err) + return false + } + if err := changeParam.Check(); err != nil { + keeper.Logger(ctx).Error("The CSCParamChange proposal is invalid, will skip.", "proposalId", proposal.GetProposalID(), "param", changeParam, "err", err) + return false + } + changes = append(changes, changeParam) + } + return false + }) + return changes +} diff --git a/x/paramHub/keeper/fees.go b/x/paramHub/keeper/fees.go new file mode 100644 index 000000000..f63998ffb --- /dev/null +++ b/x/paramHub/keeper/fees.go @@ -0,0 +1,153 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/fees" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/paramHub/types" +) + +func (keeper *Keeper) initFeeGenesis(ctx sdk.Context, state types.GenesisState) { + keeper.SetFeeParams(ctx, state.FeeGenesis) +} + +func (keeper *Keeper) UpdateFeeParams(ctx sdk.Context, updates []types.FeeParam) { + log := keeper.Logger(ctx) + origin := keeper.GetFeeParams(ctx) + opFeeMap := make(map[string]int, len(updates)) + dexFeeLoc := 0 + for index, update := range origin { + switch update := update.(type) { + case types.MsgFeeParams: + opFeeMap[update.GetMsgType()] = index + case *types.DexFeeParam: + dexFeeLoc = index + default: + log.Debug("Origin Fee param not supported ", "feeParam", update) + } + } + for _, update := range updates { + switch update := update.(type) { + case types.MsgFeeParams: + if index, exist := opFeeMap[update.GetMsgType()]; exist { + origin[index] = update + } else { + opFeeMap[update.GetMsgType()] = len(origin) + origin = append(origin, update) + } + case *types.DexFeeParam: + origin[dexFeeLoc] = update + default: + log.Info("Update fee param not supported ", "feeParam", update) + } + } + keeper.updateFeeCalculator(origin) + keeper.SetFeeParams(ctx, origin) + return +} + +func (keeper *Keeper) loadFeeParam(ctx sdk.Context) { + fp := keeper.GetFeeParams(ctx) + keeper.notifyOnLoad(ctx, fp) +} + +func (keeper *Keeper) registerFeeParamCallBack() { + keeper.SubscribeParamChange( + func(ctx sdk.Context, iChange interface{}) { + switch change := iChange.(type) { + case []types.FeeParam: + keeper.UpdateFeeParams(ctx, change) + default: + keeper.Logger(ctx).Debug("Receive param changes that not interested.") + } + }, + nil, + func(context sdk.Context, state interface{}) { + switch genesisState := state.(type) { + case types.GenesisState: + keeper.SetFeeParams(context, genesisState.FeeGenesis) + keeper.updateFeeCalculator(genesisState.FeeGenesis) + default: + keeper.Logger(context).Debug("Receive genesis param that not interested.") + } + }, + func(context sdk.Context, iLoad interface{}) { + switch load := iLoad.(type) { + case []types.FeeParam: + keeper.updateFeeCalculator(load) + default: + keeper.Logger(context).Debug("Receive load param that not interested.") + } + }, + ) +} + +func (keeper *Keeper) updateFeeCalculator(updates []types.FeeParam) { + fees.UnsetAllCalculators() + for _, u := range updates { + if u, ok := u.(types.MsgFeeParams); ok { + generator := fees.GetCalculatorGenerator(u.GetMsgType()) + if generator == nil { + continue + } else { + err := u.Check() + if err != nil { + panic(err) + } + fees.RegisterCalculator(u.GetMsgType(), generator(u)) + } + } + } +} + +func (keeper *Keeper) getLastFeeChangeParam(ctx sdk.Context) []types.FeeParam { + log := keeper.Logger(ctx) + var latestProposal *gov.Proposal + lastProposalId := keeper.getLastFeeChangeProposalId(ctx) + keeper.govKeeper.Iterate(ctx, nil, nil, gov.StatusPassed, lastProposalId.ProposalID, true, func(proposal gov.Proposal) bool { + if proposal.GetProposalType() == gov.ProposalTypeFeeChange { + latestProposal = &proposal + return true + } + return false + }) + if latestProposal != nil { + var changeParam types.FeeChangeParams + strProposal := (*latestProposal).GetDescription() + err := keeper.cdc.UnmarshalJSON([]byte(strProposal), &changeParam) + if err != nil { + log.Error("Get broken data when unmarshal FeeChangeParams msg, will skip", "proposalId", (*latestProposal).GetProposalID(), "err", err) + return nil + } + // setLastFeeProposal first. If invalid, the proposal before it will not been processed too. + keeper.setLastFeeChangeProposalId(ctx, types.LastProposalID{(*latestProposal).GetProposalID()}) + if err := changeParam.Check(); err != nil { + log.Error("The latest fee param change proposal is invalid.", "proposalId", (*latestProposal).GetProposalID(), "param", changeParam, "err", err) + return nil + } + return changeParam.FeeParams + } + return nil +} + +func (keeper *Keeper) GetFeeParams(ctx sdk.Context) []types.FeeParam { + feeParams := make([]types.FeeParam, 0) + keeper.paramSpace.Get(ctx, ParamStoreKeyFees, &feeParams) + return feeParams +} + +func (keeper *Keeper) SetFeeParams(ctx sdk.Context, fp []types.FeeParam) { + keeper.paramSpace.Set(ctx, ParamStoreKeyFees, fp) + return +} + +func (keeper *Keeper) getLastFeeChangeProposalId(ctx sdk.Context) types.LastProposalID { + var id types.LastProposalID + keeper.paramSpace.Get(ctx, ParamStoreKeyLastFeeChangeProposalID, &id) + return id +} + +func (keeper *Keeper) setLastFeeChangeProposalId(ctx sdk.Context, id types.LastProposalID) { + keeper.paramSpace.Set(ctx, ParamStoreKeyLastFeeChangeProposalID, &id) + return +} diff --git a/x/paramHub/keeper/ibc.go b/x/paramHub/keeper/ibc.go new file mode 100644 index 000000000..de9939b15 --- /dev/null +++ b/x/paramHub/keeper/ibc.go @@ -0,0 +1,23 @@ +package keeper + +import ( + "github.com/cosmos/cosmos-sdk/bsc/rlp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/paramHub/types" +) + +const ( + ChannelName = "params" + ChannelId = sdk.ChannelID(9) +) + +func (keeper *Keeper) SaveParamChangeToIbc(ctx sdk.Context, sideChainId string, paramChange types.CSCParamChange) (seq uint64, sdkErr sdk.Error) { + if keeper.ibcKeeper == nil { + return 0, sdk.ErrInternal("the keeper is not prepared for side chain") + } + bz, err := rlp.EncodeToBytes(¶mChange) + if err != nil { + return 0, sdk.ErrInternal("failed to encode paramChange") + } + return keeper.ibcKeeper.CreateIBCSyncPackage(ctx, sideChainId, ChannelName, bz) +} diff --git a/x/paramHub/keeper/keeper.go b/x/paramHub/keeper/keeper.go new file mode 100644 index 000000000..9d9b4c914 --- /dev/null +++ b/x/paramHub/keeper/keeper.go @@ -0,0 +1,222 @@ +package keeper + +import ( + "fmt" + "time" + + "github.com/tendermint/tendermint/libs/log" + + "github.com/cosmos/cosmos-sdk/bsc/rlp" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/ibc" + "github.com/cosmos/cosmos-sdk/x/paramHub/types" + "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/sidechain" + sTypes "github.com/cosmos/cosmos-sdk/x/sidechain/types" +) + +var ( + ParamStoreKeyLastFeeChangeProposalID = []byte("lastFeeChangeProposalID") + ParamStoreKeyFees = []byte("fees") + + // for side chain + ParamStoreKeySCLastParamsChangeProposalID = []byte("SCLastParamsChangeProposalID") +) + +const ( + ParamSpace = "paramhub" + + SafeToleratePeriod = 2 * 7 * 24 * 60 * 60 * time.Second // 2 weeks +) + +func ParamTypeTable() params.TypeTable { + return params.NewTypeTable( + ParamStoreKeyLastFeeChangeProposalID, types.LastProposalID{}, + ParamStoreKeyFees, []types.FeeParam{}, + ParamStoreKeySCLastParamsChangeProposalID, types.LastProposalID{}, + ) +} + +type Keeper struct { + params.Keeper + cdc *codec.Codec + paramSpace params.Subspace + + // just for query + subscriberParamSpace []*types.ParamSpaceProto + + govKeeper *gov.Keeper + ibcKeeper *ibc.Keeper + ScKeeper *sidechain.Keeper + + updateCallbacks []func(sdk.Context, interface{}) + genesisCallbacks []func(sdk.Context, interface{}) + loadCallBacks []func(sdk.Context, interface{}) +} + +func NewKeeper(cdc *codec.Codec, key *sdk.KVStoreKey, tkey *sdk.TransientStoreKey) *Keeper { + keeper := Keeper{ + Keeper: params.NewKeeper(cdc, key, tkey), + cdc: cdc, + updateCallbacks: make([]func(sdk.Context, interface{}), 0), + genesisCallbacks: make([]func(sdk.Context, interface{}), 0), + subscriberParamSpace: make([]*types.ParamSpaceProto, 0), + } + keeper.paramSpace = keeper.Subspace(ParamSpace).WithTypeTable(ParamTypeTable()) + // Add global callback(belongs to no other plugin) here + keeper.registerFeeParamCallBack() + keeper.registerCSCParamsCallBack() + return &keeper +} + +func (keeper *Keeper) GetSubscriberParamSpace() []*types.ParamSpaceProto { + return keeper.subscriberParamSpace +} + +func (keeper *Keeper) SetGovKeeper(govKeeper *gov.Keeper) { + keeper.govKeeper = govKeeper +} + +func (keeper *Keeper) SetupForSideChain(scKeeper *sidechain.Keeper, ibcKeeper *ibc.Keeper) { + keeper.ScKeeper = scKeeper + keeper.ibcKeeper = ibcKeeper + keeper.initIbc() +} + +func (keeper *Keeper) GetCodeC() *codec.Codec { + return keeper.cdc +} + +func (keeper Keeper) initIbc() { + if keeper.ibcKeeper == nil { + return + } + err := keeper.ScKeeper.RegisterChannel(ChannelName, ChannelId, &keeper) + if err != nil { + panic(fmt.Sprintf("register ibc channel failed, channel=%s, err=%s", ChannelName, err.Error())) + } +} + +func (keeper *Keeper) EndBreatheBlock(ctx sdk.Context) { + log := keeper.Logger(ctx) + log.Info("Sync breath block params proposals.") + feeChange := keeper.getLastFeeChangeParam(ctx) + if feeChange != nil { + keeper.notifyOnUpdate(ctx, feeChange) + } + if sdk.IsUpgrade(sdk.LaunchBscUpgrade) { + _, storePrefixes := keeper.ScKeeper.GetAllSideChainPrefixes(ctx) + for i := range storePrefixes { + sideChainCtx := ctx.WithSideChainKeyPrefix(storePrefixes[i]) + scParamChanges := keeper.getLastSCParamChanges(sideChainCtx) + if scParamChanges != nil { + for _, change := range scParamChanges.SCParams { + keeper.notifyOnUpdate(sideChainCtx, change) + } + } + } + } + return +} + +func (keeper *Keeper) EndBlock(ctx sdk.Context) { + log := keeper.Logger(ctx) + log.Info("Sync params proposals.") + if sdk.IsUpgrade(sdk.LaunchBscUpgrade) && keeper.ScKeeper != nil { + sideChainIds, storePrefixes := keeper.ScKeeper.GetAllSideChainPrefixes(ctx) + for idx := range storePrefixes { + sideChainCtx := ctx.WithSideChainKeyPrefix(storePrefixes[idx]) + cscChanges := keeper.getLastCSCParamChanges(sideChainCtx) + if len(cscChanges) > 0 { + keeper.notifyOnUpdate(sideChainCtx, types.CSCParamChanges{Changes: cscChanges, ChainID: sideChainIds[idx]}) + } + } + } + return +} + +func (keeper *Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", "paramHub") +} + +func (keeper *Keeper) notifyOnUpdate(context sdk.Context, change interface{}) { + for _, c := range keeper.updateCallbacks { + c(context, change) + } +} + +func (keeper *Keeper) notifyOnLoad(ctx sdk.Context, load interface{}) { + for _, c := range keeper.loadCallBacks { + c(ctx, load) + } +} + +func (keeper *Keeper) notifyOnGenesis(ctx sdk.Context, state interface{}) { + for _, c := range keeper.genesisCallbacks { + c(ctx, state) + } +} + +func (keeper *Keeper) InitGenesis(ctx sdk.Context, params types.GenesisState) { + keeper.initFeeGenesis(ctx, params) + keeper.notifyOnGenesis(ctx, params) + keeper.setLastFeeChangeProposalId(ctx, types.LastProposalID{0}) +} + +func (keeper *Keeper) Load(ctx sdk.Context) { + keeper.loadFeeParam(ctx) +} + +func (keeper *Keeper) SubscribeParamChange(updateCb func(sdk.Context, interface{}), spaceProto *types.ParamSpaceProto, genesisCb func(sdk.Context, interface{}), loadCb func(sdk.Context, interface{})) { + if updateCb != nil { + keeper.SubscribeUpdateEvent(updateCb) + } + if genesisCb != nil { + keeper.SubscribeGenesisEvent(genesisCb) + } + if loadCb != nil { + keeper.SubscribeLoadEvent(loadCb) + } + if spaceProto != nil { + keeper.subscriberParamSpace = append(keeper.subscriberParamSpace, spaceProto) + } +} + +func (keeper *Keeper) SubscribeUpdateEvent(c func(sdk.Context, interface{})) { + keeper.updateCallbacks = append(keeper.updateCallbacks, c) +} + +func (keeper *Keeper) SubscribeGenesisEvent(c func(sdk.Context, interface{})) { + keeper.genesisCallbacks = append(keeper.genesisCallbacks, c) +} + +func (keeper *Keeper) SubscribeLoadEvent(c func(sdk.Context, interface{})) { + keeper.loadCallBacks = append(keeper.loadCallBacks, c) +} + +// implement cross chain app +func (keeper *Keeper) ExecuteSynPackage(ctx sdk.Context, payload []byte) sdk.ExecuteResult { + panic("receive unexpected package") +} + +func (keeper *Keeper) ExecuteAckPackage(ctx sdk.Context, payload []byte) sdk.ExecuteResult { + var ackPackage sTypes.CommonAckPackage + err := rlp.DecodeBytes(payload, &ackPackage) + if err != nil { + keeper.Logger(ctx).Error("fail to decode ack package", "payload", payload) + return sdk.ExecuteResult{Err: types.ErrInvalidCrossChainPackage(types.DefaultCodespace)} + } + if !ackPackage.IsOk() { + keeper.Logger(ctx).Error("side chain failed to process param package", "code", ackPackage.Code) + } + return sdk.ExecuteResult{} +} + +// When the ack application crash, payload is the payload of the origin package. +func (keeper *Keeper) ExecuteFailAckPackage(ctx sdk.Context, payload []byte) sdk.ExecuteResult { + //do no thing + keeper.Logger(ctx).Error("side chain process params package crashed", "payload", payload) + return sdk.ExecuteResult{} +} diff --git a/x/paramHub/keeper/keeper_test.go b/x/paramHub/keeper/keeper_test.go new file mode 100644 index 000000000..c8500b4b1 --- /dev/null +++ b/x/paramHub/keeper/keeper_test.go @@ -0,0 +1,3 @@ +package keeper + +// ParamHub module involve many modules, we write testcase in more higher layer, please check app/app_paramhub_test.go on Node repo. diff --git a/x/paramHub/keeper/scParams.go b/x/paramHub/keeper/scParams.go new file mode 100644 index 000000000..97d7e7e91 --- /dev/null +++ b/x/paramHub/keeper/scParams.go @@ -0,0 +1,67 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/paramHub/types" +) + +func (keeper *Keeper) getLastSCParamChanges(ctx sdk.Context) *types.SCChangeParams { + var latestProposal *gov.Proposal + lastProposalId := keeper.GetLastSCParamChangeProposalId(ctx) + keeper.govKeeper.Iterate(ctx, nil, nil, gov.StatusPassed, lastProposalId.ProposalID, true, func(proposal gov.Proposal) bool { + if proposal.GetProposalType() == gov.ProposalTypeSCParamsChange { + latestProposal = &proposal + return true + } + return false + }) + + if latestProposal != nil { + var changeParam types.SCChangeParams + strProposal := (*latestProposal).GetDescription() + err := keeper.cdc.UnmarshalJSON([]byte(strProposal), &changeParam) + if err != nil { + keeper.Logger(ctx).Error("Get broken data when unmarshal SCParamsChange msg, will skip.", "proposalId", (*latestProposal).GetProposalID(), "err", err) + return nil + } + // SetLastSCParamChangeProposalId first. If invalid, the proposal before it will not been processed too. + keeper.SetLastSCParamChangeProposalId(ctx, types.LastProposalID{ProposalID: (*latestProposal).GetProposalID()}) + if err := changeParam.Check(); err != nil { + keeper.Logger(ctx).Error("The SCParamsChange proposal is invalid, will skip.", "proposalId", (*latestProposal).GetProposalID(), "param", changeParam, "err", err) + return nil + } + return &changeParam + } + return nil +} + +func (keeper *Keeper) GetLastSCParamChangeProposalId(ctx sdk.Context) types.LastProposalID { + var id types.LastProposalID + keeper.paramSpace.GetIfExists(ctx, ParamStoreKeySCLastParamsChangeProposalID, &id) + return id +} + +func (keeper *Keeper) SetLastSCParamChangeProposalId(ctx sdk.Context, id types.LastProposalID) { + keeper.paramSpace.Set(ctx, ParamStoreKeySCLastParamsChangeProposalID, &id) + return +} + +func (keeper *Keeper) GetSCParams(ctx sdk.Context, sideChainId string) ([]types.SCParam, sdk.Error) { + storePrefix := keeper.ScKeeper.GetSideChainStorePrefix(ctx, sideChainId) + if len(storePrefix) == 0 { + return nil, types.ErrInvalidSideChainId(types.DefaultCodespace, "the side chain id is not registered") + } + newCtx := ctx.WithSideChainKeyPrefix(storePrefix) + params := make([]types.SCParam, 0) + for _, subSpace := range keeper.GetSubscriberParamSpace() { + param := subSpace.Proto() + if _, native := param.GetParamAttribute(); native { + subSpace.ParamSpace.GetParamSet(ctx, param) + } else { + subSpace.ParamSpace.GetParamSet(newCtx, param) + } + params = append(params, param) + } + return params, nil +} diff --git a/x/paramHub/queryable.go b/x/paramHub/queryable.go new file mode 100644 index 000000000..ce82c1441 --- /dev/null +++ b/x/paramHub/queryable.go @@ -0,0 +1,111 @@ +package paramHub + +import ( + "fmt" + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/paramHub/types" +) + +func NewQuerier(hub *ParamHub, cdc *codec.Codec) sdk.Querier { + return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) { + switch path[0] { + case "fees": + fp := hub.GetFeeParams(ctx) + res, err := cdc.MarshalBinaryLengthPrefixed(fp) + if err != nil { + return nil, sdk.ErrInternal(err.Error()) + } + return res, nil + case "sideParams": + if len(req.Data) == 0 { + return nil, types.ErrMissSideChainId(types.DefaultCodespace) + } + var sideChainId string + err := cdc.UnmarshalJSON(req.Data, &sideChainId) + if err != nil { + return nil, types.ErrInvalidSideChainId(types.DefaultCodespace, err.Error()) + } + params, sdkErr := hub.GetSCParams(ctx, sideChainId) + if err != nil { + return nil, sdkErr + } + res, err := cdc.MarshalJSON(params) + if err != nil { + return nil, sdk.ErrInternal(err.Error()) + } + return res, nil + + default: + return res, sdk.ErrUnknownRequest(req.Path) + } + } +} + +// tolerate the previous RPC api. +func CreateAbciQueryHandler(paramHub *ParamHub) func(sdk.Context, abci.RequestQuery, []string) *abci.ResponseQuery { + return func(ctx sdk.Context, req abci.RequestQuery, path []string) (res *abci.ResponseQuery) { + // expects at least two query path segments. + if path[0] != AbciQueryPrefix || len(path) < 2 { + return nil + } + switch path[1] { + case "fees": + fp := paramHub.GetFeeParams(ctx) + bz, err := paramHub.GetCodeC().MarshalBinaryLengthPrefixed(fp) + if err != nil { + return &abci.ResponseQuery{ + Code: uint32(sdk.CodeInternal), + Log: err.Error(), + } + } + return &abci.ResponseQuery{ + Code: uint32(sdk.ABCICodeOK), + Value: bz, + } + case "sideParams": + if len(req.Data) == 0 { + return &abci.ResponseQuery{ + Code: uint32(sdk.CodeInternal), + Log: "missing side chain id", + } + } + var sideChainId string + err := paramHub.GetCodeC().UnmarshalJSON(req.Data, &sideChainId) + if err != nil { + return &abci.ResponseQuery{ + Code: uint32(sdk.CodeInternal), + Log: fmt.Sprintf("invalid data %v", err), + } + } + params, sdkErr := paramHub.GetSCParams(ctx, sideChainId) + if sdkErr != nil { + return &abci.ResponseQuery{ + Code: uint32(sdkErr.ABCICode()), + Log: sdkErr.ABCILog(), + } + } + bz, err := paramHub.GetCodeC().MarshalJSON(params) + if err != nil { + return &abci.ResponseQuery{ + Code: uint32(sdk.CodeInternal), + Log: err.Error(), + } + } + return &abci.ResponseQuery{ + Code: uint32(sdk.ABCICodeOK), + Value: bz, + } + + default: + return &abci.ResponseQuery{ + Code: uint32(sdk.CodeOK), + Info: fmt.Sprintf( + "Unknown `%s` query path: %v", + AbciQueryPrefix, path), + } + } + } +} diff --git a/x/paramHub/types/errors.go b/x/paramHub/types/errors.go new file mode 100644 index 000000000..64c93dafd --- /dev/null +++ b/x/paramHub/types/errors.go @@ -0,0 +1,28 @@ +// nolint +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type CodeType = sdk.CodeType + +const ( + DefaultCodespace sdk.CodespaceType = 12 + + CodeMissSideChainId CodeType = 101 + CodeInvalidSideChainId CodeType = 102 + CodeInvalidCrossChainPackage CodeType = 103 +) + +func ErrMissSideChainId(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeMissSideChainId, "side chain id is missing") +} + +func ErrInvalidSideChainId(codespace sdk.CodespaceType, msg string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidSideChainId, msg) +} + +func ErrInvalidCrossChainPackage(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidCrossChainPackage, "invalid cross chain package") +} diff --git a/x/paramHub/types/types.go b/x/paramHub/types/types.go new file mode 100644 index 000000000..3b4a4eed7 --- /dev/null +++ b/x/paramHub/types/types.go @@ -0,0 +1,316 @@ +package types + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "math" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/params/subspace" +) + +const ( + OperateFeeType = "operate" + TransferFeeType = "transfer" + DexFeeType = "dex" + + JSONFORMAT = "json" + AMINOFORMAT = "amino" +) + +var ( + // To avoid cycle import , use literal key. Please update here when new type message is introduced. + ValidFixedFeeMsgTypes = map[string]struct{}{ + "submit_proposal": {}, + "deposit": {}, + "vote": {}, + "dexList": {}, + "orderNew": {}, + "orderCancel": {}, + "issueMsg": {}, + "mintMsg": {}, + "tokensBurn": {}, + "setAccountFlags": {}, + "tokensFreeze": {}, + "create_validator": {}, + "remove_validator": {}, + "timeLock": {}, + "timeUnlock": {}, + "timeRelock": {}, + "crossBind": {}, + "crossUnbind": {}, + "crossTransferOut": {}, + "crossBindRelayFee": {}, + "crossUnbindRelayFee": {}, + "crossTransferOutRelayFee": {}, + "oracleClaim": {}, + + "HTLT": {}, + "depositHTLT": {}, + "claimHTLT": {}, + "refundHTLT": {}, + + "side_create_validator": {}, + "side_edit_validator": {}, + "side_delegate": {}, + "side_redelegate": {}, + "side_undelegate": {}, + + "bsc_submit_evidence": {}, + "side_chain_unjail": {}, + + "side_submit_proposal": {}, + "side_deposit": {}, + "side_vote": {}, + "tinyIssueMsg": {}, + "miniIssueMsg": {}, + "miniTokensSetURI": {}, + "dexListMini": {}, + } + + ValidTransferFeeMsgTypes = map[string]struct{}{ + "send": {}, + } +) + +type ParamChangePublisher interface { + SubscribeParamChange(updateCb func(sdk.Context, interface{}), spaceProto *ParamSpaceProto, genesisCb func(sdk.Context, interface{}), loadCb func(sdk.Context, interface{})) +} + +type LastProposalID struct { + ProposalID int64 `json:"proposal_id"` +} + +type GenesisState struct { + FeeGenesis []FeeParam `json:"fees"` +} + +// --------- Definition about fee prams ------------------- // + +type FeeChangeParams struct { + FeeParams []FeeParam `json:"fee_params"` + Description string `json:"description"` +} + +type FeeParam interface { + GetParamType() string + Check() error +} + +var _ FeeParam = MsgFeeParams(nil) + +type MsgFeeParams interface { + FeeParam + GetMsgType() string +} + +var _ MsgFeeParams = (*FixedFeeParams)(nil) + +type FixedFeeParams struct { + MsgType string `json:"msg_type"` + Fee int64 `json:"fee"` + FeeFor sdk.FeeDistributeType `json:"fee_for"` +} + +func (p *FixedFeeParams) GetParamType() string { + return OperateFeeType +} + +func (p *FixedFeeParams) GetMsgType() string { + return p.MsgType +} + +func (p *FixedFeeParams) Check() error { + if p.FeeFor != sdk.FeeForProposer && p.FeeFor != sdk.FeeForAll && p.FeeFor != sdk.FeeFree { + return fmt.Errorf("fee_for %d is invalid", p.FeeFor) + } + if p.Fee < 0 { + return fmt.Errorf("fee(%d) should not be negative", p.Fee) + } + if _, ok := ValidFixedFeeMsgTypes[p.GetMsgType()]; !ok { + return fmt.Errorf("msg type %s can't be fixedFeeParams", p.GetMsgType()) + } + return nil +} + +var _ MsgFeeParams = (*TransferFeeParam)(nil) + +type TransferFeeParam struct { + FixedFeeParams `json:"fixed_fee_params"` + MultiTransferFee int64 `json:"multi_transfer_fee"` + LowerLimitAsMulti int64 `json:"lower_limit_as_multi"` +} + +func (p *TransferFeeParam) GetParamType() string { + return TransferFeeType +} + +func (p *TransferFeeParam) Check() error { + if p.FeeFor != sdk.FeeForProposer && p.FeeFor != sdk.FeeForAll && p.FeeFor != sdk.FeeFree { + return fmt.Errorf("fee_for %d is invalid", p.FeeFor) + } + if p.Fee <= 0 || p.MultiTransferFee <= 0 { + return fmt.Errorf("both fee(%d) and multi_transfer_fee(%d) should be positive", p.Fee, p.MultiTransferFee) + } + if p.MultiTransferFee > p.Fee { + return fmt.Errorf("multi_transfer_fee(%d) should not be bigger than fee(%d)", p.MultiTransferFee, p.Fee) + } + if p.LowerLimitAsMulti <= 1 { + return fmt.Errorf("lower_limit_as_multi should > 1") + } + if _, ok := ValidTransferFeeMsgTypes[p.GetMsgType()]; !ok { + return fmt.Errorf("msg type %s can't be transferFeeParam", p.GetMsgType()) + } + return nil +} + +type DexFeeField struct { + FeeName string `json:"fee_name"` + FeeValue int64 `json:"fee_value"` +} + +type DexFeeParam struct { + DexFeeFields []DexFeeField `json:"dex_fee_fields"` +} + +func (p *DexFeeParam) isNil() bool { + for _, d := range p.DexFeeFields { + if d.FeeValue < 0 { + return true + } + } + return false +} + +func (p *DexFeeParam) GetParamType() string { + return DexFeeType +} + +func (p *DexFeeParam) Check() error { + if p.isNil() { + return fmt.Errorf("Dex fee param is less than 0 ") + } + return nil +} + +func (f *FeeChangeParams) Check() error { + return checkFeeParams(f.FeeParams) +} + +func (f *FeeChangeParams) String() string { + bz, err := json.Marshal(f) + if err != nil { + return "" + } + return string(bz) +} + +func checkFeeParams(fees []FeeParam) error { + numDexFeeParams := 0 + for _, c := range fees { + err := c.Check() + if err != nil { + return err + } + if _, ok := c.(*DexFeeParam); ok { + numDexFeeParams++ + } + } + if numDexFeeParams > 1 { + return fmt.Errorf("have more than one DexFeeParam, actural %d", numDexFeeParams) + } + return nil +} + +// --------- Definition cross side chain prams change ------------------- // +type CSCParamChanges struct { + Changes []CSCParamChange + ChainID string +} + +type CSCParamChange struct { + Key string `json:"key"` // the name of the parameter + Value string `json:"value" rlp:"-"` + Target string `json:"target" rlp:"-"` + + // Since byte slice is not friendly to show in proposal description, omit it. + ValueBytes []byte `json:"-"` // the value of the parameter + TargetBytes []byte `json:"-"` // the address of the target contract +} + +func (c *CSCParamChange) Check() error { + targetBytes, err := hex.DecodeString(c.Target) + if err != nil { + return fmt.Errorf("target is not hex encoded, err %v", err) + } + c.TargetBytes = targetBytes + + valueBytes, err := hex.DecodeString(c.Value) + if err != nil { + return fmt.Errorf("value is not hex encoded, err %v", err) + } + c.ValueBytes = valueBytes + keyBytes := []byte(c.Key) + if len(keyBytes) <= 0 || len(keyBytes) > math.MaxUint8 { + return fmt.Errorf("the length of key exceed the limitation") + } + if len(c.ValueBytes) <= 0 || len(c.ValueBytes) > math.MaxUint8 { + return fmt.Errorf("the length of value exceed the limitation") + } + if len(c.TargetBytes) != sdk.AddrLen { + return fmt.Errorf("the length of target address is not %d", sdk.AddrLen) + } + return nil +} + +// --------- Definition side chain prams change ------------------- // +type ParamSpaceProto struct { + ParamSpace subspace.Subspace + + Proto func() SCParam +} + +type SCParam interface { + subspace.ParamSet + UpdateCheck() error + // native means weather the parameter stored in native store context or side chain store context + //GetParamAttribute() (string, bool) + GetParamAttribute() (string, bool) +} + +type SCChangeParams struct { + SCParams []SCParam `json:"sc_params"` + Description string `json:"description"` +} + +func (s *SCChangeParams) Check() error { + // use literal string to avoid import cycle + supportParams := []string{"slash", "ibc", "oracle", "staking"} + + if len(s.SCParams) != len(supportParams) { + return fmt.Errorf("the sc_params length mismatch, suppose %d", len(supportParams)) + } + + paramSet := make(map[string]bool) + for _, s := range supportParams { + paramSet[s] = true + } + + for _, sc := range s.SCParams { + if sc == nil { + return fmt.Errorf("sc_params contains empty element") + } + err := sc.UpdateCheck() + if err != nil { + return err + } + paramType, _ := sc.GetParamAttribute() + if exist := paramSet[paramType]; exist { + delete(paramSet, paramType) + } else { + return fmt.Errorf("unsupported param type %s", paramType) + } + } + return nil +} diff --git a/x/paramHub/types/types_test.go b/x/paramHub/types/types_test.go new file mode 100644 index 000000000..177d98ccb --- /dev/null +++ b/x/paramHub/types/types_test.go @@ -0,0 +1,197 @@ +package types_test + +import ( + "encoding/hex" + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/ibc" + "github.com/cosmos/cosmos-sdk/x/oracle/types" + fTypes "github.com/cosmos/cosmos-sdk/x/paramHub/types" + "github.com/cosmos/cosmos-sdk/x/slashing" + "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/stretchr/testify/assert" + "github.com/tendermint/go-amino" + "github.com/tendermint/tendermint/libs/common" +) + +var testScParams = `[{"type": "params/StakeParamSet","value": {"unbonding_time": "604800000000000","max_validators": 11,"bond_denom": "BNB","min_self_delegation": "5000000000000","min_delegation_change": "100000000"}},{"type": "params/SlashParamSet","value": {"max_evidence_age": "259200000000000","signed_blocks_window": "0","min_signed_per_window": "0","double_sign_unbond_duration": "9223372036854775807","downtime_unbond_duration": "172800000000000","too_low_del_unbond_duration": "86400000000000","slash_fraction_double_sign": "0","slash_fraction_downtime": "0","double_sign_slash_amount": "1000000000000","downtime_slash_amount": "5000000000","submitter_reward": "100000000000","downtime_slash_fee": "1000000000"}},{"type": "params/OracleParamSet","value": {"ConsensusNeeded": "70000000"}},{"type": "params/IbcParamSet","value": {"relayer_fee": "1000000"}}]` + +func TestFixedFeeParamTypeCheck(t *testing.T) { + testCases := []struct { + fp fTypes.FixedFeeParams + expectError bool + }{ + {fTypes.FixedFeeParams{"send", 0, sdk.FeeForProposer}, true}, + {fTypes.FixedFeeParams{"submit_proposal", 0, sdk.FeeForProposer}, false}, + {fTypes.FixedFeeParams{"remove_validator", 0, 0}, true}, + {fTypes.FixedFeeParams{"tokensBurn", -1, sdk.FeeForProposer}, true}, + {fTypes.FixedFeeParams{"tokensBurn", 100, sdk.FeeForProposer}, false}, + } + for _, testCase := range testCases { + err := testCase.fp.Check() + if testCase.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + } +} + +func TestTransferFeeParamTypeCheck(t *testing.T) { + testCases := []struct { + fp fTypes.TransferFeeParam + expectError bool + }{ + {fTypes.TransferFeeParam{fTypes.FixedFeeParams{"send", 100, sdk.FeeForProposer}, 1, 2}, false}, + {fTypes.TransferFeeParam{fTypes.FixedFeeParams{"wrong type", 100, sdk.FeeForProposer}, 1, 2}, true}, + {fTypes.TransferFeeParam{fTypes.FixedFeeParams{"send", -1, sdk.FeeForProposer}, 1, 2}, true}, + {fTypes.TransferFeeParam{fTypes.FixedFeeParams{"send", 100, sdk.FeeForProposer}, 1, 1}, true}, + } + for _, testCase := range testCases { + err := testCase.fp.Check() + if testCase.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + } +} + +func TestDexFeeParamTypeCheck(t *testing.T) { + testCases := []struct { + fp fTypes.DexFeeParam + expectError bool + }{ + {fTypes.DexFeeParam{[]fTypes.DexFeeField{{"ExpireFee", 1000}}}, false}, + {fTypes.DexFeeParam{[]fTypes.DexFeeField{{"ExpireFee", -1}}}, true}, + } + for _, testCase := range testCases { + err := testCase.fp.Check() + if testCase.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + } +} + +func TestFeeChangeParamsCheck(t *testing.T) { + testCases := []struct { + fp fTypes.FeeChangeParams + expectError bool + }{ + {fTypes.FeeChangeParams{FeeParams: []fTypes.FeeParam{&fTypes.DexFeeParam{[]fTypes.DexFeeField{{"ExpireFee", 1000}}}, &fTypes.TransferFeeParam{fTypes.FixedFeeParams{"send", 100, sdk.FeeForProposer}, 1, 2}}}, false}, + {fTypes.FeeChangeParams{FeeParams: []fTypes.FeeParam{&fTypes.DexFeeParam{[]fTypes.DexFeeField{{"ExpireFee", 1000}}}, &fTypes.FixedFeeParams{"send", 100, sdk.FeeForProposer}}}, true}, + {fTypes.FeeChangeParams{FeeParams: []fTypes.FeeParam{&fTypes.DexFeeParam{[]fTypes.DexFeeField{{"ExpireFee", 1000}}}, &fTypes.DexFeeParam{[]fTypes.DexFeeField{{"ExpireFee", 1000}}}}}, true}, + } + for _, testCase := range testCases { + err := testCase.fp.Check() + if testCase.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + } +} + +func TestCSCParamChangeCheck(t *testing.T) { + type TestCase struct { + cp fTypes.CSCParamChange + expectError bool + } + testcases := make([]TestCase, 0, 100) + for i := 0; i < 100; i++ { + testcases = append(testcases, TestCase{cp: generatCSCParamChange(), expectError: false}) + } + testcases[91].cp.Key = common.RandStr(255) + testcases[92].cp.Value = hex.EncodeToString(common.RandBytes(255)) + + // empty key + testcases[93].cp.Key = "" + testcases[93].expectError = true + //key length exceed 255 + testcases[94].cp.Key = common.RandStr(256) + testcases[94].expectError = true + // empty value + testcases[95].cp.Value = hex.EncodeToString([]byte{}) + testcases[95].expectError = true + //value length exceed 255 + testcases[96].cp.Value = hex.EncodeToString(common.RandBytes(256)) + testcases[96].expectError = true + // empty target + testcases[97].cp.Target = hex.EncodeToString([]byte{}) + testcases[97].expectError = true + //target length not 20 + testcases[98].cp.Target = hex.EncodeToString(common.RandBytes(19)) + testcases[98].expectError = true + //target length not 20 + testcases[99].cp.Target = hex.EncodeToString(common.RandBytes(21)) + testcases[99].expectError = true + + for _, c := range testcases { + err := c.cp.Check() + if c.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + } + +} + +func TestSCParamCheck(t *testing.T) { + type TestCase struct { + cp fTypes.SCChangeParams + expectError bool + } + testcases := []TestCase{ + {cp: generatSCParamChange(&types.Params{ConsensusNeeded: sdk.NewDecWithPrec(7, 1)}, 2), expectError: false}, + {cp: generatSCParamChange(&types.Params{ConsensusNeeded: sdk.NewDecWithPrec(7, 0)}, 2), expectError: true}, + {cp: generatSCParamChange(&types.Params{ConsensusNeeded: sdk.ZeroDec()}, 2), expectError: true}, + {cp: generatSCParamChange(&stake.Params{UnbondingTime: 24 * time.Hour, MaxValidators: 10, BondDenom: "BNB", MinSelfDelegation: 100e8, MinDelegationChange: 1e5}, 0), expectError: false}, + {cp: generatSCParamChange(&stake.Params{UnbondingTime: 24 * time.Hour, MaxValidators: 10, BondDenom: "BNB1", MinSelfDelegation: 100e8, MinDelegationChange: 1e5}, 0), expectError: true}, + {cp: generatSCParamChange(&stake.Params{UnbondingTime: 1 * time.Second, MaxValidators: 10, BondDenom: "BNB", MinSelfDelegation: 100e8, MinDelegationChange: 1e5}, 0), expectError: true}, + {cp: generatSCParamChange(&stake.Params{UnbondingTime: 24 * time.Hour, MaxValidators: 0, BondDenom: "BNB", MinSelfDelegation: 100e8, MinDelegationChange: 1e5}, 0), expectError: true}, + {cp: generatSCParamChange(&stake.Params{UnbondingTime: 24 * time.Hour, MaxValidators: 10, BondDenom: "BNB", MinSelfDelegation: 1e7, MinDelegationChange: 1e5}, 0), expectError: true}, + {cp: fTypes.SCChangeParams{SCParams: []fTypes.SCParam{nil}}, expectError: true}, + {cp: fTypes.SCChangeParams{SCParams: []fTypes.SCParam{}}, expectError: true}, + } + + for _, c := range testcases { + err := c.cp.Check() + if c.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + } + +} + +func generatCSCParamChange() fTypes.CSCParamChange { + return fTypes.CSCParamChange{ + Key: common.RandStr(common.RandIntn(255) + 1), + Value: hex.EncodeToString(common.RandBytes(common.RandIntn(255) + 1)), + Target: hex.EncodeToString(common.RandBytes(20)), + } +} + +func generatSCParamChange(s fTypes.SCParam, idx int) fTypes.SCChangeParams { + iScPrams := make([]fTypes.SCParam, 0) + cdc := amino.NewCodec() + testRegisterWire(cdc) + cdc.UnmarshalJSON([]byte(testScParams), &iScPrams) + iScPrams[idx] = s + return fTypes.SCChangeParams{SCParams: iScPrams, Description: "test"} +} + +// Register concrete types on wire codec +func testRegisterWire(cdc *amino.Codec) { + cdc.RegisterInterface((*fTypes.SCParam)(nil), nil) + cdc.RegisterConcrete(&types.Params{}, "params/OracleParamSet", nil) + cdc.RegisterConcrete(&stake.Params{}, "params/StakeParamSet", nil) + cdc.RegisterConcrete(&slashing.Params{}, "params/SlashParamSet", nil) + cdc.RegisterConcrete(&ibc.Params{}, "params/IbcParamSet", nil) +} diff --git a/x/paramHub/wire.go b/x/paramHub/wire.go new file mode 100644 index 000000000..832ad0742 --- /dev/null +++ b/x/paramHub/wire.go @@ -0,0 +1,17 @@ +package paramHub + +import ( + "github.com/tendermint/go-amino" + + "github.com/cosmos/cosmos-sdk/x/paramHub/types" +) + +// Register concrete types on wire codec +func RegisterWire(cdc *amino.Codec) { + cdc.RegisterInterface((*types.FeeParam)(nil), nil) + cdc.RegisterInterface((*types.MsgFeeParams)(nil), nil) + cdc.RegisterConcrete(&types.FixedFeeParams{}, "params/FixedFeeParams", nil) + cdc.RegisterConcrete(&types.TransferFeeParam{}, "params/TransferFeeParams", nil) + cdc.RegisterConcrete(&types.DexFeeParam{}, "params/DexFeeParam", nil) + cdc.RegisterInterface((*types.SCParam)(nil), nil) +} diff --git a/x/sidechain/chanManage.go b/x/sidechain/chanManage.go new file mode 100644 index 000000000..4f5f1c282 --- /dev/null +++ b/x/sidechain/chanManage.go @@ -0,0 +1,84 @@ +package sidechain + +import ( + "encoding/hex" + "time" + + "github.com/cosmos/cosmos-sdk/bsc/rlp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov" + pTypes "github.com/cosmos/cosmos-sdk/x/paramHub/types" + "github.com/cosmos/cosmos-sdk/x/sidechain/types" +) + +const ( + SafeToleratePeriod = 2 * 7 * 24 * 60 * 60 * time.Second // 2 weeks + + EnableOrDisableChannelKey = "enableOrDisableChannel" +) + +var ( + CrossChainContractAddr, _ = hex.DecodeString("0000000000000000000000000000000000002000") +) + +func (k *Keeper) getLastChanPermissionChanges(ctx sdk.Context) []types.ChanPermissionSetting { + changes := make([]types.ChanPermissionSetting, 0) + // It can still find the valid proposal if the block chain stop for SafeToleratePeriod time + backPeriod := SafeToleratePeriod + gov.MaxVotingPeriod + k.govKeeper.Iterate(ctx, nil, nil, gov.StatusNil, 0, true, func(proposal gov.Proposal) bool { + if proposal.GetProposalType() == gov.ProposalTypeManageChanPermission { + if ctx.BlockHeader().Time.Sub(proposal.GetVotingStartTime()) > backPeriod { + return true + } + if proposal.GetStatus() != gov.StatusPassed { + return false + } + + proposal.SetStatus(gov.StatusExecuted) + k.govKeeper.SetProposal(ctx, proposal) + + var setting types.ChanPermissionSetting + strProposal := proposal.GetDescription() + err := k.cdc.UnmarshalJSON([]byte(strProposal), &setting) + if err != nil { + ctx.Logger().With("module", "side_chain").Error("Get broken data when unmarshal ChanPermissionSetting msg, will skip.", + "proposalId", proposal.GetProposalID(), "err", err) + return false + } + if _, ok := k.cfg.destChainNameToID[setting.SideChainId]; !ok { + ctx.Logger().With("module", "side_chain").Error("The SideChainId do not exist, will skip.", + "proposalId", proposal.GetProposalID(), "setting", setting) + return false + } + if _, ok := k.cfg.channelIDToName[setting.ChannelId]; !ok { + ctx.Logger().With("module", "side_chain").Error("The ChannelId do not exist, will skip.", + "proposalId", proposal.GetProposalID(), "setting", setting) + return false + } + if err := setting.Check(); err != nil { + ctx.Logger().With("module", "side_chain").Error("The ChanPermissionSetting proposal is invalid, will skip.", + "proposalId", proposal.GetProposalID(), "setting", setting, "err", err) + return false + } + changes = append(changes, setting) + } + return false + }) + return changes +} + +func (k *Keeper) SaveChannelSettingChangeToIbc(ctx sdk.Context, sideChainId sdk.ChainID, channelId sdk.ChannelID, permission sdk.ChannelPermission) (seq uint64, sdkErr sdk.Error) { + valueBytes := []byte{byte(channelId), byte(permission)} + + paramChange := pTypes.CSCParamChange{ + Key: EnableOrDisableChannelKey, + ValueBytes: valueBytes, + TargetBytes: CrossChainContractAddr, + } + + bz, err := rlp.EncodeToBytes(¶mChange) + if err != nil { + return 0, sdk.ErrInternal("failed to encode paramChange") + } + return k.ibcKeeper.CreateRawIBCPackageById(ctx, sideChainId, types.GovChannelId, sdk.SynCrossChainPackageType, bz) +} diff --git a/x/sidechain/client/cli/chanManage.go b/x/sidechain/client/cli/chanManage.go new file mode 100644 index 000000000..230d5ba1c --- /dev/null +++ b/x/sidechain/client/cli/chanManage.go @@ -0,0 +1,132 @@ +package cli + +import ( + "errors" + "fmt" + "time" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/tendermint/go-amino" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/utils" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/sidechain/types" +) + +const ( + flagChannelId = "channel-id" + flagChannelEnable = "enable" +) + +func SubmitChannelManageProposalCmd(cdc *codec.Codec) *cobra.Command { + var channelSetting types.ChanPermissionSetting + cmd := &cobra.Command{ + Use: "submit-channel-manage-proposal", + Short: "Submit a cross chain channel management proposal", + RunE: func(cmd *cobra.Command, args []string) error { + txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) + title := viper.GetString(flagTitle) + initialDeposit := viper.GetString(flagDeposit) + votingPeriodInSeconds := viper.GetInt64(flagVotingPeriod) + channelId := viper.GetUint(flagChannelId) + sideChainId := viper.GetString(flagSideChainId) + if sideChainId == "" { + return fmt.Errorf("missing side-chain-id") + } + + channelSetting.ChannelId = sdk.ChannelID(channelId) + channelSetting.SideChainId = sideChainId + enable := viper.GetBool(flagChannelEnable) + if enable { + channelSetting.Permission = sdk.ChannelAllow + } else { + channelSetting.Permission = sdk.ChannelForbidden + } + + err := channelSetting.Check() + if err != nil { + return err + } + fromAddr, err := cliCtx.GetFromAddress() + if err != nil { + return err + } + amount, err := sdk.ParseCoins(initialDeposit) + if err != nil { + return err + } + // cscParam get interface field, use amino + cscParamsBz, err := cdc.MarshalJSON(channelSetting) + if err != nil { + return err + } + + if votingPeriodInSeconds <= 0 { + return errors.New("voting period should be positive") + } + + votingPeriod := time.Duration(votingPeriodInSeconds) * time.Second + if votingPeriod > gov.MaxVotingPeriod { + return fmt.Errorf("voting period should less than %d seconds", gov.MaxVotingPeriod/time.Second) + } + + msg := gov.NewMsgSubmitProposal(title, string(cscParamsBz), gov.ProposalTypeManageChanPermission, fromAddr, amount, votingPeriod) + err = msg.ValidateBasic() + if err != nil { + return err + } + if cliCtx.GenerateOnly { + return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) + } + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) + }, + } + cmd.Flags().Uint8(flagChannelId, 0, "the the channel id that want to manage") + cmd.Flags().Bool(flagChannelEnable, true, "enable the channel or not") + cmd.Flags().String(flagTitle, "", "title of proposal") + cmd.Flags().Int64(flagVotingPeriod, 7*24*60*60, "voting period in seconds") + cmd.Flags().String(flagDeposit, "", "deposit of proposal") + cmd.Flags().String(flagSideChainId, "", "the id of side chain") + return cmd +} +func ShowChannelPermissionCmd(cdc *amino.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "show-channel-permissions", + Short: "Show channel permissions of side chain", + RunE: func(cmd *cobra.Command, args []string) error { + + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) + sideChainId := viper.GetString(flagSideChainId) + if sideChainId == "" { + return fmt.Errorf("missing side-chain-id") + } + + queryData, err := cdc.MarshalJSON(sideChainId) + if err != nil { + return err + } + + bz, err := cliCtx.Query(fmt.Sprintf("custom/sideChain/channelSettings"), queryData) + if err != nil { + return err + } + fmt.Println(string(bz)) + return nil + }, + } + + cmd.Flags().String(flagSideChainId, "", "the id of side chain") + return cmd +} diff --git a/x/sidechain/client/cli/command.go b/x/sidechain/client/cli/command.go new file mode 100644 index 000000000..cef213486 --- /dev/null +++ b/x/sidechain/client/cli/command.go @@ -0,0 +1,30 @@ +package cli + +import ( + "github.com/spf13/cobra" + "github.com/tendermint/go-amino" + + "github.com/cosmos/cosmos-sdk/client" +) + +const ( + flagTitle = "title" + flagDeposit = "deposit" + flagVotingPeriod = "voting-period" + flagSideChainId = "side-chain-id" +) + +func AddCommands(cmd *cobra.Command, cdc *amino.Codec) { + + dexCmd := &cobra.Command{ + Use: "side-chain", + Short: "side chain management commands", + } + dexCmd.AddCommand( + client.PostCommands( + SubmitChannelManageProposalCmd(cdc))...) + dexCmd.AddCommand( + client.GetCommands( + ShowChannelPermissionCmd(cdc))...) + cmd.AddCommand(dexCmd) +} diff --git a/x/sidechain/config.go b/x/sidechain/config.go new file mode 100644 index 000000000..9eab777b2 --- /dev/null +++ b/x/sidechain/config.go @@ -0,0 +1,26 @@ +package sidechain + +import sdk "github.com/cosmos/cosmos-sdk/types" + +type crossChainConfig struct { + srcChainID sdk.ChainID + + nameToChannelID map[string]sdk.ChannelID + channelIDToName map[sdk.ChannelID]string + channelIDToApp map[sdk.ChannelID]sdk.CrossChainApplication + + destChainNameToID map[string]sdk.ChainID + destChainIDToName map[sdk.ChainID]string +} + +func newCrossChainCfg() *crossChainConfig { + config := &crossChainConfig{ + srcChainID: 0, + nameToChannelID: make(map[string]sdk.ChannelID), + channelIDToName: make(map[sdk.ChannelID]string), + destChainNameToID: make(map[string]sdk.ChainID), + destChainIDToName: make(map[sdk.ChainID]string), + channelIDToApp: make(map[sdk.ChannelID]sdk.CrossChainApplication), + } + return config +} diff --git a/x/sidechain/config_test.go b/x/sidechain/config_test.go new file mode 100644 index 000000000..4606d01a7 --- /dev/null +++ b/x/sidechain/config_test.go @@ -0,0 +1,63 @@ +package sidechain + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestInitCrossChainID(t *testing.T) { + sourceChainID := sdk.ChainID(0x0001) + _, keeper := CreateTestInput(t, true) + keeper.SetSrcChainID(sourceChainID) + + require.Equal(t, sourceChainID, keeper.GetSrcChainID()) +} + +func TestRegisterCrossChainChannel(t *testing.T) { + _, keeper := CreateTestInput(t, true) + require.NoError(t, keeper.RegisterChannel("bind", sdk.ChannelID(1), nil)) + require.NoError(t, keeper.RegisterChannel("transfer", sdk.ChannelID(2), nil)) + require.NoError(t, keeper.RegisterChannel("timeout", sdk.ChannelID(3), nil)) + require.NoError(t, keeper.RegisterChannel("staking", sdk.ChannelID(4), nil)) + require.Error(t, keeper.RegisterChannel("staking", sdk.ChannelID(5), nil)) + require.Error(t, keeper.RegisterChannel("staking-new", sdk.ChannelID(4), nil)) + + channeID, err := keeper.GetChannelID("transfer") + require.NoError(t, err) + require.Equal(t, sdk.ChannelID(2), channeID) + + channeID, err = keeper.GetChannelID("staking") + require.NoError(t, err) + require.Equal(t, sdk.ChannelID(4), channeID) +} + +func TestRegisterDestChainID(t *testing.T) { + _, keeper := CreateTestInput(t, true) + require.NoError(t, keeper.RegisterDestChain("bsc", sdk.ChainID(1))) + require.NoError(t, keeper.RegisterDestChain("ethereum", sdk.ChainID(2))) + require.NoError(t, keeper.RegisterDestChain("btc", sdk.ChainID(3))) + require.NoError(t, keeper.RegisterDestChain("cosmos", sdk.ChainID(4))) + require.Error(t, keeper.RegisterDestChain("cosmos", sdk.ChainID(5))) + require.Error(t, keeper.RegisterDestChain("mock", sdk.ChainID(4))) + require.Error(t, keeper.RegisterDestChain("cosmos::", sdk.ChainID(5))) + + destChainID, err := keeper.GetDestChainID("bsc") + require.NoError(t, err) + require.Equal(t, sdk.ChainID(1), destChainID) + + destChainID, err = keeper.GetDestChainID("btc") + require.NoError(t, err) + require.Equal(t, sdk.ChainID(3), destChainID) +} + +func TestCrossChainID(t *testing.T) { + chainID, err := sdk.ParseChainID("123") + require.NoError(t, err) + require.Equal(t, sdk.ChainID(123), chainID) + + _, err = sdk.ParseChainID("65537") + require.Error(t, err) +} diff --git a/x/sidechain/errors.go b/x/sidechain/errors.go new file mode 100644 index 000000000..95f10cb62 --- /dev/null +++ b/x/sidechain/errors.go @@ -0,0 +1,15 @@ +package sidechain + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + DefaultCodespace sdk.CodespaceType = 31 + + CodeInvalidSideChainId sdk.CodeType = 101 +) + +func ErrInvalidSideChainId(codespace sdk.CodespaceType, msg string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidSideChainId, msg) +} diff --git a/x/sidechain/hooks.go b/x/sidechain/hooks.go new file mode 100644 index 000000000..1341c6e85 --- /dev/null +++ b/x/sidechain/hooks.go @@ -0,0 +1,45 @@ +package sidechain + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/sidechain/types" + "github.com/tendermint/go-amino" +) + +//--------------------- ChanPermissionSettingHooks ----------------- +type ChanPermissionSettingHooks struct { + cdc *amino.Codec + k *Keeper +} + +func NewChanPermissionSettingHook(cdc *amino.Codec, keeper *Keeper) ChanPermissionSettingHooks { + return ChanPermissionSettingHooks{cdc, keeper} +} + +var _ gov.GovHooks = ChanPermissionSettingHooks{} + +func (hooks ChanPermissionSettingHooks) OnProposalSubmitted(ctx sdk.Context, proposal gov.Proposal) error { + if proposal.GetProposalType() != gov.ProposalTypeManageChanPermission { + panic(fmt.Sprintf("received wrong type of proposal %x", proposal.GetProposalType())) + } + + var changeParam types.ChanPermissionSetting + strProposal := proposal.GetDescription() + err := hooks.cdc.UnmarshalJSON([]byte(strProposal), &changeParam) + if err != nil { + fmt.Errorf("get broken data when unmarshal ChanPermissionSetting msg. proposalId %d, err %v", proposal.GetProposalID(), err) + } + if err := changeParam.Check(); err != nil { + return err + } + if _, ok := hooks.k.cfg.destChainNameToID[changeParam.SideChainId]; !ok { + return fmt.Errorf("the SideChainId do not exist") + } + if _, ok := hooks.k.cfg.channelIDToName[changeParam.ChannelId]; !ok { + return fmt.Errorf("the ChannelId do not exist") + } + return nil +} diff --git a/x/sidechain/keeper.go b/x/sidechain/keeper.go index 7e031a600..daa25a585 100644 --- a/x/sidechain/keeper.go +++ b/x/sidechain/keeper.go @@ -1,30 +1,57 @@ package sidechain import ( + "encoding/binary" "fmt" + "strings" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/params" ) +var ( + separator = "::" +) + type Keeper struct { storeKey sdk.StoreKey paramspace params.Subspace + cfg *crossChainConfig + cdc *codec.Codec + + govKeeper *gov.Keeper + ibcKeeper IbcKeeper +} + +type IbcKeeper interface { + CreateRawIBCPackageById(ctx sdk.Context, destChainID sdk.ChainID, channelID sdk.ChannelID, + packageType sdk.CrossChainPackageType, packageLoad []byte) (uint64, sdk.Error) } -func NewKeeper(storeKey sdk.StoreKey, paramspace params.Subspace) Keeper { +func NewKeeper(storeKey sdk.StoreKey, paramspace params.Subspace, cdc *codec.Codec) Keeper { return Keeper{ storeKey: storeKey, paramspace: paramspace.WithTypeTable(ParamTypeTable()), + cfg: newCrossChainCfg(), + cdc: cdc, } } -func (k Keeper) PrepareCtxForSideChain(ctx sdk.Context, sideChainId string) (sdk.Context, error) { +func (k *Keeper) SetGovKeeper(govKeeper *gov.Keeper) { + k.govKeeper = govKeeper +} + +func (k *Keeper) SetIbcKeeper(ibcKeeper IbcKeeper) { + k.ibcKeeper = ibcKeeper +} + +func (k *Keeper) PrepareCtxForSideChain(ctx sdk.Context, sideChainId string) (sdk.Context, error) { storePrefix := k.GetSideChainStorePrefix(ctx, sideChainId) if storePrefix == nil { return sdk.Context{}, fmt.Errorf("invalid sideChainId: %s", sideChainId) } - // add store prefix to ctx for side chain use return ctx.WithSideChainKeyPrefix(storePrefix), nil } @@ -43,7 +70,7 @@ func (k Keeper) GetSideChainStorePrefix(ctx sdk.Context, sideChainId string) []b return store.Get(GetSideChainStorePrefixKey(sideChainId)) } -func (k Keeper) GetAllSideChainPrefixes(ctx sdk.Context) ([]string, [][]byte) { +func (k *Keeper) GetAllSideChainPrefixes(ctx sdk.Context) ([]string, [][]byte) { store := ctx.KVStore(k.storeKey) sideChainIds := make([]string, 0, 1) prefixes := make([][]byte, 0, 1) @@ -56,3 +83,162 @@ func (k Keeper) GetAllSideChainPrefixes(ctx sdk.Context) ([]string, [][]byte) { } return sideChainIds, prefixes } + +func (k *Keeper) RegisterChannel(name string, id sdk.ChannelID, app sdk.CrossChainApplication) error { + _, ok := k.cfg.nameToChannelID[name] + if ok { + return fmt.Errorf("duplicated channel name") + } + _, ok = k.cfg.channelIDToName[id] + if ok { + return fmt.Errorf("duplicated channel id") + } + k.cfg.nameToChannelID[name] = id + k.cfg.channelIDToName[id] = name + k.cfg.channelIDToApp[id] = app + return nil +} + +// internally, we use name as the id of the chain, must be unique +func (k *Keeper) RegisterDestChain(name string, chainID sdk.ChainID) error { + if strings.Contains(name, separator) { + return fmt.Errorf("destination chain name should not contains %s", separator) + } + _, ok := k.cfg.destChainNameToID[name] + if ok { + return fmt.Errorf("duplicated destination chain name") + } + _, ok = k.cfg.destChainIDToName[chainID] + if ok { + return fmt.Errorf("duplicated destination chain chainID") + } + k.cfg.destChainNameToID[name] = chainID + k.cfg.destChainIDToName[chainID] = name + return nil +} + +func (k *Keeper) SetChannelSendPermission(ctx sdk.Context, destChainID sdk.ChainID, channelID sdk.ChannelID, permission sdk.ChannelPermission) { + kvStore := ctx.KVStore(k.storeKey) + kvStore.Set(buildChannelPermissionKey(destChainID, channelID), []byte{byte(permission)}) +} + +func (k *Keeper) GetChannelSendPermission(ctx sdk.Context, destChainID sdk.ChainID, channelID sdk.ChannelID) sdk.ChannelPermission { + kvStore := ctx.KVStore(k.storeKey) + bz := kvStore.Get(buildChannelPermissionKey(destChainID, channelID)) + if bz == nil { + return sdk.ChannelForbidden + } + return sdk.ChannelPermission(bz[0]) +} + +func (k *Keeper) GetChannelSendPermissions(ctx sdk.Context, destChainID sdk.ChainID) map[sdk.ChannelID]sdk.ChannelPermission { + kvStore := ctx.KVStore(k.storeKey).Prefix(buildChannelPermissionsPrefixKey(destChainID)) + ite := kvStore.Iterator(nil, nil) + defer ite.Close() + permissions := make(map[sdk.ChannelID]sdk.ChannelPermission, 0) + for ; ite.Valid(); ite.Next() { + key := ite.Key() + if len(key) < 1 { + continue + } + channelId := sdk.ChannelID(key[0]) + value := ite.Value() + permissions[channelId] = sdk.ChannelPermission(value[0]) + } + return permissions +} + +func (k *Keeper) GetChannelID(channelName string) (sdk.ChannelID, error) { + id, ok := k.cfg.nameToChannelID[channelName] + if !ok { + return sdk.ChannelID(0), fmt.Errorf("non-existing channel") + } + return id, nil +} + +func (k *Keeper) SetSrcChainID(srcChainID sdk.ChainID) { + k.cfg.srcChainID = srcChainID +} + +func (k *Keeper) GetSrcChainID() sdk.ChainID { + return k.cfg.srcChainID +} + +func (k *Keeper) GetDestChainID(name string) (sdk.ChainID, error) { + destChainID, exist := k.cfg.destChainNameToID[name] + if !exist { + return sdk.ChainID(0), fmt.Errorf("non-existing destination chainName ") + } + return destChainID, nil +} + +func (k *Keeper) GetDestChainName(id sdk.ChainID) (string, error) { + destChainName, exist := k.cfg.destChainIDToName[id] + if !exist { + return "", fmt.Errorf("non-existing destination chainID") + } + return destChainName, nil +} + +func (k *Keeper) GetSendSequence(ctx sdk.Context, destChainID sdk.ChainID, channelID sdk.ChannelID) uint64 { + return k.getSequence(ctx, destChainID, channelID, PrefixForSendSequenceKey) +} + +func (k *Keeper) IncrSendSequence(ctx sdk.Context, destChainID sdk.ChainID, channelID sdk.ChannelID) { + k.incrSequence(ctx, destChainID, channelID, PrefixForSendSequenceKey) +} + +func (k *Keeper) GetReceiveSequence(ctx sdk.Context, destChainID sdk.ChainID, channelID sdk.ChannelID) uint64 { + return k.getSequence(ctx, destChainID, channelID, PrefixForReceiveSequenceKey) +} + +func (k *Keeper) IncrReceiveSequence(ctx sdk.Context, destChainID sdk.ChainID, channelID sdk.ChannelID) { + k.incrSequence(ctx, destChainID, channelID, PrefixForReceiveSequenceKey) +} + +func (k *Keeper) GetCrossChainApp(ctx sdk.Context, channelID sdk.ChannelID) sdk.CrossChainApplication { + return k.cfg.channelIDToApp[channelID] +} + +func (k *Keeper) getSequence(ctx sdk.Context, destChainID sdk.ChainID, channelID sdk.ChannelID, prefix []byte) uint64 { + kvStore := ctx.KVStore(k.storeKey) + bz := kvStore.Get(buildChannelSequenceKey(destChainID, channelID, prefix)) + if bz == nil { + return 0 + } + return binary.BigEndian.Uint64(bz) +} + +func (k *Keeper) incrSequence(ctx sdk.Context, destChainID sdk.ChainID, channelID sdk.ChannelID, prefix []byte) { + var sequence uint64 + kvStore := ctx.KVStore(k.storeKey) + bz := kvStore.Get(buildChannelSequenceKey(destChainID, channelID, prefix)) + if bz == nil { + sequence = 0 + } else { + sequence = binary.BigEndian.Uint64(bz) + } + + sequenceBytes := make([]byte, sequenceLength) + binary.BigEndian.PutUint64(sequenceBytes, sequence+1) + kvStore.Set(buildChannelSequenceKey(destChainID, channelID, prefix), sequenceBytes) +} + +func EndBlock(ctx sdk.Context, k Keeper) { + if sdk.IsUpgrade(sdk.LaunchBscUpgrade) && k.govKeeper != nil { + chanPermissions := k.getLastChanPermissionChanges(ctx) + // should in reverse order + for j := len(chanPermissions) - 1; j >= 0; j-- { + change := chanPermissions[j] + // must exist + id, _ := k.cfg.destChainNameToID[change.SideChainId] + k.SetChannelSendPermission(ctx, id, change.ChannelId, change.Permission) + _, err := k.SaveChannelSettingChangeToIbc(ctx, id, change.ChannelId, change.Permission) + if err != nil { + ctx.Logger().With("module", "side_chain").Error("failed to write cross chain channel permission change message ", + "err", err) + } + } + } + return +} diff --git a/x/sidechain/keeper_test.go b/x/sidechain/keeper_test.go index 8c6c293ce..b267acccf 100644 --- a/x/sidechain/keeper_test.go +++ b/x/sidechain/keeper_test.go @@ -36,7 +36,7 @@ func CreateTestInput(t *testing.T, isCheckTx bool) (sdk.Context, Keeper) { mode = sdk.RunTxModeCheck } ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, mode, log.NewNopLogger()) - k := NewKeeper(key, paramsKeeper.Subspace(DefaultParamspace)) + k := NewKeeper(key, paramsKeeper.Subspace(DefaultParamspace), cdc) k.SetParams(ctx, DefaultParams()) return ctx, k } diff --git a/x/sidechain/key.go b/x/sidechain/key.go index 7cc41f5f0..b58e8ed3d 100644 --- a/x/sidechain/key.go +++ b/x/sidechain/key.go @@ -1,9 +1,53 @@ package sidechain +import ( + "encoding/binary" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + prefixLength = 1 + destChainIDLength = 2 + channelIDLength = 1 + sequenceLength = 8 +) + var ( SideChainStorePrefixByIdKey = []byte{0x01} // prefix for each key to a side chain store prefix, by side chain id + + PrefixForSendSequenceKey = []byte{0xf0} + PrefixForReceiveSequenceKey = []byte{0xf1} + + PrefixForChannelPermissionKey = []byte{0xc0} ) func GetSideChainStorePrefixKey(sideChainId string) []byte { return append(SideChainStorePrefixByIdKey, []byte(sideChainId)...) } + +func buildChannelSequenceKey(destChainID sdk.ChainID, channelID sdk.ChannelID, prefix []byte) []byte { + key := make([]byte, prefixLength+destChainIDLength+channelIDLength) + + copy(key[:prefixLength], prefix) + binary.BigEndian.PutUint16(key[prefixLength:prefixLength+destChainIDLength], uint16(destChainID)) + copy(key[prefixLength+destChainIDLength:], []byte{byte(channelID)}) + return key +} + +func buildChannelPermissionKey(destChainID sdk.ChainID, channelID sdk.ChannelID) []byte { + key := make([]byte, prefixLength+destChainIDLength+channelIDLength) + + copy(key[:prefixLength], PrefixForChannelPermissionKey) + binary.BigEndian.PutUint16(key[prefixLength:prefixLength+destChainIDLength], uint16(destChainID)) + copy(key[prefixLength+destChainIDLength:], []byte{byte(channelID)}) + return key +} + +func buildChannelPermissionsPrefixKey(destChainID sdk.ChainID) []byte { + key := make([]byte, prefixLength+destChainIDLength) + + copy(key[:prefixLength], PrefixForChannelPermissionKey) + binary.BigEndian.PutUint16(key[prefixLength:prefixLength+destChainIDLength], uint16(destChainID)) + return key +} diff --git a/x/sidechain/queryable.go b/x/sidechain/queryable.go new file mode 100644 index 000000000..c7b444659 --- /dev/null +++ b/x/sidechain/queryable.go @@ -0,0 +1,46 @@ +package sidechain + +import ( + "encoding/json" + sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +const ( + QuerychannelSettings = "channelSettings" +) + +// creates a querier for staking REST endpoints +func NewQuerier(k Keeper) sdk.Querier { + return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) { + switch path[0] { + case QuerychannelSettings: + var sideChainId string + err := k.cdc.UnmarshalJSON(req.Data, &sideChainId) + if err != nil { + return nil, ErrInvalidSideChainId(DefaultCodespace, err.Error()) + } + if len(sideChainId) == 0 { + return nil, ErrInvalidSideChainId(DefaultCodespace, "SideChainId is missing") + } + return queryChannelSettings(ctx, k, sideChainId) + default: + return nil, sdk.ErrUnknownRequest("unknown side chain query endpoint") + } + } +} + +func queryChannelSettings(ctx sdk.Context, k Keeper, sideChainId string) ([]byte, sdk.Error) { + id, err := k.GetDestChainID(sideChainId) + if err != nil { + return nil, ErrInvalidSideChainId(DefaultCodespace, err.Error()) + } + permissionMap := k.GetChannelSendPermissions(ctx, id) + + res, resErr := json.Marshal(permissionMap) + if resErr != nil { + return res, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", resErr.Error())) + } + + return res, nil +} diff --git a/x/sidechain/types/types.go b/x/sidechain/types/types.go new file mode 100644 index 000000000..4d819eba3 --- /dev/null +++ b/x/sidechain/types/types.go @@ -0,0 +1,70 @@ +package types + +import ( + "fmt" + "math/big" + + "github.com/cosmos/cosmos-sdk/bsc/rlp" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + MaxSideChainIdLength = 20 + + GovChannelId = sdk.ChannelID(9) +) + +const ( + CrossChainFeeLength = 32 + PackageTypeLength = 1 + PackageHeaderLength = CrossChainFeeLength + PackageTypeLength +) + +func EncodePackageHeader(packageType sdk.CrossChainPackageType, relayerFee big.Int) []byte { + packageHeader := make([]byte, PackageHeaderLength) + packageHeader[0] = uint8(packageType) + length := len(relayerFee.Bytes()) + copy(packageHeader[PackageHeaderLength-length:PackageHeaderLength], relayerFee.Bytes()) + return packageHeader +} + +func DecodePackageHeader(packageHeader []byte) (packageType sdk.CrossChainPackageType, relayFee big.Int, err error) { + if len(packageHeader) < PackageHeaderLength { + err = fmt.Errorf("length of packageHeader is less than %d", PackageHeaderLength) + return + } + packageType = sdk.CrossChainPackageType(packageHeader[0]) + relayFee.SetBytes(packageHeader[PackageTypeLength : CrossChainFeeLength+PackageTypeLength]) + return +} + +type CommonAckPackage struct { + Code uint32 +} + +func (p CommonAckPackage) IsOk() bool { + return p.Code == 0 +} + +func GenCommonAckPackage(code uint32) ([]byte, error) { + return rlp.EncodeToBytes(&CommonAckPackage{Code: code}) +} + +type ChanPermissionSetting struct { + SideChainId string `json:"side_chain_id"` + ChannelId sdk.ChannelID `json:"channel_id"` + Permission sdk.ChannelPermission `json:"permission"` +} + +func (c *ChanPermissionSetting) Check() error { + if len(c.SideChainId) == 0 || len(c.SideChainId) > MaxSideChainIdLength { + return fmt.Errorf("invalid side chain id") + } + if c.ChannelId == GovChannelId { + return fmt.Errorf("gov channel id is forbidden to set") + } + if c.Permission != sdk.ChannelAllow && c.Permission != sdk.ChannelForbidden { + return fmt.Errorf("permission %d is invalid", c.Permission) + } + return nil +} diff --git a/x/slashing/app_test.go b/x/slashing/app_test.go index 4df1b707b..b2ac87794 100644 --- a/x/slashing/app_test.go +++ b/x/slashing/app_test.go @@ -36,10 +36,10 @@ func getMockApp(t *testing.T) (*mock.App, stake.Keeper, Keeper) { keyParams := sdk.NewKVStoreKey("params") tkeyParams := sdk.NewTransientStoreKey("transient_params") bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper) - ibcKeeper := ibc.NewKeeper(keyIbc, ibc.DefaultCodespace) paramsKeeper := params.NewKeeper(mapp.Cdc, keyParams, tkeyParams) - scKeeper := sidechain.NewKeeper(keySideChain, paramsKeeper.Subspace(sidechain.DefaultParamspace)) + scKeeper := sidechain.NewKeeper(keySideChain, paramsKeeper.Subspace(sidechain.DefaultParamspace), mapp.Cdc) + ibcKeeper := ibc.NewKeeper(keyIbc, paramsKeeper.Subspace(ibc.DefaultParamspace), ibc.DefaultCodespace, scKeeper) stakeKeeper := stake.NewKeeper(mapp.Cdc, keyStake, tkeyStake, bankKeeper, nil, paramsKeeper.Subspace(stake.DefaultParamspace), mapp.RegisterCodespace(stake.DefaultCodespace)) stakeKeeper.SetupForSideChain(&scKeeper, &ibcKeeper) keeper := NewKeeper(mapp.Cdc, keySlashing, stakeKeeper, paramsKeeper.Subspace(DefaultParamspace), mapp.RegisterCodespace(DefaultCodespace), bankKeeper) diff --git a/x/slashing/claim.go b/x/slashing/claim.go deleted file mode 100644 index 23f416644..000000000 --- a/x/slashing/claim.go +++ /dev/null @@ -1,128 +0,0 @@ -package slashing - -import ( - "encoding/json" - "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/fees" -) - -const ( - ClaimTypeDowntimeSlash sdk.ClaimType = 0x5 - - ClaimNameDowntimeSlash = "DowntimeSlash" -) - -type ClaimHooks struct { - k Keeper -} - -// Return the wrapper struct -func (k Keeper) ClaimHooks() ClaimHooks { - return ClaimHooks{k} -} - -var _ sdk.ClaimHooks = ClaimHooks{} - -type SideDowntimeSlashClaim struct { - SideConsAddr []byte `json:"side_cons_addr"` - SideHeight int64 `json:"side_height"` - SideChainId string `json:"side_chain_id"` - SideTimestamp int64 `json:"side_timestamp"` -} - -// implement Claim hooks -func (h ClaimHooks) CheckClaim(ctx sdk.Context, claim string) sdk.Error { - var slashClaim SideDowntimeSlashClaim - err := json.Unmarshal([]byte(claim), &slashClaim) - if err != nil { - return ErrInvalidClaim(h.k.Codespace, fmt.Sprintf("unmarshal side downtime slash claim error, claim=%s", claim)) - } - - if len(slashClaim.SideConsAddr) != sdk.AddrLen { - return ErrInvalidClaim(h.k.Codespace, fmt.Sprintf("wrong sideConsAddr length, expected=%d", slashClaim.SideConsAddr)) - } - - if slashClaim.SideHeight <= 0 { - return ErrInvalidClaim(h.k.Codespace, "side height must be positive") - } - - if slashClaim.SideTimestamp <= 0 { - return ErrInvalidClaim(h.k.Codespace, "invalid side timestamp") - } - return nil -} - -func (h ClaimHooks) ExecuteClaim(ctx sdk.Context, finalClaim string) (sdk.ClaimResult, sdk.Error) { - var slashClaim SideDowntimeSlashClaim - err := json.Unmarshal([]byte(finalClaim), &slashClaim) - if err != nil { - return sdk.ClaimResult{}, ErrInvalidClaim(h.k.Codespace, fmt.Sprintf("unmarshal side downtime slash claim error, claim=%s", finalClaim)) - } - - sideCtx, err := h.k.ScKeeper.PrepareCtxForSideChain(ctx, slashClaim.SideChainId) - if err != nil { - return sdk.ClaimResult{}, ErrInvalidSideChainId(DefaultCodespace) - } - - header := sideCtx.BlockHeader() - age := header.Time.Unix() - slashClaim.SideTimestamp - if age > int64(h.k.MaxEvidenceAge(sideCtx).Seconds()) { - return sdk.ClaimResult{ - Code: int(CodeExpiredEvidence), - Msg: "The given evidences are expired", - }, nil - } - - if h.k.hasSlashRecord(sideCtx, slashClaim.SideConsAddr, Downtime, slashClaim.SideHeight) { - return sdk.ClaimResult{}, ErrDuplicateDowntimeClaim(h.k.Codespace) - } - - slashAmt := h.k.DowntimeSlashAmount(sideCtx) - slashedAmt, err := h.k.validatorSet.SlashSideChain(ctx, slashClaim.SideChainId, slashClaim.SideConsAddr, sdk.NewDec(slashAmt)) - if err != nil { - return sdk.ClaimResult{}, ErrFailedToSlash(h.k.Codespace, err.Error()) - } - - downtimeClaimFee := h.k.DowntimeSlashFee(sideCtx) - downtimeClaimFeeReal := sdk.MinInt64(downtimeClaimFee, slashedAmt.RawInt()) - bondDenom := h.k.validatorSet.BondDenom(sideCtx) - if downtimeClaimFeeReal > 0 && ctx.IsDeliverTx() { - feeCoinAdd := sdk.NewCoin(bondDenom, downtimeClaimFeeReal) - fees.Pool.AddAndCommitFee("side_downtime_slash", sdk.NewFee(sdk.Coins{feeCoinAdd}, sdk.FeeForAll)) - } - - remaining := slashedAmt.RawInt() - downtimeClaimFeeReal - if remaining > 0 { - found, err := h.k.validatorSet.AllocateSlashAmtToValidators(sideCtx, slashClaim.SideConsAddr, sdk.NewDec(remaining)) - if err != nil { - return sdk.ClaimResult{}, ErrFailedToSlash(h.k.Codespace, err.Error()) - } - if !found && ctx.IsDeliverTx() { - remainingCoin := sdk.NewCoin(bondDenom, remaining) - fees.Pool.AddAndCommitFee("side_downtime_slash_remaining", sdk.NewFee(sdk.Coins{remainingCoin}, sdk.FeeForAll)) - } - } - - jailUtil := header.Time.Add(h.k.DowntimeUnbondDuration(sideCtx)) - sr := SlashRecord{ - ConsAddr: slashClaim.SideConsAddr, - InfractionType: Downtime, - InfractionHeight: slashClaim.SideHeight, - SlashHeight: header.Height, - JailUntil: jailUtil, - SlashAmt: slashedAmt.RawInt(), - SideChainId: slashClaim.SideChainId, - } - h.k.setSlashRecord(sideCtx, sr) - - // Set or updated validator jail duration - signInfo, found := h.k.getValidatorSigningInfo(sideCtx, slashClaim.SideConsAddr) - if !found { - return sdk.ClaimResult{}, sdk.ErrInternal(fmt.Sprintf("Expected signing info for validator %s but not found", sdk.HexEncode(slashClaim.SideConsAddr))) - } - signInfo.JailedUntil = jailUtil - h.k.setValidatorSigningInfo(sideCtx, slashClaim.SideConsAddr, signInfo) - - return sdk.ClaimResult{}, nil -} diff --git a/x/slashing/client/cli/query_sidechain.go b/x/slashing/client/cli/query_sidechain.go index 681658323..dadd02eb6 100644 --- a/x/slashing/client/cli/query_sidechain.go +++ b/x/slashing/client/cli/query_sidechain.go @@ -143,7 +143,7 @@ func GetCmdQuerySideChainSlashRecord(storeName string, cdc *codec.Codec) *cobra. if err != nil { return err } - height := viper.GetInt64(FlagInfractionHeight) + height := viper.GetUint64(FlagInfractionHeight) key := append(sideChainStorePrefix, slashing.GetSlashRecordKey(sideConsAddr, resType, height)...) res, err := cliCtx.QueryStore(key, storeName) diff --git a/x/slashing/client/cli/tx_sidechain.go b/x/slashing/client/cli/tx_sidechain.go index bbc193702..a5d10aa8c 100644 --- a/x/slashing/client/cli/tx_sidechain.go +++ b/x/slashing/client/cli/tx_sidechain.go @@ -6,6 +6,7 @@ import ( "fmt" "io/ioutil" + "github.com/cosmos/cosmos-sdk/bsc" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" @@ -13,7 +14,6 @@ import ( authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" "github.com/cosmos/cosmos-sdk/x/slashing" - "github.com/cosmos/cosmos-sdk/x/slashing/bsc" "github.com/spf13/cobra" "github.com/spf13/viper" ) diff --git a/x/slashing/client/rest/tx.go b/x/slashing/client/rest/tx.go index b4946c001..d6db40f6d 100644 --- a/x/slashing/client/rest/tx.go +++ b/x/slashing/client/rest/tx.go @@ -6,6 +6,9 @@ import ( "io/ioutil" "net/http" + "github.com/gorilla/mux" + + "github.com/cosmos/cosmos-sdk/bsc" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" @@ -13,8 +16,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" "github.com/cosmos/cosmos-sdk/x/slashing" - "github.com/cosmos/cosmos-sdk/x/slashing/bsc" - "github.com/gorilla/mux" ) func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec, kb keys.Keybase) { diff --git a/x/slashing/codec.go b/x/slashing/codec.go index 771f9d754..436f7799b 100644 --- a/x/slashing/codec.go +++ b/x/slashing/codec.go @@ -9,6 +9,7 @@ func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(MsgUnjail{}, "cosmos-sdk/MsgUnjail", nil) cdc.RegisterConcrete(MsgSideChainUnjail{}, "cosmos-sdk/MsgSideChainUnjail", nil) cdc.RegisterConcrete(MsgBscSubmitEvidence{}, "cosmos-sdk/MsgBscSubmitEvidence", nil) + cdc.RegisterConcrete(&Params{}, "params/SlashParamSet", nil) } // generic sealed codec to be used throughout sdk diff --git a/x/slashing/claim_test.go b/x/slashing/execute_test.go similarity index 67% rename from x/slashing/claim_test.go rename to x/slashing/execute_test.go index d2eedb79e..e84e01753 100644 --- a/x/slashing/claim_test.go +++ b/x/slashing/execute_test.go @@ -1,15 +1,16 @@ package slashing import ( - "encoding/json" - "github.com/cosmos/cosmos-sdk/types/fees" + "github.com/cosmos/cosmos-sdk/bsc/rlp" "testing" "time" + "github.com/stretchr/testify/require" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/fees" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/stake" - "github.com/stretchr/testify/require" ) func TestSideChainSlashDowntime(t *testing.T) { @@ -17,7 +18,6 @@ func TestSideChainSlashDowntime(t *testing.T) { slashingParams := DefaultParams() slashingParams.MaxEvidenceAge = 12 * 60 * 60 * time.Second ctx, sideCtx, _, stakeKeeper, _, keeper := createSideTestInput(t, slashingParams) - hooks := keeper.ClaimHooks() // create a validator bondAmount := int64(10000e8) @@ -30,24 +30,19 @@ func TestSideChainSlashDowntime(t *testing.T) { // end block stake.EndBreatheBlock(ctx, stakeKeeper) - sideHeight := int64(100) + sideHeight := uint64(100) sideChainId := "bsc" sideTimestamp := ctx.BlockHeader().Time.Add(-6 * 60 * 60 * time.Second) - claim := SideDowntimeSlashClaim{ + claim := SideDowntimeSlashPackage{ SideConsAddr: sideConsAddr, SideHeight: sideHeight, - SideChainId: sideChainId, - SideTimestamp: sideTimestamp.Unix(), + SideChainId: sdk.ChainID(1), + SideTimestamp: uint64(sideTimestamp.Unix()), } - jsonClaim, err := json.Marshal(claim) - require.Nil(t, err) - - sdkErr := hooks.CheckClaim(ctx, string(jsonClaim)) - require.Nil(t, sdkErr) + result := keeper.executeSynPackage(ctx, &claim) - _, sdkErr = hooks.ExecuteClaim(ctx, string(jsonClaim)) - require.Nil(t, sdkErr, "Expected nil, but got : %v", sdkErr) + require.Nil(t, result, "Expected nil, but got : %v", result) info, found := keeper.getValidatorSigningInfo(sideCtx, sideConsAddr) require.True(t, found) @@ -70,56 +65,43 @@ func TestSideChainSlashDowntime(t *testing.T) { require.True(t, found) require.EqualValues(t, bondAmount-realSlashAmt, delegation.Shares.RawInt()) - _, sdkErr = hooks.ExecuteClaim(ctx, string(jsonClaim)) - require.NotNil(t, sdkErr) - require.EqualValues(t, CodeDuplicateDowntimeClaim, sdkErr.Code()) + result = keeper.executeSynPackage(ctx, &claim) + require.NotNil(t, result) + require.EqualValues(t, CodeDuplicateDowntimeClaim, result.Code()) - sdkErr = hooks.CheckClaim(ctx, "") - require.NotNil(t, sdkErr) + exeResult := keeper.ExecuteSynPackage(ctx, []byte("")) + require.NotNil(t, exeResult.Err) claim.SideHeight = 0 - jsonClaim, err = json.Marshal(claim) - require.Nil(t, err) - sdkErr = hooks.CheckClaim(ctx, string(jsonClaim)) - require.NotNil(t, sdkErr) + bz, _ := rlp.EncodeToBytes(&claim) + _, result = keeper.checkAndParseSynPackage(bz) + require.NotNil(t, result) claim.SideHeight = sideHeight claim.SideConsAddr = createSideAddr(21) - jsonClaim, err = json.Marshal(claim) - require.Nil(t, err) - sdkErr = hooks.CheckClaim(ctx, string(jsonClaim)) - require.NotNil(t, sdkErr) + + result = keeper.executeSynPackage(ctx, &claim) + require.NotNil(t, result) claim.SideConsAddr = sideConsAddr - claim.SideTimestamp = ctx.BlockHeader().Time.Add(-24 * 60 * 60 * time.Second).Unix() - jsonClaim, err = json.Marshal(claim) - require.Nil(t, err) - sdkErr = hooks.CheckClaim(ctx, string(jsonClaim)) - require.Nil(t, sdkErr) - claimResult, sdkErr := hooks.ExecuteClaim(ctx, string(jsonClaim)) - require.Nil(t, sdkErr) - require.EqualValues(t, CodeExpiredEvidence, claimResult.Code, "Expected got 201 err code, but got err: %v", sdkErr) - - claim.SideTimestamp = ctx.BlockHeader().Time.Add(-6 * 60 * 60 * time.Second).Unix() + claim.SideTimestamp = uint64(ctx.BlockHeader().Time.Add(-24 * 60 * 60 * time.Second).Unix()) + result = keeper.executeSynPackage(ctx, &claim) + require.EqualValues(t, CodeExpiredEvidence, result.Code(), "Expected got 201 err code, but got err: %v", result) + + claim.SideTimestamp = uint64(ctx.BlockHeader().Time.Add(-6 * 60 * 60 * time.Second).Unix()) claim.SideConsAddr = sideConsAddr - claim.SideChainId = "bcc" - jsonClaim, err = json.Marshal(claim) - require.Nil(t, err) - sdkErr = hooks.CheckClaim(ctx, string(jsonClaim)) - require.Nil(t, sdkErr) - _, sdkErr = hooks.ExecuteClaim(ctx, string(jsonClaim)) - require.NotNil(t, sdkErr, "Expected get err, but got nil") - require.EqualValues(t, CodeInvalidSideChain, sdkErr.Code(), "Expected got 205 error code, but got err: %v", sdkErr) + claim.SideChainId = sdk.ChainID(2) + + result = keeper.executeSynPackage(ctx, &claim) + require.NotNil(t, result, "Expected get err, but got nil") + require.EqualValues(t, CodeInvalidSideChain, result.Code(), "Expected got 205 error code, but got err: %v", result) claim.SideHeight = sideHeight claim.SideConsAddr = createSideAddr(20) - claim.SideChainId = sideChainId - jsonClaim, err = json.Marshal(claim) - require.Nil(t, err) - sdkErr = hooks.CheckClaim(ctx, string(jsonClaim)) - require.Nil(t, sdkErr) - _, sdkErr = hooks.ExecuteClaim(ctx, string(jsonClaim)) - require.NotNil(t, sdkErr, "Expected got err of no signing info found, but got nil") + claim.SideChainId = sdk.ChainID(1) + + result = keeper.executeSynPackage(ctx, &claim) + require.NotNil(t, result, "Expected got err of no signing info found, but got nil") } @@ -130,7 +112,6 @@ func TestSlashDowntimeBalanceVerify(t *testing.T) { slashingParams.DowntimeSlashAmount = 8000e8 slashingParams.DowntimeSlashFee = 5000e8 ctx, sideCtx, bk, stakeKeeper, _, keeper := createSideTestInput(t, slashingParams) - hooks := keeper.ClaimHooks() bondAmount := int64(10000e8) // create validator to be allocated slashed amount further @@ -155,23 +136,18 @@ func TestSlashDowntimeBalanceVerify(t *testing.T) { // end block stake.EndBreatheBlock(ctx, stakeKeeper) - sideHeight := int64(50) - sideChainId := "bsc" + sideHeight := uint64(50) sideTimestamp := ctx.BlockHeader().Time.Add(-6 * 60 * 60 * time.Second) - claim := SideDowntimeSlashClaim{ + claim := SideDowntimeSlashPackage{ SideConsAddr: sideConsAddr2, SideHeight: sideHeight, - SideChainId: sideChainId, - SideTimestamp: sideTimestamp.Unix(), + SideChainId: sdk.ChainID(1), + SideTimestamp: uint64(sideTimestamp.Unix()), } - jsonClaim, err := json.Marshal(claim) - require.Nil(t, err) - sdkErr := hooks.CheckClaim(ctx, string(jsonClaim)) - require.Nil(t, sdkErr) feesInPoolBefore := fees.Pool.BlockFees().Tokens.AmountOf("steak") - _, sdkErr = hooks.ExecuteClaim(ctx, string(jsonClaim)) - require.Nil(t, sdkErr) + result := keeper.executeSynPackage(ctx, &claim) + require.Nil(t, result) validator2, found := stakeKeeper.GetValidator(sideCtx, valAddr2) require.True(t, found) @@ -188,21 +164,17 @@ func TestSlashDowntimeBalanceVerify(t *testing.T) { coins := bk.GetCoins(ctx, distributionAddr) require.EqualValues(t, 3000e8, coins.AmountOf("steak")) // remaining amount(3000e8) allocated to - sideHeight = int64(80) - sideChainId = "bsc" + sideHeight = uint64(80) sideTimestamp = ctx.BlockHeader().Time.Add(-3 * 60 * 60 * time.Second) - claim = SideDowntimeSlashClaim{ + claim = SideDowntimeSlashPackage{ SideConsAddr: sideConsAddr2, SideHeight: sideHeight, - SideChainId: sideChainId, - SideTimestamp: sideTimestamp.Unix(), + SideChainId: sdk.ChainID(1), + SideTimestamp: uint64(sideTimestamp.Unix()), } - jsonClaim, err = json.Marshal(claim) - require.Nil(t, err) - sdkErr = hooks.CheckClaim(ctx, string(jsonClaim)) - require.Nil(t, sdkErr) - _, sdkErr = hooks.ExecuteClaim(ctx, string(jsonClaim)) - require.Nil(t, sdkErr) + + result = keeper.executeSynPackage(ctx, &claim) + require.Nil(t, result) validator2, found = stakeKeeper.GetValidator(sideCtx, valAddr2) require.True(t, found) diff --git a/x/slashing/handler_sidechain.go b/x/slashing/handler_sidechain.go index b2493053a..28bb427b9 100644 --- a/x/slashing/handler_sidechain.go +++ b/x/slashing/handler_sidechain.go @@ -29,7 +29,7 @@ func handleMsgBscSubmitEvidence(ctx sdk.Context, msg MsgBscSubmitEvidence, k Kee return ErrInvalidEvidence(DefaultCodespace, "The signers of two block headers are not the same").Result() } - if k.hasSlashRecord(sideCtx, sideConsAddr.Bytes(), DoubleSign, msg.Headers[0].Number) { + if k.hasSlashRecord(sideCtx, sideConsAddr.Bytes(), DoubleSign, uint64(msg.Headers[0].Number)) { return ErrEvidenceHasBeenHandled(k.Codespace).Result() } @@ -77,7 +77,7 @@ func handleMsgBscSubmitEvidence(ctx sdk.Context, msg MsgBscSubmitEvidence, k Kee sr := SlashRecord{ ConsAddr: sideConsAddr.Bytes(), InfractionType: DoubleSign, - InfractionHeight: msg.Headers[0].Number, + InfractionHeight: uint64(msg.Headers[0].Number), SlashHeight: header.Height, JailUntil: jailUtil, SlashAmt: slashedAmount.RawInt(), diff --git a/x/slashing/handler_sidechain_test.go b/x/slashing/handler_sidechain_test.go index 2add6568d..463539305 100644 --- a/x/slashing/handler_sidechain_test.go +++ b/x/slashing/handler_sidechain_test.go @@ -6,10 +6,10 @@ import ( "testing" "time" + "github.com/cosmos/cosmos-sdk/bsc" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/fees" "github.com/cosmos/cosmos-sdk/x/gov" - "github.com/cosmos/cosmos-sdk/x/slashing/bsc" "github.com/cosmos/cosmos-sdk/x/stake" "github.com/stretchr/testify/require" diff --git a/x/slashing/ibc.go b/x/slashing/ibc.go new file mode 100644 index 000000000..08c651798 --- /dev/null +++ b/x/slashing/ibc.go @@ -0,0 +1,6 @@ +package slashing + +import "github.com/cosmos/cosmos-sdk/types" + +const ChannelName = "slashing" +const ChannelId = types.ChannelID(11) diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index d2efb3831..67c2c135a 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -2,17 +2,23 @@ package slashing import ( "fmt" + "math" "time" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/cosmos/cosmos-sdk/bsc/rlp" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/fees" "github.com/cosmos/cosmos-sdk/x/bank" - "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/paramHub/types" + param "github.com/cosmos/cosmos-sdk/x/params" "github.com/cosmos/cosmos-sdk/x/sidechain" + sTypes "github.com/cosmos/cosmos-sdk/x/sidechain/types" stake "github.com/cosmos/cosmos-sdk/x/stake/types" - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" - tmtypes "github.com/tendermint/tendermint/types" ) // Keeper of the slashing store @@ -20,7 +26,7 @@ type Keeper struct { storeKey sdk.StoreKey cdc *codec.Codec validatorSet sdk.ValidatorSet - paramspace params.Subspace + paramspace param.Subspace // codespace Codespace sdk.CodespaceType @@ -30,7 +36,7 @@ type Keeper struct { } // NewKeeper creates a slashing keeper -func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, vs sdk.ValidatorSet, paramspace params.Subspace, codespace sdk.CodespaceType, bk bank.Keeper) Keeper { +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, vs sdk.ValidatorSet, paramspace param.Subspace, codespace sdk.CodespaceType, bk bank.Keeper) Keeper { keeper := Keeper{ storeKey: key, cdc: cdc, @@ -44,13 +50,14 @@ func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, vs sdk.ValidatorSet, paramspa func (k *Keeper) SetSideChain(scKeeper *sidechain.Keeper) { k.ScKeeper = scKeeper + k.initIbc() } -func (k Keeper) ClaimRegister(oracleKeeper sdk.OracleKeeper) error { - if err := oracleKeeper.RegisterClaimType(ClaimTypeDowntimeSlash, ClaimNameDowntimeSlash, k.ClaimHooks()); err != nil { - return err +func (k Keeper) initIbc() { + err := k.ScKeeper.RegisterChannel(ChannelName, ChannelId, &k) + if err != nil { + panic(fmt.Sprintf("register ibc channel failed, channel=%s, err=%s", ChannelName, err.Error())) } - return nil } // handle a validator signing two blocks at the same height @@ -195,6 +202,153 @@ func (k Keeper) AddValidators(ctx sdk.Context, vals []abci.ValidatorUpdate) { } } +func (k *Keeper) SubscribeParamChange(hub types.ParamChangePublisher) { + hub.SubscribeParamChange( + func(context sdk.Context, iChange interface{}) { + switch change := iChange.(type) { + case *Params: + // do double check + err := change.UpdateCheck() + if err != nil { + context.Logger().Error("skip invalid param change", "err", err, "param", change) + } else { + k.SetParams(context, *change) + break + } + default: + context.Logger().Debug("skip unknown param change") + } + }, + &types.ParamSpaceProto{ParamSpace: k.paramspace, Proto: func() types.SCParam { + return new(Params) + }}, + nil, + nil, + ) +} + +// implement cross chain app +func (k *Keeper) ExecuteSynPackage(ctx sdk.Context, payload []byte) sdk.ExecuteResult { + var resCode uint32 + pack, err := k.checkAndParseSynPackage(payload) + if err == nil { + err = k.executeSynPackage(ctx, pack) + } + if err != nil { + resCode = uint32(err.ABCICode()) + } + ackPackage, encodeErr := sTypes.GenCommonAckPackage(resCode) + if encodeErr != nil { + panic(encodeErr) + } + return sdk.ExecuteResult{ + Payload: ackPackage, + Err: err, + Tags: sdk.EmptyTags(), + } +} +func (k *Keeper) ExecuteAckPackage(ctx sdk.Context, payload []byte) sdk.ExecuteResult { + panic("receive unexpected ack package") +} + +// When the ack application crash, payload is the payload of the origin package. +func (k *Keeper) ExecuteFailAckPackage(ctx sdk.Context, payload []byte) sdk.ExecuteResult { + panic("receive unexpected fail ack package") +} + +func (k *Keeper) checkAndParseSynPackage(payload []byte) (*SideDowntimeSlashPackage, sdk.Error) { + var slashEvent SideDowntimeSlashPackage + err := rlp.DecodeBytes(payload, &slashEvent) + if err != nil { + return nil, ErrInvalidInput(k.Codespace, "failed to parse the payload") + } + if len(slashEvent.SideConsAddr) != sdk.AddrLen { + return nil, ErrInvalidClaim(k.Codespace, fmt.Sprintf("wrong sideConsAddr length, expected=%d", slashEvent.SideConsAddr)) + } + + if slashEvent.SideHeight <= 0 { + return nil, ErrInvalidClaim(k.Codespace, "side height must be positive") + } + + if slashEvent.SideHeight > math.MaxInt64 { + return nil, ErrInvalidClaim(k.Codespace, "side height overflow") + } + + if slashEvent.SideTimestamp <= 0 { + return nil, ErrInvalidClaim(k.Codespace, "invalid side timestamp") + } + return &slashEvent, nil +} + +func (k *Keeper) executeSynPackage(ctx sdk.Context, pack *SideDowntimeSlashPackage) sdk.Error { + sideChainName, err := k.ScKeeper.GetDestChainName(pack.SideChainId) + if err != nil { + return ErrInvalidSideChainId(DefaultCodespace) + } + sideCtx, err := k.ScKeeper.PrepareCtxForSideChain(ctx, sideChainName) + if err != nil { + return ErrInvalidSideChainId(DefaultCodespace) + } + + header := sideCtx.BlockHeader() + age := uint64(header.Time.Unix()) - pack.SideTimestamp + if age > uint64(k.MaxEvidenceAge(sideCtx).Seconds()) { + return ErrExpiredEvidence(DefaultCodespace) + } + + if k.hasSlashRecord(sideCtx, pack.SideConsAddr, Downtime, pack.SideHeight) { + return ErrDuplicateDowntimeClaim(k.Codespace) + } + + slashAmt := k.DowntimeSlashAmount(sideCtx) + slashedAmt, err := k.validatorSet.SlashSideChain(ctx, sideChainName, pack.SideConsAddr, sdk.NewDec(slashAmt)) + if err != nil { + return ErrFailedToSlash(k.Codespace, err.Error()) + } + + downtimeClaimFee := k.DowntimeSlashFee(sideCtx) + downtimeClaimFeeReal := sdk.MinInt64(downtimeClaimFee, slashedAmt.RawInt()) + bondDenom := k.validatorSet.BondDenom(sideCtx) + if downtimeClaimFeeReal > 0 && ctx.IsDeliverTx() { + feeCoinAdd := sdk.NewCoin(bondDenom, downtimeClaimFeeReal) + fees.Pool.AddAndCommitFee("side_downtime_slash", sdk.NewFee(sdk.Coins{feeCoinAdd}, sdk.FeeForAll)) + } + + remaining := slashedAmt.RawInt() - downtimeClaimFeeReal + if remaining > 0 { + found, err := k.validatorSet.AllocateSlashAmtToValidators(sideCtx, pack.SideConsAddr, sdk.NewDec(remaining)) + if err != nil { + return ErrFailedToSlash(k.Codespace, err.Error()) + } + if !found && ctx.IsDeliverTx() { + remainingCoin := sdk.NewCoin(bondDenom, remaining) + fees.Pool.AddAndCommitFee("side_downtime_slash_remaining", sdk.NewFee(sdk.Coins{remainingCoin}, sdk.FeeForAll)) + } + } + + jailUtil := header.Time.Add(k.DowntimeUnbondDuration(sideCtx)) + sr := SlashRecord{ + ConsAddr: pack.SideConsAddr, + InfractionType: Downtime, + InfractionHeight: pack.SideHeight, + SlashHeight: header.Height, + JailUntil: jailUtil, + SlashAmt: slashedAmt.RawInt(), + SideChainId: sideChainName, + } + k.setSlashRecord(sideCtx, sr) + + // Set or updated validator jail duration + signInfo, found := k.getValidatorSigningInfo(sideCtx, pack.SideConsAddr) + if !found { + return sdk.ErrInternal(fmt.Sprintf("Expected signing info for validator %s but not found", sdk.HexEncode(pack.SideConsAddr))) + } + signInfo.JailedUntil = jailUtil + k.setValidatorSigningInfo(sideCtx, pack.SideConsAddr, signInfo) + + return nil +} + // TODO: Make a method to remove the pubkey from the map when a validator is unbonded. func (k Keeper) addPubkey(ctx sdk.Context, pubkey crypto.PubKey) { addr := pubkey.Address() diff --git a/x/slashing/keys.go b/x/slashing/keys.go index 9c7f90083..0d25ea883 100644 --- a/x/slashing/keys.go +++ b/x/slashing/keys.go @@ -50,9 +50,9 @@ func getAddrPubkeyRelationKey(address []byte) []byte { return append(AddrPubkeyRelationKey, address...) } -func GetSlashRecordKey(consAddr []byte, infractionType byte, infractionHeight int64) []byte { +func GetSlashRecordKey(consAddr []byte, infractionType byte, infractionHeight uint64) []byte { heightBz := make([]byte, 8) - binary.BigEndian.PutUint64(heightBz, uint64(infractionHeight)) + binary.BigEndian.PutUint64(heightBz, infractionHeight) return append(GetSlashRecordsByAddrAndTypeIndexKey(consAddr, infractionType), heightBz...) } diff --git a/x/slashing/msg.go b/x/slashing/msg.go index 2373f6ad8..45f855b62 100644 --- a/x/slashing/msg.go +++ b/x/slashing/msg.go @@ -4,9 +4,10 @@ import ( "bytes" "fmt" + "github.com/cosmos/cosmos-sdk/bsc" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/slashing/bsc" + "github.com/cosmos/cosmos-sdk/x/sidechain/types" ) var cdc = codec.New() @@ -17,8 +18,6 @@ const ( TypeMsgUnjail = "unjail" TypeMsgSideChainUnjail = "side_chain_unjail" TypeMsgBscSubmitEvidence = "bsc_submit_evidence" - - MaxSideChainIdLength = 20 ) // verify interface at compile time @@ -99,8 +98,8 @@ func (msg MsgSideChainUnjail) ValidateBasic() sdk.Error { if msg.ValidatorAddr == nil { return ErrBadValidatorAddr(DefaultCodespace) } - if len(msg.SideChainId) == 0 || len(msg.SideChainId) > MaxSideChainIdLength { - return ErrInvalidInput(DefaultCodespace, fmt.Sprintf("side chain id must be included and max length is %d bytes", MaxSideChainIdLength)) + if len(msg.SideChainId) == 0 || len(msg.SideChainId) > types.MaxSideChainIdLength { + return ErrInvalidInput(DefaultCodespace, fmt.Sprintf("side chain id must be included and max length is %d bytes", types.MaxSideChainIdLength)) } return nil } diff --git a/x/slashing/params.go b/x/slashing/params.go index a45ca15e8..1412161fe 100644 --- a/x/slashing/params.go +++ b/x/slashing/params.go @@ -1,6 +1,7 @@ package slashing import ( + "fmt" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -49,6 +50,39 @@ type Params struct { DowntimeSlashFee int64 `json:"downtime_slash_fee"` } +func (p *Params) GetParamAttribute() (string, bool) { + return "slash", false +} + +func (p *Params) UpdateCheck() error { + // no check for SignedBlocksWindow, MinSignedPerWindow, SlashFractionDoubleSign, SlashFractionDowntime + if p.MaxEvidenceAge < 1*time.Minute || p.MaxEvidenceAge > 100*24*time.Hour { + return fmt.Errorf("the max_evidence_age should be in range 1 minutes to 100 day") + } + if p.DoubleSignUnbondDuration < 1*time.Hour { + return fmt.Errorf("the double_sign_unbond_duration should be greate than 1 hour") + } + if p.DowntimeUnbondDuration < 60*time.Second || p.DowntimeUnbondDuration > 100*24*time.Hour { + return fmt.Errorf("the downtime_unbond_duration should be in range 1 minutes to 100 day") + } + if p.TooLowDelUnbondDuration < 60*time.Second || p.TooLowDelUnbondDuration > 100*24*time.Hour { + return fmt.Errorf("the too_low_del_unbond_duration should be in range 1 minutes to 100 day") + } + if p.DoubleSignSlashAmount < 1e8 { + return fmt.Errorf("the double_sign_slash_amount should be larger than 1e8") + } + if p.DowntimeSlashAmount < 1e8 || p.DowntimeSlashAmount > 10000e8 { + return fmt.Errorf("the downtime_slash_amount should be in range 1e8 to 10000e8") + } + if p.SubmitterReward < 1e7 || p.SubmitterReward > 1000e8 { + return fmt.Errorf("the submitter_reward should be in range 1e7 to 1000e8") + } + if p.DowntimeSlashFee < 1e8 || p.DowntimeSlashFee > 1000e8 { + return fmt.Errorf("the downtime_slash_fee should be in range 1e8 to 1000e8") + } + return nil +} + // Implements params.ParamStruct func (p *Params) KeyValuePairs() params.KeyValuePairs { return params.KeyValuePairs{ diff --git a/x/slashing/slash_record.go b/x/slashing/slash_record.go index 121f7673d..75ae3f86a 100644 --- a/x/slashing/slash_record.go +++ b/x/slashing/slash_record.go @@ -18,7 +18,7 @@ const ( type SlashRecord struct { ConsAddr []byte InfractionType byte - InfractionHeight int64 + InfractionHeight uint64 SlashHeight int64 JailUntil time.Time SlashAmt int64 @@ -104,7 +104,7 @@ func UnmarshalSlashRecord(cdc *codec.Codec, key []byte, value []byte) (SlashReco infractionType := keys[sdk.AddrLen : sdk.AddrLen+1] infractionHeightBz := keys[sdk.AddrLen+1:] - infractionHeight := int64(binary.BigEndian.Uint64(infractionHeightBz)) + infractionHeight := binary.BigEndian.Uint64(infractionHeightBz) return SlashRecord{ ConsAddr: consAddr, InfractionType: infractionType[0], @@ -122,7 +122,7 @@ func (k Keeper) setSlashRecord(ctx sdk.Context, record SlashRecord) { store.Set(GetSlashRecordKey(record.ConsAddr, record.InfractionType, record.InfractionHeight), bz) } -func (k Keeper) getSlashRecord(ctx sdk.Context, consAddr []byte, infractionType byte, infractionHeight int64) (sr SlashRecord, found bool) { +func (k Keeper) getSlashRecord(ctx sdk.Context, consAddr []byte, infractionType byte, infractionHeight uint64) (sr SlashRecord, found bool) { store := ctx.KVStore(k.storeKey) key := GetSlashRecordKey(consAddr, infractionType, infractionHeight) bz := store.Get(key) @@ -132,7 +132,7 @@ func (k Keeper) getSlashRecord(ctx sdk.Context, consAddr []byte, infractionType return MustUnmarshalSlashRecord(k.cdc, key, bz), true } -func (k Keeper) hasSlashRecord(ctx sdk.Context, consAddr []byte, infractionType byte, infractionHeight int64) bool { +func (k Keeper) hasSlashRecord(ctx sdk.Context, consAddr []byte, infractionType byte, infractionHeight uint64) bool { store := ctx.KVStore(k.storeKey) return store.Get(GetSlashRecordKey(consAddr, infractionType, infractionHeight)) != nil } diff --git a/x/slashing/slash_record_test.go b/x/slashing/slash_record_test.go index a5bb105a9..984783c74 100644 --- a/x/slashing/slash_record_test.go +++ b/x/slashing/slash_record_test.go @@ -11,7 +11,7 @@ import ( func TestSetGetSlashRecord(t *testing.T) { ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) sideConsAddr := randomSideConsAddr() - iHeight := int64(100) + iHeight := uint64(100) sHeight := int64(150) jailUtil := time.Now().Add(60 * 60 * time.Second) sr := SlashRecord{ diff --git a/x/slashing/test_common.go b/x/slashing/test_common.go index 8d70fdad8..1a9649c6b 100644 --- a/x/slashing/test_common.go +++ b/x/slashing/test_common.go @@ -88,8 +88,8 @@ func createTestInput(t *testing.T, defaults Params) (sdk.Context, bank.Keeper, s ck := bank.NewBaseKeeper(accountKeeper) paramsKeeper := params.NewKeeper(cdc, keyParams, tkeyParams) - ibcKeeper := ibc.NewKeeper(keyIbc, ibc.DefaultCodespace) - scKeeper := sidechain.NewKeeper(keySideChain, paramsKeeper.Subspace(sidechain.DefaultParamspace)) + scKeeper := sidechain.NewKeeper(keySideChain, paramsKeeper.Subspace(sidechain.DefaultParamspace), cdc) + ibcKeeper := ibc.NewKeeper(keyIbc, paramsKeeper.Subspace(ibc.DefaultParamspace), ibc.DefaultCodespace, scKeeper) sk := stake.NewKeeper(cdc, keyStake, tkeyStake, ck, nil, paramsKeeper.Subspace(stake.DefaultParamspace), stake.DefaultCodespace) sk.SetupForSideChain(&scKeeper, &ibcKeeper) genesis := stake.DefaultGenesisState() @@ -148,17 +148,19 @@ func createSideTestInput(t *testing.T, defaults Params) (sdk.Context, sdk.Contex paramsKeeper := params.NewKeeper(cdc, keyParams, tkeyParams) - ibcKeeper := ibc.NewKeeper(keyIbc, ibc.DefaultCodespace) - // set up IBC chainID for BBC - ibcKeeper.SetSrcIbcChainID(sdk.IbcChainID(1)) - // set up IBC chainID for BSC - err = ibcKeeper.RegisterDestChain("bsc", sdk.IbcChainID(1)) - require.Nil(t, err) - - scKeeper := sidechain.NewKeeper(keySideChain, paramsKeeper.Subspace(sidechain.DefaultParamspace)) + scKeeper := sidechain.NewKeeper(keySideChain, paramsKeeper.Subspace(sidechain.DefaultParamspace), cdc) bscStorePrefix := []byte{0x99} scKeeper.SetSideChainIdAndStorePrefix(ctx, "bsc", bscStorePrefix) scKeeper.SetParams(ctx, sidechain.DefaultParams()) + + ibcKeeper := ibc.NewKeeper(keyIbc, paramsKeeper.Subspace(ibc.DefaultParamspace), ibc.DefaultCodespace, scKeeper) + // set up IBC chainID for BBC + scKeeper.SetSrcChainID(sdk.ChainID(1)) + err = scKeeper.RegisterDestChain("bsc", sdk.ChainID(1)) + require.Nil(t, err) + storePrefix := scKeeper.GetSideChainStorePrefix(ctx, "bsc") + ibcKeeper.SetParams(ctx.WithSideChainKeyPrefix(storePrefix), ibc.Params{RelayerFee: ibc.DefaultRelayerFeeParam}) + sk := stake.NewKeeper(cdc, keyStake, tkeyStake, ck, nil, paramsKeeper.Subspace(stake.DefaultParamspace), stake.DefaultCodespace) sk.SetupForSideChain(&scKeeper, &ibcKeeper) genesis := stake.DefaultGenesisState() @@ -184,6 +186,7 @@ func createSideTestInput(t *testing.T, defaults Params) (sdk.Context, sdk.Contex sk = sk.WithHooks(keeper.Hooks()) keeper.SetSideChain(&scKeeper) keeper.SetParams(sideCtx, defaults) + scKeeper.SetChannelSendPermission(ctx, sdk.ChainID(1), sdk.ChannelID(8), sdk.ChannelAllow) require.NotPanics(t, func() { InitGenesis(ctx, keeper, GenesisState{defaults}, genesis) @@ -248,8 +251,8 @@ func newTestMsgSideUnDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, am return stake.MsgSideChainUndelegate{ DelegatorAddr: delAddr, ValidatorAddr: valAddr, - Amount: sdk.NewCoin("steak", amount), - SideChainId: "bsc", + Amount: sdk.NewCoin("steak", amount), + SideChainId: "bsc", } } diff --git a/x/slashing/types.go b/x/slashing/types.go new file mode 100644 index 000000000..a985610b0 --- /dev/null +++ b/x/slashing/types.go @@ -0,0 +1,10 @@ +package slashing + +import "github.com/cosmos/cosmos-sdk/types" + +type SideDowntimeSlashPackage struct { + SideConsAddr []byte `json:"side_cons_addr"` + SideHeight uint64 `json:"side_height"` + SideChainId types.ChainID `json:"side_chain_id"` + SideTimestamp uint64 `json:"side_timestamp"` +} diff --git a/x/stake/app_test.go b/x/stake/app_test.go index 3e274d8dd..e957374df 100644 --- a/x/stake/app_test.go +++ b/x/stake/app_test.go @@ -32,8 +32,8 @@ func getMockApp(t *testing.T) (*mock.App, Keeper) { bankKeeper := bank.NewBaseKeeper(mApp.AccountKeeper) pk := params.NewKeeper(mApp.Cdc, keyParams, tkeyParams) - ibcKeeper := ibc.NewKeeper(keyIbc, ibc.DefaultCodespace) - scKeeper := sidechain.NewKeeper(keySideChain, pk.Subspace(sidechain.DefaultParamspace)) + scKeeper := sidechain.NewKeeper(keySideChain, pk.Subspace(sidechain.DefaultParamspace), mApp.Cdc) + ibcKeeper := ibc.NewKeeper(keyIbc, pk.Subspace(ibc.DefaultParamspace), ibc.DefaultCodespace, scKeeper) keeper := NewKeeper(mApp.Cdc, keyStake, tkeyStake, bankKeeper, nil, pk.Subspace(DefaultParamspace), mApp.RegisterCodespace(DefaultCodespace)) keeper.SetupForSideChain(&scKeeper, &ibcKeeper) diff --git a/x/stake/client/rest/tx.go b/x/stake/client/rest/tx.go index 179e6005d..6acf3f660 100644 --- a/x/stake/client/rest/tx.go +++ b/x/stake/client/rest/tx.go @@ -190,10 +190,9 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte i++ } - txBldr := authtxb.TxBuilder{ - Codec: cdc, - ChainID: baseReq.ChainID, + Codec: cdc, + ChainID: baseReq.ChainID, } // sign messages diff --git a/x/stake/endblock.go b/x/stake/endblock.go index 70edc9eda..478cee7d0 100644 --- a/x/stake/endblock.go +++ b/x/stake/endblock.go @@ -43,16 +43,19 @@ func EndBreatheBlock(ctx sdk.Context, k keeper.Keeper) (validatorUpdates []abci. } func saveSideChainValidatorsToIBC(ctx sdk.Context, sideChainId string, newVals []types.Validator, k keeper.Keeper) { - ibcValidatorSet := make(types.IbcValidatorSet, len(newVals)) + ibcPackage := types.IbcValidatorSetPackage{ + Type: types.StakePackageType, + ValidatorSet: make([]types.IbcValidator, len(newVals)), + } for i := range newVals { - ibcValidatorSet[i] = types.IbcValidator{ + ibcPackage.ValidatorSet[i] = types.IbcValidator{ ConsAddr: newVals[i].SideConsAddr, FeeAddr: newVals[i].SideFeeAddr, DistAddr: newVals[i].DistributionAddr, - Power: newVals[i].GetPower().RawInt(), + Power: uint64(newVals[i].GetPower().RawInt()), } } - _, err := k.SaveValidatorSetToIbc(ctx, sideChainId, ibcValidatorSet) + _, err := k.SaveValidatorSetToIbc(ctx, sideChainId, ibcPackage) if err != nil { k.Logger(ctx).Error("save validators to ibc package failed: " + err.Error()) return diff --git a/x/stake/handler.go b/x/stake/handler.go index e8385a613..3a48c405e 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -66,7 +66,6 @@ func NewStakeHandler(k Keeper) sdk.Handler { } } - //_____________________________________________________________________ // These functions assume everything has been authenticated, @@ -95,7 +94,7 @@ func handleMsgRemoveValidatorAfterProposal(ctx sdk.Context, msg MsgRemoveValidat msgBeginUnbonding := MsgBeginUnbonding{ ValidatorAddr: del.GetValidatorAddr(), DelegatorAddr: del.GetDelegatorAddr(), - SharesAmount: del.GetShares(), + SharesAmount: del.GetShares(), } result = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, k) // handleMsgBeginUnbonding return error, abort execution diff --git a/x/stake/keeper/distribute_sidechain.go b/x/stake/keeper/distribute_sidechain.go index 895c5c938..b71041e37 100644 --- a/x/stake/keeper/distribute_sidechain.go +++ b/x/stake/keeper/distribute_sidechain.go @@ -45,7 +45,7 @@ func (k Keeper) Distribute(ctx sdk.Context) { } } // assign rewards to delegator - changedAddrs := make([]sdk.AccAddress, len(rewards) + 1) + changedAddrs := make([]sdk.AccAddress, len(rewards)+1) for i := range rewards { if _, _, err := k.bankKeeper.AddCoins(ctx, rewards[i].AccAddr, sdk.Coins{sdk.NewCoin(bondDenom, rewards[i].Amount)}); err != nil { panic(err) diff --git a/x/stake/keeper/ibc.go b/x/stake/keeper/ibc.go index 20b40984a..b0c9a5db9 100644 --- a/x/stake/keeper/ibc.go +++ b/x/stake/keeper/ibc.go @@ -1,40 +1,21 @@ package keeper import ( + "github.com/cosmos/cosmos-sdk/bsc/rlp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake/types" ) -const IbcChannelName = "staking" -const IbcChannelId = sdk.IbcChannelID(8) +const ChannelName = "stake" +const ChannelId = sdk.ChannelID(8) -func (k Keeper) SaveValidatorSetToIbc(ctx sdk.Context, sideChainId string, ibcVals types.IbcValidatorSet) (seq uint64, sdkErr sdk.Error) { +func (k Keeper) SaveValidatorSetToIbc(ctx sdk.Context, sideChainId string, ibcPackage types.IbcValidatorSetPackage) (seq uint64, sdkErr sdk.Error) { if k.ibcKeeper == nil { return 0, sdk.ErrInternal("the keeper is not prepared for side chain") } - bz, err := ibcVals.Serialize() + bz, err := rlp.EncodeToBytes(ibcPackage) if err != nil { - k.Logger(ctx).Error("serialize failed: " + err.Error()) - return 0, sdk.ErrInternal(err.Error()) + return 0, sdk.ErrInternal("failed to encode IbcValidatorSetPackage") } - // prepend a flag 0x00 - bz = addPrefix(byte(0x00), bz) - return k.ibcKeeper.CreateIBCPackage(ctx, sideChainId, IbcChannelName, bz) -} - -func (k Keeper) SaveJailedValidatorToIbc(ctx sdk.Context, sideChainId string, ibcVal types.IbcValidator) (seq uint64, sdkErr sdk.Error) { - bz, err := ibcVal.Serialize() - if err != nil { - k.Logger(ctx).Error("serialize failed: " + err.Error()) - return 0, sdk.ErrInternal(err.Error()) - } - newBz := addPrefix(byte(0x01), bz) - return k.ibcKeeper.CreateIBCPackage(ctx, sideChainId, IbcChannelName, newBz) -} - -func addPrefix(prefix byte, bz []byte) []byte { - newBz := make([]byte, len(bz)+1) - newBz[0] = prefix - copy(newBz[1:], bz) - return newBz + return k.ibcKeeper.CreateIBCSyncPackage(ctx, sideChainId, ChannelName, bz) } diff --git a/x/stake/keeper/keeper.go b/x/stake/keeper/keeper.go index 2c6c02d77..ba09d319b 100644 --- a/x/stake/keeper/keeper.go +++ b/x/stake/keeper/keeper.go @@ -3,12 +3,15 @@ package keeper import ( "fmt" + "github.com/cosmos/cosmos-sdk/bsc/rlp" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/ibc" + pTypes "github.com/cosmos/cosmos-sdk/x/paramHub/types" "github.com/cosmos/cosmos-sdk/x/params" "github.com/cosmos/cosmos-sdk/x/sidechain" + sTypes "github.com/cosmos/cosmos-sdk/x/sidechain/types" "github.com/cosmos/cosmos-sdk/x/stake/types" "github.com/tendermint/tendermint/libs/log" ) @@ -52,9 +55,9 @@ func (k Keeper) initIbc() { if k.ibcKeeper == nil { return } - err := k.ibcKeeper.RegisterChannel(IbcChannelName, IbcChannelId) + err := k.ScKeeper.RegisterChannel(ChannelName, ChannelId, &k) if err != nil { - panic(fmt.Sprintf("register ibc channel failed, channel=%s, err=%s", IbcChannelName, err.Error())) + panic(fmt.Sprintf("register ibc channel failed, channel=%s, err=%s", ChannelName, err.Error())) } } @@ -172,3 +175,57 @@ func (k Keeper) SetIntraTxCounter(ctx sdk.Context, counter int16) { bz := k.cdc.MustMarshalBinaryLengthPrefixed(counter) store.Set(IntraTxCounterKey, bz) } + +func (k *Keeper) SubscribeParamChange(hub pTypes.ParamChangePublisher) { + hub.SubscribeParamChange( + func(context sdk.Context, iChange interface{}) { + switch change := iChange.(type) { + case *types.Params: + // do double check + err := change.UpdateCheck() + if err != nil { + context.Logger().Error("skip invalid param change", "err", err, "param", change) + } else { + res := k.GetParams(context) + // ignore BondDenom update if have. + change.BondDenom = res.BondDenom + k.SetParams(context, *change) + break + } + + default: + context.Logger().Debug("skip unknown param change") + } + }, + &pTypes.ParamSpaceProto{ParamSpace: k.paramstore, Proto: func() pTypes.SCParam { + return new(types.Params) + }}, + nil, + nil, + ) +} + +// cross chain app implement +func (k *Keeper) ExecuteSynPackage(ctx sdk.Context, payload []byte) sdk.ExecuteResult { + panic("receive unexpected syn package") +} + +func (k *Keeper) ExecuteAckPackage(ctx sdk.Context, payload []byte) sdk.ExecuteResult { + logger := ctx.Logger().With("module", "stake") + var ackPackage sTypes.CommonAckPackage + err := rlp.DecodeBytes(payload, &ackPackage) + if err != nil { + logger.Error("fail to decode ack package", "payload", payload) + return sdk.ExecuteResult{Err: types.ErrInvalidCrosschainPackage(k.codespace)} + } + if !ackPackage.IsOk() { + logger.Error("side chain failed to process staking package", "code", ackPackage.Code) + } + return sdk.ExecuteResult{} +} + +func (k *Keeper) ExecuteFailAckPackage(ctx sdk.Context, payload []byte) sdk.ExecuteResult { + //do no thing + ctx.Logger().Error("side chain process staking package crashed", "payload", payload) + return sdk.ExecuteResult{} +} diff --git a/x/stake/keeper/key_test.go b/x/stake/keeper/key_test.go index 8028e6c93..928d2235d 100644 --- a/x/stake/keeper/key_test.go +++ b/x/stake/keeper/key_test.go @@ -27,7 +27,7 @@ func TestGetValidatorPowerRank(t *testing.T) { val2, val3, val4 := val1, val1, val1 val2.Tokens = sdk.NewDecFromInt(1) val3.Tokens = sdk.NewDecFromInt(10) - val4.Tokens = sdk.NewDecFromInt(1<<20) + val4.Tokens = sdk.NewDecFromInt(1 << 20) tests := []struct { validator types.Validator @@ -54,7 +54,7 @@ func TestGetValidatorPowerRankNew(t *testing.T) { val2.Tokens = sdk.NewDec(0) val3, val4 := val1, val2 val3.Tokens = sdk.NewDecFromInt(1) - val4.Tokens = sdk.NewDecFromInt(1<<20) + val4.Tokens = sdk.NewDecFromInt(1 << 20) t.Log(hex.EncodeToString(valAddr1)) t.Log(hex.EncodeToString(valAddr2)) diff --git a/x/stake/keeper/params_test.go b/x/stake/keeper/params_test.go index ef81459ca..cf975789b 100644 --- a/x/stake/keeper/params_test.go +++ b/x/stake/keeper/params_test.go @@ -35,6 +35,8 @@ func TestSetParams(t *testing.T) { ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, mode, log.NewNopLogger()) pk := params.NewKeeper(cdc, keyParams, tkeyParams) k := NewKeeper(cdc, keyStake, tkeyStake, nil, nil, pk.Subspace(DefaultParamspace), types.DefaultCodespace) + sdk.UpgradeMgr.AddUpgradeHeight(sdk.LaunchBscUpgrade, 10) + sdk.UpgradeMgr.SetHeight(1) k.SetParams(ctx, types.DefaultParams()) require.True(t, k.paramstore.Has(ctx, types.KeyUnbondingTime)) @@ -43,7 +45,6 @@ func TestSetParams(t *testing.T) { require.False(t, k.paramstore.Has(ctx, types.KeyMinSelfDelegation)) require.False(t, k.paramstore.Has(ctx, types.KeyMinDelegationChange)) - sdk.UpgradeMgr.AddUpgradeHeight(sdk.LaunchBscUpgrade, 10) sdk.UpgradeMgr.SetHeight(10) k.SetParams(ctx, types.DefaultParams()) require.True(t, k.paramstore.Has(ctx, types.KeyUnbondingTime)) diff --git a/x/stake/keeper/slash_sidechain.go b/x/stake/keeper/slash_sidechain.go index d8ce238f2..8d2d41151 100644 --- a/x/stake/keeper/slash_sidechain.go +++ b/x/stake/keeper/slash_sidechain.go @@ -75,13 +75,18 @@ func (k Keeper) SlashSideChain(ctx sdk.Context, sideChainId string, sideConsAddr k.addrPool.AddAddrs([]sdk.AccAddress{DelegationAccAddr}) } if validator.IsBonded() { - ibcValidator := types.IbcValidator{ - ConsAddr: validator.SideConsAddr, - FeeAddr: validator.SideFeeAddr, - DistAddr: validator.DistributionAddr, - Power: validator.GetPower().RawInt(), + ibcPackage := types.IbcValidatorSetPackage{ + Type: types.JailPackageType, + ValidatorSet: []types.IbcValidator{ + { + ConsAddr: validator.SideConsAddr, + FeeAddr: validator.SideFeeAddr, + DistAddr: validator.DistributionAddr, + Power: uint64(validator.GetPower().RawInt()), + }, + }, } - if _, err := k.SaveJailedValidatorToIbc(ctx, sideChainId, ibcValidator); err != nil { + if _, err := k.SaveValidatorSetToIbc(ctx, sideChainId, ibcPackage); err != nil { return sdk.ZeroDec(), errors.New(err.Error()) } } diff --git a/x/stake/keeper/test_common.go b/x/stake/keeper/test_common.go index f6069c2ba..7d343c49d 100644 --- a/x/stake/keeper/test_common.go +++ b/x/stake/keeper/test_common.go @@ -123,10 +123,11 @@ func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context ctx = ctx.WithAccountCache(accountCache) ck := bank.NewBaseKeeper(accountKeeper) - ibcKeeper := ibc.NewKeeper(keyIbc, ibc.DefaultCodespace) pk := params.NewKeeper(cdc, keyParams, tkeyParams) - scKeeper := sidechain.NewKeeper(keySideChain, pk.Subspace(sidechain.DefaultParamspace)) + scKeeper := sidechain.NewKeeper(keySideChain, pk.Subspace(sidechain.DefaultParamspace), cdc) + + ibcKeeper := ibc.NewKeeper(keyIbc, pk.Subspace(ibc.DefaultParamspace), ibc.DefaultCodespace, scKeeper) keeper := NewKeeper(cdc, keyStake, tkeyStake, ck, nil, pk.Subspace(DefaultParamspace), types.DefaultCodespace) keeper.SetPool(ctx, types.InitialPool()) keeper.SetParams(ctx, types.DefaultParams()) diff --git a/x/stake/simulation/sim_test.go b/x/stake/simulation/sim_test.go index 748c5d6b2..0dd44ac3e 100644 --- a/x/stake/simulation/sim_test.go +++ b/x/stake/simulation/sim_test.go @@ -37,8 +37,8 @@ func TestStakeWithRandomMessages(t *testing.T) { feeCollectionKeeper := auth.NewFeeCollectionKeeper(mapp.Cdc, feeKey) paramstore := params.NewKeeper(mapp.Cdc, paramsKey, paramsTKey) - ibcKeeper := ibc.NewKeeper(ibcKey, ibc.DefaultCodespace) - scKeeper := sidechain.NewKeeper(keySideChain, paramstore.Subspace(sidechain.DefaultParamspace)) + scKeeper := sidechain.NewKeeper(keySideChain, paramstore.Subspace(sidechain.DefaultParamspace), mapp.Cdc) + ibcKeeper := ibc.NewKeeper(ibcKey, paramstore.Subspace(ibc.DefaultParamspace), ibc.DefaultCodespace, scKeeper) stakeKeeper := stake.NewKeeper(mapp.Cdc, stakeKey, stakeTKey, bankKeeper, nil, paramstore.Subspace(stake.DefaultParamspace), stake.DefaultCodespace) stakeKeeper.SetupForSideChain(&scKeeper, &ibcKeeper) distrKeeper := distribution.NewKeeper(mapp.Cdc, distrKey, paramstore.Subspace(distribution.DefaultParamspace), bankKeeper, stakeKeeper, feeCollectionKeeper, distribution.DefaultCodespace) diff --git a/x/stake/tags/tags.go b/x/stake/tags/tags.go index bff409b34..4469bf132 100644 --- a/x/stake/tags/tags.go +++ b/x/stake/tags/tags.go @@ -4,6 +4,7 @@ package tags import ( sdk "github.com/cosmos/cosmos-sdk/types" ) + // TODO: remove them all var ( SrcValidator = sdk.TagSrcValidator @@ -12,5 +13,4 @@ var ( Moniker = "moniker" Identity = "identity" EndTime = "end-time" - ) diff --git a/x/stake/types/codec.go b/x/stake/types/codec.go index 0ad1dcccb..f20af0d0e 100644 --- a/x/stake/types/codec.go +++ b/x/stake/types/codec.go @@ -19,6 +19,8 @@ func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(MsgSideChainDelegate{}, "cosmos-sdk/MsgSideChainDelegate", nil) cdc.RegisterConcrete(MsgSideChainRedelegate{}, "cosmos-sdk/MsgSideChainRedelegate", nil) cdc.RegisterConcrete(MsgSideChainUndelegate{}, "cosmos-sdk/MsgSideChainUndelegate", nil) + + cdc.RegisterConcrete(&Params{}, "params/StakeParamSet", nil) } // generic sealed codec to be used throughout sdk diff --git a/x/stake/types/errors.go b/x/stake/types/errors.go index 4c39df76c..10873b5d4 100644 --- a/x/stake/types/errors.go +++ b/x/stake/types/errors.go @@ -13,16 +13,17 @@ type CodeType = sdk.CodeType const ( DefaultCodespace sdk.CodespaceType = 4 - CodeInvalidValidator CodeType = 101 - CodeInvalidDelegation CodeType = 102 - CodeInvalidInput CodeType = 103 - CodeValidatorJailed CodeType = 104 - CodeInvalidProposal CodeType = 105 - CodeInvalidSideChain CodeType = 106 - CodeInvalidAddress CodeType = sdk.CodeInvalidAddress - CodeUnauthorized CodeType = sdk.CodeUnauthorized - CodeInternal CodeType = sdk.CodeInternal - CodeUnknownRequest CodeType = sdk.CodeUnknownRequest + CodeInvalidValidator CodeType = 101 + CodeInvalidDelegation CodeType = 102 + CodeInvalidInput CodeType = 103 + CodeValidatorJailed CodeType = 104 + CodeInvalidProposal CodeType = 105 + CodeInvalidSideChain CodeType = 106 + CodeInvalidCrossChainPackage CodeType = 107 + CodeInvalidAddress CodeType = sdk.CodeInvalidAddress + CodeUnauthorized CodeType = sdk.CodeUnauthorized + CodeInternal CodeType = sdk.CodeInternal + CodeUnknownRequest CodeType = sdk.CodeUnknownRequest ) //validator @@ -226,3 +227,7 @@ func ErrInvalidProposal(codespace sdk.CodespaceType, reason string) sdk.Error { func ErrInvalidSideChainId(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidSideChain, "invalid side chain id") } + +func ErrInvalidCrosschainPackage(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidCrossChainPackage, "invalid cross chain package") +} diff --git a/x/stake/types/events.go b/x/stake/types/events.go index 0b5d5136f..01634d200 100644 --- a/x/stake/types/events.go +++ b/x/stake/types/events.go @@ -1,13 +1,13 @@ package types const ( - EventTypeCompleteUnbonding = "complete_unbonding" - EventTypeCompleteRedelegation = "complete_redelegation" - EventTypeCreateValidator = "create_validator" - EventTypeEditValidator = "edit_validator" - EventTypeDelegate = "delegate" - EventTypeUnbond = "unbond" - EventTypeRedelegate = "redelegate" + EventTypeCompleteUnbonding = "complete_unbonding" + EventTypeCompleteRedelegation = "complete_redelegation" + EventTypeCreateValidator = "create_validator" + EventTypeEditValidator = "edit_validator" + EventTypeDelegate = "delegate" + EventTypeUnbond = "unbond" + EventTypeRedelegate = "redelegate" AttributeKeyValidator = "validator" AttributeKeyCommissionRate = "commission_rate" @@ -17,5 +17,5 @@ const ( AttributeKeyDelegator = "delegator" AttributeKeyCompletionTime = "completion_time" - AttributeKeySideChainId = "side_chain_id" + AttributeKeySideChainId = "side_chain_id" ) diff --git a/x/stake/types/msg_sidechain.go b/x/stake/types/msg_sidechain.go index dec9480cc..cc2740749 100644 --- a/x/stake/types/msg_sidechain.go +++ b/x/stake/types/msg_sidechain.go @@ -5,6 +5,7 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/sidechain/types" ) const ( @@ -13,8 +14,6 @@ const ( MsgTypeSideChainDelegate = "side_delegate" MsgTypeSideChainRedelegate = "side_redelegate" MsgTypeSideChainUndelegate = "side_undelegate" - - MaxSideChainIdLength = 20 ) type SideChainIder interface { @@ -96,7 +95,7 @@ func (msg MsgCreateSideChainValidator) ValidateBasic() sdk.Error { return err } - if len(msg.SideChainId) == 0 || len(msg.SideChainId) > MaxSideChainIdLength { + if len(msg.SideChainId) == 0 || len(msg.SideChainId) > types.MaxSideChainIdLength { return sdk.NewError(DefaultCodespace, CodeInvalidInput, "side chain id must be included and max length is 20 bytes") } @@ -173,7 +172,7 @@ func (msg MsgEditSideChainValidator) ValidateBasic() sdk.Error { } } - if len(msg.SideChainId) == 0 || len(msg.SideChainId) > MaxSideChainIdLength { + if len(msg.SideChainId) == 0 || len(msg.SideChainId) > types.MaxSideChainIdLength { return sdk.NewError(DefaultCodespace, CodeInvalidInput, "side chain id must be included and max length is 20 bytes") } @@ -242,7 +241,7 @@ func (msg MsgSideChainDelegate) ValidateBasic() sdk.Error { if len(msg.ValidatorAddr) != sdk.AddrLen { return sdk.ErrInvalidAddress(fmt.Sprintf("Expected validator address length is %d, actual length is %d", sdk.AddrLen, len(msg.ValidatorAddr))) } - if len(msg.SideChainId) == 0 || len(msg.SideChainId) > MaxSideChainIdLength { + if len(msg.SideChainId) == 0 || len(msg.SideChainId) > types.MaxSideChainIdLength { return sdk.NewError(DefaultCodespace, CodeInvalidInput, "side chain id must be included and max length is 20 bytes") } return nil @@ -305,7 +304,7 @@ func (msg MsgSideChainRedelegate) ValidateBasic() sdk.Error { if bytes.Equal(msg.ValidatorSrcAddr, msg.ValidatorDstAddr) { return ErrSelfRedelegation(DefaultCodespace) } - if len(msg.SideChainId) == 0 || len(msg.SideChainId) > MaxSideChainIdLength { + if len(msg.SideChainId) == 0 || len(msg.SideChainId) > types.MaxSideChainIdLength { return sdk.NewError(DefaultCodespace, CodeInvalidInput, "side chain id must be included and max length is 20 bytes") } return nil @@ -355,7 +354,7 @@ func (msg MsgSideChainUndelegate) ValidateBasic() sdk.Error { if msg.Amount.Amount <= 0 { return ErrBadDelegationAmount(DefaultCodespace, "undelegation amount must be positive") } - if len(msg.SideChainId) == 0 || len(msg.SideChainId) > MaxSideChainIdLength { + if len(msg.SideChainId) == 0 || len(msg.SideChainId) > types.MaxSideChainIdLength { return sdk.NewError(DefaultCodespace, CodeInvalidInput, "side chain id must be included and max length is 20 bytes") } return nil diff --git a/x/stake/types/params.go b/x/stake/types/params.go index 810c93a1c..951d41c30 100644 --- a/x/stake/types/params.go +++ b/x/stake/types/params.go @@ -6,6 +6,7 @@ import ( "time" "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/params" ) @@ -49,6 +50,33 @@ type Params struct { MinDelegationChange int64 `json:"min_delegation_change"` // the minimal delegation amount changed } +func (p *Params) GetParamAttribute() (string, bool) { + return "staking", false +} + +func (p *Params) UpdateCheck() error { + if p.BondDenom != types.NativeTokenSymbol { + return fmt.Errorf("only native token is availabe as bond_denom so far") + } + // the valid range is 1 minute to 100 day. + if p.UnbondingTime > 100*24*time.Hour || p.UnbondingTime < time.Minute { + return fmt.Errorf("the UnbondingTime should be in range 1 minute to 100 days") + } + if p.MaxValidators < 1 || p.MaxValidators > 500 { + return fmt.Errorf("the max validator should be in range 1 to 500") + } + // BondDenom do not check here, it should be native token and do not support update so far. + // Leave the check in node repo. + + if p.MinSelfDelegation > 10000000e8 || p.MinSelfDelegation < 1e8 { + return fmt.Errorf("the min_self_delegation should be in range 1e8 to 10000000e8") + } + if p.MinDelegationChange < 1e5 { + return fmt.Errorf("the min_delegation_change should be no less than 1e5") + } + return nil +} + // Implements params.ParamSet func (p *Params) KeyValuePairs() params.KeyValuePairs { return params.KeyValuePairs{ diff --git a/x/stake/types/validator_ibc.go b/x/stake/types/validator_ibc.go index b83550e69..d54590279 100644 --- a/x/stake/types/validator_ibc.go +++ b/x/stake/types/validator_ibc.go @@ -1,61 +1,24 @@ package types import ( - "encoding/binary" - "errors" - sdk "github.com/cosmos/cosmos-sdk/types" ) +type PackageType uint8 + +const ( + StakePackageType PackageType = 0x00 + JailPackageType PackageType = 0x01 +) + type IbcValidator struct { ConsAddr []byte FeeAddr []byte DistAddr sdk.AccAddress - Power int64 -} - -// {20 bytes consensusAddress} + {20 bytes feeAddress} + {20 bytes distributionAddress} + {8 bytes voting power} -func (v *IbcValidator) Serialize() ([]byte, error) { - consAddrLen, feeAddrLen, distAddrLen, powerLen:= len(v.ConsAddr), len(v.FeeAddr), len(v.DistAddr), 8 - if consAddrLen == 0 || feeAddrLen == 0 || distAddrLen == 0 || v.Power == 0 { - return nil, errors.New("not all the IbcValidator fields are filled") - } - totalLen := consAddrLen + feeAddrLen + distAddrLen + powerLen - result := make([]byte, totalLen) - copy(result[:consAddrLen], v.ConsAddr) - copy(result[consAddrLen:consAddrLen+feeAddrLen], v.FeeAddr) - copy(result[consAddrLen+feeAddrLen:totalLen-powerLen], v.DistAddr) - binary.BigEndian.PutUint64(result[totalLen-powerLen:], uint64(v.Power)) - return result, nil + Power uint64 } -type IbcValidatorSet []IbcValidator - -func (vs IbcValidatorSet) Serialize() ([]byte, error) { - vsLen := len(vs) - if vsLen == 0 { - return []byte{}, nil - } - - v0 := vs[0] - consAddrLen, feeAddrLen, distAddrLen, powerLen:= len(v0.ConsAddr), len(v0.FeeAddr), sdk.AddrLen, 8 - eachLen := consAddrLen + feeAddrLen + distAddrLen + powerLen - result := make([]byte, vsLen*eachLen) - for i:= range vs { - v := vs[i] - if len(v.ConsAddr) != consAddrLen || - len(v.FeeAddr) != feeAddrLen || - len(v.DistAddr) != distAddrLen || - v.Power == 0 { - return nil, errors.New("not all validators' fields are complete") - } - start := i*eachLen - copy(result[start: start+consAddrLen], vs[i].ConsAddr) - copy(result[start+consAddrLen: start+consAddrLen+feeAddrLen], vs[i].FeeAddr) - copy(result[start+consAddrLen+feeAddrLen: start+eachLen-powerLen], vs[i].DistAddr) - binary.BigEndian.PutUint64(result[start+eachLen-powerLen:start+eachLen], uint64(vs[i].Power)) - } - return result, nil +type IbcValidatorSetPackage struct { + Type PackageType + ValidatorSet []IbcValidator } - - diff --git a/x/stake/types/validator_ibc_test.go b/x/stake/types/validator_ibc_test.go deleted file mode 100644 index b8f86c0f3..000000000 --- a/x/stake/types/validator_ibc_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package types - -import ( - "crypto/rand" - "encoding/binary" - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto/tmhash" -) - -func TestIbcValidator_Serialize(t *testing.T) { - consAddr := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14} - feeAddr := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13} - distAddr := sdk.AccAddress(tmhash.SumTruncated([]byte("dist"))) - v := IbcValidator{ - ConsAddr: consAddr, - FeeAddr: feeAddr, - DistAddr: distAddr, - Power: 1000000, - } - bz, err := v.Serialize() - require.NoError(t, err) - require.Equal(t, 68, len(bz)) - require.Equal(t, consAddr, bz[:20]) - require.Equal(t, feeAddr, bz[20:40]) - require.Equal(t, distAddr.Bytes(), bz[40:60]) - require.Equal(t, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x42, 0x40}, bz[60:]) -} - -func TestIbcValidatorSet_Serialize(t *testing.T) { - ibcValidatorSet := make(IbcValidatorSet, 11) - for i := range ibcValidatorSet { - ibcValidatorSet[i] = IbcValidator{ - ConsAddr:randAddr(t, 20), - FeeAddr: randAddr(t, 20), - DistAddr: sdk.AccAddress(randAddr(t, 20)), - Power: int64((i+1)*1000), - } - } - bz, err := ibcValidatorSet.Serialize() - require.NoError(t, err) - require.Equal(t, 748,len(bz)) - for i:= range ibcValidatorSet { - require.Equal(t, ibcValidatorSet[i].ConsAddr, bz[i*68:i*68+20]) - require.Equal(t, ibcValidatorSet[i].FeeAddr, bz[i*68+20:i*68+40]) - require.Equal(t, ibcValidatorSet[i].DistAddr.Bytes(), bz[i*68+40:i*68+60]) - require.Equal(t, ibcValidatorSet[i].Power, int64(binary.BigEndian.Uint64(bz[i*68+60:(i+1)*68]))) - } -} - -func randAddr(t *testing.T, size int64) []byte { - addr := make([]byte, size) - n, err := rand.Read(addr) - require.NoError(t, err) - require.Equal(t, 20, n) - require.Equal(t, 20, len(addr)) - return addr -} \ No newline at end of file diff --git a/x/stake/types/validator_test.go b/x/stake/types/validator_test.go index 4d277e8f9..276dddb2d 100644 --- a/x/stake/types/validator_test.go +++ b/x/stake/types/validator_test.go @@ -1,6 +1,7 @@ package types import ( + "crypto/rand" "fmt" "testing" @@ -322,3 +323,12 @@ func TestMarshalValidator(t *testing.T) { require.EqualValues(t, validator.Status, getVal.Status) require.EqualValues(t, validator.SideConsAddr, getVal.SideConsAddr) } + +func randAddr(t *testing.T, size int64) []byte { + addr := make([]byte, size) + n, err := rand.Read(addr) + require.NoError(t, err) + require.Equal(t, 20, n) + require.Equal(t, 20, len(addr)) + return addr +}