From 04b9b31cc6df4d1b2ebddb3b4758aa72d4acb889 Mon Sep 17 00:00:00 2001 From: Robert Zaremba Date: Thu, 7 Jan 2021 22:50:52 +0100 Subject: [PATCH 001/728] codecs: rename MakeCodecs rename and docs update (#8245) * codecs: rename MakeCodecs to MakeTestCodecs * update changelog * remove MakeTestCodecs * typo --- codec/amino_codec_test.go | 5 ++--- simapp/app.go | 9 --------- simapp/encoding.go | 6 +++--- simapp/simd/cmd/genaccounts_test.go | 2 +- x/auth/ante/sigverify_test.go | 2 +- x/auth/keeper/keeper_test.go | 2 +- x/auth/simulation/decoder_test.go | 2 +- x/auth/types/common_test.go | 3 ++- x/auth/vesting/types/common_test.go | 4 ++-- x/bank/keeper/keeper_test.go | 2 +- x/capability/simulation/decoder_test.go | 2 +- x/distribution/simulation/decoder_test.go | 2 +- x/evidence/keeper/querier_test.go | 12 ++++++------ x/gov/keeper/common_test.go | 4 ++-- x/gov/simulation/decoder_test.go | 3 +-- x/mint/simulation/decoder_test.go | 2 +- x/slashing/simulation/decoder_test.go | 2 +- x/staking/keeper/grpc_query_test.go | 5 ++--- x/staking/simulation/decoder_test.go | 3 +-- 19 files changed, 30 insertions(+), 42 deletions(-) diff --git a/codec/amino_codec_test.go b/codec/amino_codec_test.go index 7df62adf59..bff4868c4e 100644 --- a/codec/amino_codec_test.go +++ b/codec/amino_codec_test.go @@ -120,9 +120,8 @@ func TestAminoCodecUnpackAnyFails(t *testing.T) { func TestAminoCodecFullDecodeAndEncode(t *testing.T) { // This tx comes from https://github.com/cosmos/cosmos-sdk/issues/8117. - txSigned := `{"type":"lbm-sdk/StdTx","value":{"msg":[{"type":"lbm-sdk/MsgCreateValidator","value":{"description":{"moniker":"fulltest","identity":"satoshi","website":"example.com","details":"example inc"},"commission":{"rate":"0.500000000000000000","max_rate":"1.000000000000000000","max_change_rate":"0.200000000000000000"},"min_self_delegation":"1000000","delegator_address":"link120yvjfy7m2gnu9mvusrs40cxxhpt8nr3qhn8re","validator_address":"linkvaloper120yvjfy7m2gnu9mvusrs40cxxhpt8nr3jr36d2","pubkey":{"type":"ostracon/PubKeyEd25519","value":"CYrOiM3HtS7uv1B1OAkknZnFYSRpQYSYII8AtMMtev0="},"value":{"denom":"umuon","amount":"700000000"}}}],"fee":{"amount":[{"denom":"umuon","amount":"6000"}],"gas":"160000"},"signatures":[{"pub_key":{"type":"ostracon/PubKeySecp256k1","value":"AwAOXeWgNf1FjMaayrSnrOOKz+Fivr6DiI/i0x0sZCHw"},"signature":"RcnfS/u2yl7uIShTrSUlDWvsXo2p2dYu6WJC8VDVHMBLEQZWc8bsINSCjOnlsIVkUNNe1q/WCA9n3Gy1+0zhYA=="}],"memo":"","timeout_height":"0"}}` - _, legacyCdc := simapp.MakeCodecs() - + txSigned := `{"type":"lbm-sdk/StdTx","value":{"msg":[{"type":"lbm-sdk/MsgCreateValidator","value":{"description":{"moniker":"fulltest","identity":"satoshi","website":"example.com","details":"example inc"},"commission":{"rate":"0.500000000000000000","max_rate":"1.000000000000000000","max_change_rate":"0.200000000000000000"},"min_self_delegation":"1000000","delegator_address":"link14pt0q5cwf38zt08uu0n6yrstf3rndzr5057jys","validator_address":"linkvaloper14pt0q5cwf38zt08uu0n6yrstf3rndzr52q28gr","pubkey":{"type":"tendermint/PubKeyEd25519","value":"CYrOiM3HtS7uv1B1OAkknZnFYSRpQYSYII8AtMMtev0="},"value":{"denom":"umuon","amount":"700000000"}}}],"fee":{"amount":[{"denom":"umuon","amount":"6000"}],"gas":"160000"},"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AwAOXeWgNf1FjMaayrSnrOOKz+Fivr6DiI/i0x0sZCHw"},"signature":"RcnfS/u2yl7uIShTrSUlDWvsXo2p2dYu6WJC8VDVHMBLEQZWc8bsINSCjOnlsIVkUNNe1q/WCA9n3Gy1+0zhYA=="}],"memo":"","timeout_height":"0"}}` + var legacyCdc = simapp.MakeTestEncodingConfig().Amino var tx legacytx.StdTx err := legacyCdc.UnmarshalJSON([]byte(txSigned), &tx) require.NoError(t, err) diff --git a/simapp/app.go b/simapp/app.go index c11f9df2ac..dbaabdedb2 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -232,7 +232,6 @@ func NewSimApp( appOpts servertypes.AppOptions, baseAppOptions ...func(*baseapp.BaseApp), ) *SimApp { - // TODO: Remove cdc in favor of appCodec once all modules are migrated. appCodec := encodingConfig.Marshaler legacyAmino := encodingConfig.Amino interfaceRegistry := encodingConfig.InterfaceRegistry @@ -508,14 +507,6 @@ func NewSimApp( return app } -// MakeCodecs constructs the *std.Codec and *codec.LegacyAmino instances used by -// simapp. It is useful for tests and clients who do not want to construct the -// full simapp -func MakeCodecs() (codec.Marshaler, *codec.LegacyAmino) { - config := MakeTestEncodingConfig() - return config.Marshaler, config.Amino -} - // Name returns the name of the App func (app *SimApp) Name() string { return app.BaseApp.Name() } diff --git a/simapp/encoding.go b/simapp/encoding.go index 9fffb37851..bd6ed744ea 100644 --- a/simapp/encoding.go +++ b/simapp/encoding.go @@ -5,9 +5,9 @@ import ( "github.com/line/lbm-sdk/std" ) -// MakeTestEncodingConfig creates an EncodingConfig for testing. -// This function should be used only internally (in the SDK). -// App user should'nt create new codecs - use the app.AppCodec instead. +// MakeTestEncodingConfig creates an EncodingConfig for testing. This function +// should be used only in tests or when creating a new app instance (NewApp*()). +// App user shouldn't create new codecs - use the app.AppCodec instead. // [DEPRECATED] func MakeTestEncodingConfig() simappparams.EncodingConfig { encodingConfig := simappparams.MakeTestEncodingConfig() diff --git a/simapp/simd/cmd/genaccounts_test.go b/simapp/simd/cmd/genaccounts_test.go index d0edeacc62..6b45aff95d 100644 --- a/simapp/simd/cmd/genaccounts_test.go +++ b/simapp/simd/cmd/genaccounts_test.go @@ -73,7 +73,7 @@ func TestAddGenesisAccountCmd(t *testing.T) { cfg, err := genutiltest.CreateDefaultTendermintConfig(home) require.NoError(t, err) - appCodec, _ := simapp.MakeCodecs() + appCodec := simapp.MakeTestEncodingConfig().Marshaler err = genutiltest.ExecInitCmd(testMbm, home, appCodec) require.NoError(t, err) diff --git a/x/auth/ante/sigverify_test.go b/x/auth/ante/sigverify_test.go index 36473ebcb5..fd26c715d5 100644 --- a/x/auth/ante/sigverify_test.go +++ b/x/auth/ante/sigverify_test.go @@ -67,7 +67,7 @@ func (suite *AnteTestSuite) TestSetPubKey() { func (suite *AnteTestSuite) TestConsumeSignatureVerificationGas() { params := types.DefaultParams() msg := []byte{1, 2, 3, 4} - _, cdc := simapp.MakeCodecs() + cdc := simapp.MakeTestEncodingConfig().Amino pkSet1, sigSet1 := generatePubKeysAndSignatures(5, msg, false) multisigKey1 := kmultisig.NewLegacyAminoPubKey(2, pkSet1) diff --git a/x/auth/keeper/keeper_test.go b/x/auth/keeper/keeper_test.go index 91499e9f0f..7a4f2d5d6a 100644 --- a/x/auth/keeper/keeper_test.go +++ b/x/auth/keeper/keeper_test.go @@ -131,7 +131,7 @@ func TestSupply_ValidatePermissions(t *testing.T) { maccPerms[multiPerm] = []string{types.Burner, types.Minter, types.Staking} maccPerms[randomPerm] = []string{"random"} - cdc, _ := simapp.MakeCodecs() + cdc := simapp.MakeTestEncodingConfig().Marshaler keeper := keeper.NewAccountKeeper( cdc, app.GetKey(types.StoreKey), app.GetSubspace(types.ModuleName), types.ProtoBaseAccount, maccPerms, diff --git a/x/auth/simulation/decoder_test.go b/x/auth/simulation/decoder_test.go index 823fc0ffb9..abb4003715 100644 --- a/x/auth/simulation/decoder_test.go +++ b/x/auth/simulation/decoder_test.go @@ -22,7 +22,7 @@ var ( func TestDecodeStore(t *testing.T) { app := simapp.Setup(false) - cdc, _ := simapp.MakeCodecs() + cdc := simapp.MakeTestEncodingConfig().Marshaler acc := types.NewBaseAccountWithAddress(delAddr1) dec := simulation.NewDecodeStore(app.AccountKeeper) diff --git a/x/auth/types/common_test.go b/x/auth/types/common_test.go index 9900af5f0d..1773a0981c 100644 --- a/x/auth/types/common_test.go +++ b/x/auth/types/common_test.go @@ -6,5 +6,6 @@ import ( var ( app = simapp.Setup(false) - appCodec, legacyAmino = simapp.MakeCodecs() + ecdc = simapp.MakeTestEncodingConfig() + appCodec, legacyAmino = ecdc.Marshaler, ecdc.Amino ) diff --git a/x/auth/vesting/types/common_test.go b/x/auth/vesting/types/common_test.go index 0b49c46ea0..40c5d35246 100644 --- a/x/auth/vesting/types/common_test.go +++ b/x/auth/vesting/types/common_test.go @@ -5,6 +5,6 @@ import ( ) var ( - app = simapp.Setup(false) - appCodec, _ = simapp.MakeCodecs() + app = simapp.Setup(false) + appCodec = simapp.MakeTestEncodingConfig().Marshaler ) diff --git a/x/bank/keeper/keeper_test.go b/x/bank/keeper/keeper_test.go index 26c380ca5d..a356e29427 100644 --- a/x/bank/keeper/keeper_test.go +++ b/x/bank/keeper/keeper_test.go @@ -249,7 +249,7 @@ func (suite *IntegrationTestSuite) TestSupply_MintCoins() { func (suite *IntegrationTestSuite) TestSupply_BurnCoins() { app := simapp.Setup(false) ctx := app.BaseApp.NewContext(false, ostproto.Header{Height: 1}) - appCodec, _ := simapp.MakeCodecs() + appCodec := simapp.MakeTestEncodingConfig().Marshaler // add module accounts to supply keeper maccPerms := simapp.GetMaccPerms() diff --git a/x/capability/simulation/decoder_test.go b/x/capability/simulation/decoder_test.go index a5bd50b3e1..8a3db1df41 100644 --- a/x/capability/simulation/decoder_test.go +++ b/x/capability/simulation/decoder_test.go @@ -14,7 +14,7 @@ import ( ) func TestDecodeStore(t *testing.T) { - cdc, _ := simapp.MakeCodecs() + cdc := simapp.MakeTestEncodingConfig().Marshaler dec := simulation.NewDecodeStore(cdc) capOwners := types.CapabilityOwners{ diff --git a/x/distribution/simulation/decoder_test.go b/x/distribution/simulation/decoder_test.go index 546a22c12b..e6490250bd 100644 --- a/x/distribution/simulation/decoder_test.go +++ b/x/distribution/simulation/decoder_test.go @@ -22,7 +22,7 @@ var ( ) func TestDecodeDistributionStore(t *testing.T) { - cdc, _ := simapp.MakeCodecs() + cdc := simapp.MakeTestEncodingConfig().Marshaler dec := simulation.NewDecodeStore(cdc) decCoins := sdk.DecCoins{sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, sdk.OneDec())} diff --git a/x/evidence/keeper/querier_test.go b/x/evidence/keeper/querier_test.go index 2a52b2d46e..f5e148c78e 100644 --- a/x/evidence/keeper/querier_test.go +++ b/x/evidence/keeper/querier_test.go @@ -18,12 +18,12 @@ const ( func (suite *KeeperTestSuite) TestQuerier_QueryEvidence_Existing() { ctx := suite.ctx.WithIsCheckTx(false) numEvidence := 100 - _, cdc := simapp.MakeCodecs() + legacyCdc := simapp.MakeTestEncodingConfig().Amino evidence := suite.populateEvidence(ctx, numEvidence) query := abci.RequestQuery{ Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryEvidence}, "/"), - Data: cdc.MustMarshalJSON(types.NewQueryEvidenceRequest(evidence[0].Hash())), + Data: legacyCdc.MustMarshalJSON(types.NewQueryEvidenceRequest(evidence[0].Hash())), } bz, err := suite.querier(ctx, []string{types.QueryEvidence}, query) @@ -31,13 +31,13 @@ func (suite *KeeperTestSuite) TestQuerier_QueryEvidence_Existing() { suite.NotNil(bz) var e exported.Evidence - suite.Nil(cdc.UnmarshalJSON(bz, &e)) + suite.Nil(legacyCdc.UnmarshalJSON(bz, &e)) suite.Equal(evidence[0], e) } func (suite *KeeperTestSuite) TestQuerier_QueryEvidence_NonExisting() { ctx := suite.ctx.WithIsCheckTx(false) - cdc, _ := simapp.MakeCodecs() + cdc := simapp.MakeTestEncodingConfig().Marshaler numEvidence := 100 suite.populateEvidence(ctx, numEvidence) @@ -53,7 +53,7 @@ func (suite *KeeperTestSuite) TestQuerier_QueryEvidence_NonExisting() { func (suite *KeeperTestSuite) TestQuerier_QueryAllEvidence() { ctx := suite.ctx.WithIsCheckTx(false) - _, cdc := simapp.MakeCodecs() + cdc := simapp.MakeTestEncodingConfig().Amino numEvidence := 100 suite.populateEvidence(ctx, numEvidence) @@ -73,7 +73,7 @@ func (suite *KeeperTestSuite) TestQuerier_QueryAllEvidence() { func (suite *KeeperTestSuite) TestQuerier_QueryAllEvidence_InvalidPagination() { ctx := suite.ctx.WithIsCheckTx(false) - _, cdc := simapp.MakeCodecs() + cdc := simapp.MakeTestEncodingConfig().Amino numEvidence := 100 suite.populateEvidence(ctx, numEvidence) diff --git a/x/gov/keeper/common_test.go b/x/gov/keeper/common_test.go index 4665720a87..21a300e93e 100644 --- a/x/gov/keeper/common_test.go +++ b/x/gov/keeper/common_test.go @@ -21,10 +21,10 @@ func createValidators(t *testing.T, ctx sdk.Context, app *simapp.SimApp, powers addrs := simapp.AddTestAddrsIncremental(app, ctx, 5, sdk.NewInt(30000000)) valAddrs := simapp.ConvertAddrsToValAddrs(addrs) pks := simapp.CreateTestPubKeys(5) + cdc := simapp.MakeTestEncodingConfig().Marshaler - appCodec, _ := simapp.MakeCodecs() app.StakingKeeper = stakingkeeper.NewKeeper( - appCodec, + cdc, app.GetKey(stakingtypes.StoreKey), app.AccountKeeper, app.BankKeeper, diff --git a/x/gov/simulation/decoder_test.go b/x/gov/simulation/decoder_test.go index 9e1370db08..a3c33d4a78 100644 --- a/x/gov/simulation/decoder_test.go +++ b/x/gov/simulation/decoder_test.go @@ -22,11 +22,10 @@ var ( ) func TestDecodeStore(t *testing.T) { - cdc, _ := simapp.MakeCodecs() + cdc := simapp.MakeTestEncodingConfig().Marshaler dec := simulation.NewDecodeStore(cdc) endTime := time.Now().UTC() - content := types.ContentFromProposalType("test", "test", types.ProposalTypeText) proposal, err := types.NewProposal(content, 1, endTime, endTime.Add(24*time.Hour)) require.NoError(t, err) diff --git a/x/mint/simulation/decoder_test.go b/x/mint/simulation/decoder_test.go index af8cbe76a8..6460fc1c41 100644 --- a/x/mint/simulation/decoder_test.go +++ b/x/mint/simulation/decoder_test.go @@ -14,7 +14,7 @@ import ( ) func TestDecodeStore(t *testing.T) { - cdc, _ := simapp.MakeCodecs() + cdc := simapp.MakeTestEncodingConfig().Marshaler dec := simulation.NewDecodeStore(cdc) minter := types.NewMinter(sdk.OneDec(), sdk.NewDec(15)) diff --git a/x/slashing/simulation/decoder_test.go b/x/slashing/simulation/decoder_test.go index b6297d3208..76ab967202 100644 --- a/x/slashing/simulation/decoder_test.go +++ b/x/slashing/simulation/decoder_test.go @@ -25,7 +25,7 @@ var ( ) func TestDecodeStore(t *testing.T) { - cdc, _ := simapp.MakeCodecs() + cdc := simapp.MakeTestEncodingConfig().Marshaler dec := simulation.NewDecodeStore(cdc) info := types.NewValidatorSigningInfo(consAddr1, time.Now().UTC(), false, 0, 1) diff --git a/x/staking/keeper/grpc_query_test.go b/x/staking/keeper/grpc_query_test.go index 3593194bba..d0a6bbc664 100644 --- a/x/staking/keeper/grpc_query_test.go +++ b/x/staking/keeper/grpc_query_test.go @@ -725,10 +725,9 @@ func createValidators(t *testing.T, ctx sdk.Context, app *simapp.SimApp, powers addrs := simapp.AddTestAddrsIncremental(app, ctx, 5, sdk.TokensFromConsensusPower(300)) valAddrs := simapp.ConvertAddrsToValAddrs(addrs) pks := simapp.CreateTestPubKeys(5) - - appCodec, _ := simapp.MakeCodecs() + cdc := simapp.MakeTestEncodingConfig().Marshaler app.StakingKeeper = keeper.NewKeeper( - appCodec, + cdc, app.GetKey(types.StoreKey), app.AccountKeeper, app.BankKeeper, diff --git a/x/staking/simulation/decoder_test.go b/x/staking/simulation/decoder_test.go index da8ebe021f..f2a3315287 100644 --- a/x/staking/simulation/decoder_test.go +++ b/x/staking/simulation/decoder_test.go @@ -32,9 +32,8 @@ func makeTestCodec() (cdc *codec.LegacyAmino) { } func TestDecodeStore(t *testing.T) { - cdc, _ := simapp.MakeCodecs() + cdc := simapp.MakeTestEncodingConfig().Marshaler dec := simulation.NewDecodeStore(cdc) - bondTime := time.Now().UTC() val, err := types.NewValidator(valAddr1, delPk1, types.NewDescription("test", "test", "test", "test", "test")) From 31891454060153c4bb9dfdc980d376a37d9bf73a Mon Sep 17 00:00:00 2001 From: MD Aleem <72057206+aleem1314@users.noreply.github.com> Date: Fri, 8 Jan 2021 15:22:33 +0530 Subject: [PATCH 002/728] fix GET /upgrade/current query (#8280) * fix cli queries * fix codec issue * revert cli Co-authored-by: SaReN From d36ab89e5c9be438b877b62b1b70ab946751ef49 Mon Sep 17 00:00:00 2001 From: MD Aleem <72057206+aleem1314@users.noreply.github.com> Date: Fri, 8 Jan 2021 17:18:04 +0530 Subject: [PATCH 003/728] [docs] x/mint - general audit and cleanup (#8267) * update docs * update docs * review changes Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- x/mint/spec/02_state.md | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/x/mint/spec/02_state.md b/x/mint/spec/02_state.md index c4d7a3eff1..1f9eae3447 100644 --- a/x/mint/spec/02_state.md +++ b/x/mint/spec/02_state.md @@ -8,28 +8,14 @@ order: 2 The minter is a space for holding current inflation information. - - Minter: `0x00 -> amino(minter)` + - Minter: `0x00 -> ProtocolBuffer(minter)` -```go -type Minter struct { - Inflation sdk.Dec // current annual inflation rate - AnnualProvisions sdk.Dec // current annual exptected provisions -} -``` ++++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0-rc7/proto/cosmos/mint/v1beta1/mint.proto#L8-L19 ## Params Minting params are held in the global params store. - - Params: `mint/params -> amino(params)` - -```go -type Params struct { - MintDenom string // type of coin to mint - InflationRateChange sdk.Dec // maximum annual change in inflation rate - InflationMax sdk.Dec // maximum inflation rate - InflationMin sdk.Dec // minimum inflation rate - GoalBonded sdk.Dec // goal of percent bonded atoms - BlocksPerYear uint64 // expected blocks per year -} -``` + - Params: `mint/params -> legacy_amino(params)` + ++++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0-rc7/proto/cosmos/mint/v1beta1/mint.proto#L21-L53 \ No newline at end of file From 640bb364dede2770de4816f4fb2db272efc8c224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?colin=20axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Fri, 8 Jan 2021 14:00:07 +0100 Subject: [PATCH 004/728] Generic 02-client cli cmds, removes light client specific ones (#8259) * remove old light client cli cmds and replace with generic ones housed in client * remove print and add client cli getter cmds * use any as arguments * revert back to single pub key * fix tests * typo * remove todo * add upgrade cli cmd cc @AdityaSripal * register upgrade cmd --- x/auth/client/rest/rest_test.go | 18 +- x/ibc/core/02-client/client/cli/cli.go | 20 ++ x/ibc/core/02-client/client/cli/query.go | 6 +- x/ibc/core/02-client/client/cli/tx.go | 246 ++++++++++++++++++ x/ibc/core/02-client/module.go | 5 + x/ibc/core/client/cli/cli.go | 5 +- .../06-solomachine/client/cli/cli.go | 27 -- .../06-solomachine/client/cli/tx.go | 169 ------------ x/ibc/light-clients/06-solomachine/module.go | 8 - .../06-solomachine/types/misbehaviour.go | 4 +- x/ibc/light-clients/99-ostracon/module.go | 8 - 11 files changed, 289 insertions(+), 227 deletions(-) create mode 100644 x/ibc/core/02-client/client/cli/tx.go delete mode 100644 x/ibc/light-clients/06-solomachine/client/cli/cli.go delete mode 100644 x/ibc/light-clients/06-solomachine/client/cli/tx.go diff --git a/x/auth/client/rest/rest_test.go b/x/auth/client/rest/rest_test.go index 4467eb4c4c..c1ffcfe6e7 100644 --- a/x/auth/client/rest/rest_test.go +++ b/x/auth/client/rest/rest_test.go @@ -496,10 +496,20 @@ func (s *IntegrationTestSuite) testQueryIBCTx(txRes sdk.TxResponse, cmd *cobra.C func (s *IntegrationTestSuite) TestLegacyRestErrMessages() { val := s.network.Validators[0] + // Write client state json to temp file, used for an IBC message. + // Generated by printing the result of cdc.MarshalIntefaceJSON on + // a solo machine client state + clientStateJSON := testutil.WriteToNewTempFile( + s.T(), + `{"@type":"/ibc.lightclients.solomachine.v1.ClientState","sequence":"1","frozen_sequence":"0","consensus_state":{"public_key":{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AtK50+5pJOoaa04qqAqrnyAqsYrwrR/INnA6UPIaYZlp"},"diversifier":"testing","timestamp":"10"},"allow_update_after_proposal":false}`, + ) + // Write consensus json to temp file, used for an IBC message. + // Generated by printing the result of cdc.MarshalIntefaceJSON on + // a solo machine consensus state consensusJSON := testutil.WriteToNewTempFile( s.T(), - `{"public_key":{"@type":"/lbm.crypto.secp256k1.PubKey","key":"A/3SXL2ONYaOkxpdR5P8tHTlSlPv1AwQwSFxKRee5JQW"},"diversifier":"diversifier","timestamp":"10"}`, + `{"@type":"/ibc.lightclients.solomachine.v1.ConsensusState","public_key":{"@type":"/lbm.crypto.secp256k1.PubKey","key":"AtK50+5pJOoaa04qqAqrnyAqsYrwrR/INnA6UPIaYZlp"},"diversifier":"testing","timestamp":"10"}`, ) testCases := []struct { @@ -525,10 +535,10 @@ func (s *IntegrationTestSuite) TestLegacyRestErrMessages() { }, { "Successful IBC message", - ibcsolomachinecli.NewCreateClientCmd(), + ibcclientcli.NewCreateClientCmd(), []string{ - "1", // dummy sequence - consensusJSON.Name(), // path to consensus json, + clientStateJSON.Name(), // path to client state json + consensusJSON.Name(), // path to consensus json, fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), diff --git a/x/ibc/core/02-client/client/cli/cli.go b/x/ibc/core/02-client/client/cli/cli.go index d7d8b3d5f4..172d99aed2 100644 --- a/x/ibc/core/02-client/client/cli/cli.go +++ b/x/ibc/core/02-client/client/cli/cli.go @@ -29,3 +29,23 @@ func GetQueryCmd() *cobra.Command { return queryCmd } + +// NewTxCmd returns the command to create and handle IBC clients +func NewTxCmd() *cobra.Command { + txCmd := &cobra.Command{ + Use: types.SubModuleName, + Short: "IBC client transaction subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + txCmd.AddCommand( + NewCreateClientCmd(), + NewUpdateClientCmd(), + NewSubmitMisbehaviourCmd(), + NewUpgradeClientCmd(), + ) + + return txCmd +} diff --git a/x/ibc/core/02-client/client/cli/query.go b/x/ibc/core/02-client/client/cli/query.go index 9a94fb98e4..089675f1ca 100644 --- a/x/ibc/core/02-client/client/cli/query.go +++ b/x/ibc/core/02-client/client/cli/query.go @@ -193,12 +193,11 @@ func GetCmdQueryHeader() *cobra.Command { if err != nil { return err } - header, height, err := utils.QueryOstraconHeader(clientCtx) + header, _, err := utils.QueryOstraconHeader(clientCtx) if err != nil { return err } - clientCtx = clientCtx.WithHeight(height) return clientCtx.PrintProto(&header) }, } @@ -222,12 +221,11 @@ func GetCmdNodeConsensusState() *cobra.Command { if err != nil { return err } - state, height, err := utils.QueryNodeConsensusState(clientCtx) + state, _, err := utils.QueryNodeConsensusState(clientCtx) if err != nil { return err } - clientCtx = clientCtx.WithHeight(height) return clientCtx.PrintProto(state) }, } diff --git a/x/ibc/core/02-client/client/cli/tx.go b/x/ibc/core/02-client/client/cli/tx.go new file mode 100644 index 0000000000..28cdf2e05a --- /dev/null +++ b/x/ibc/core/02-client/client/cli/tx.go @@ -0,0 +1,246 @@ +package cli + +import ( + "fmt" + "io/ioutil" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/line/lbm-sdk/client" + "github.com/line/lbm-sdk/client/flags" + "github.com/line/lbm-sdk/client/tx" + "github.com/line/lbm-sdk/codec" + "github.com/line/lbm-sdk/version" + "github.com/line/lbm-sdk/x/ibc/core/02-client/types" + "github.com/line/lbm-sdk/x/ibc/core/exported" +) + +// NewCreateClientCmd defines the command to create a new IBC light client. +func NewCreateClientCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "create [path/to/client_state.json] [path/to/consensus_state.json]", + Short: "create new IBC client", + Long: `create a new IBC client with the specified client state and consensus state + - ClientState JSON example: {"@type":"/ibc.lightclients.solomachine.v1.ClientState","sequence":"1","frozen_sequence":"0","consensus_state":{"public_key":{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AtK50+5pJOoaa04qqAqrnyAqsYrwrR/INnA6UPIaYZlp"},"diversifier":"testing","timestamp":"10"},"allow_update_after_proposal":false} + - ConsensusState JSON example: {"@type":"/ibc.lightclients.solomachine.v1.ConsensusState","public_key":{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AtK50+5pJOoaa04qqAqrnyAqsYrwrR/INnA6UPIaYZlp"},"diversifier":"testing","timestamp":"10"}`, + Example: fmt.Sprintf("%s tx ibc %s create [path/to/client_state.json] [path/to/consensus_state.json] --from node0 --home ../node0/cli --chain-id $CID", version.AppName, types.SubModuleName), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry) + + // attempt to unmarshal client state argument + var clientState exported.ClientState + clientContentOrFileName := args[0] + if err := cdc.UnmarshalInterfaceJSON([]byte(clientContentOrFileName), &clientState); err != nil { + + // check for file path if JSON input is not provided + contents, err := ioutil.ReadFile(clientContentOrFileName) + if err != nil { + return errors.Wrap(err, "neither JSON input nor path to .json file for client state were provided") + } + + if err := cdc.UnmarshalInterfaceJSON(contents, &clientState); err != nil { + return errors.Wrap(err, "error unmarshalling client state file") + } + } + + // attempt to unmarshal consensus state argument + var consensusState exported.ConsensusState + consensusContentOrFileName := args[1] + if err := cdc.UnmarshalInterfaceJSON([]byte(consensusContentOrFileName), &consensusState); err != nil { + + // check for file path if JSON input is not provided + contents, err := ioutil.ReadFile(consensusContentOrFileName) + if err != nil { + return errors.Wrap(err, "neither JSON input nor path to .json file for consensus state were provided") + } + + if err := cdc.UnmarshalInterfaceJSON(contents, &consensusState); err != nil { + return errors.Wrap(err, "error unmarshalling consensus state file") + } + } + + msg, err := types.NewMsgCreateClient(clientState, consensusState, clientCtx.GetFromAddress()) + if err != nil { + return err + } + + if err := msg.ValidateBasic(); err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + +// NewUpdateClientCmd defines the command to update an IBC client. +func NewUpdateClientCmd() *cobra.Command { + return &cobra.Command{ + Use: "update [client-id] [path/to/header.json]", + Short: "update existing client with a header", + Long: "update existing client with a header", + Example: fmt.Sprintf("%s tx ibc %s update [client-id] [path/to/header.json] --from node0 --home ../node0/cli --chain-id $CID", version.AppName, types.SubModuleName), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + clientID := args[0] + + cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry) + + var header exported.Header + headerContentOrFileName := args[1] + if err := cdc.UnmarshalInterfaceJSON([]byte(headerContentOrFileName), &header); err != nil { + + // check for file path if JSON input is not provided + contents, err := ioutil.ReadFile(headerContentOrFileName) + if err != nil { + return errors.Wrap(err, "neither JSON input nor path to .json file for header were provided") + } + + if err := cdc.UnmarshalInterfaceJSON(contents, &header); err != nil { + return errors.Wrap(err, "error unmarshalling header file") + } + } + + msg, err := types.NewMsgUpdateClient(clientID, header, clientCtx.GetFromAddress()) + if err != nil { + return err + } + + if err := msg.ValidateBasic(); err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } +} + +// NewSubmitMisbehaviourCmd defines the command to submit a misbehaviour to prevent +// future updates. +func NewSubmitMisbehaviourCmd() *cobra.Command { + return &cobra.Command{ + Use: "misbehaviour [path/to/misbehaviour.json]", + Short: "submit a client misbehaviour", + Long: "submit a client misbehaviour to prevent future updates", + Example: fmt.Sprintf("%s tx ibc %s misbehaviour [path/to/misbehaviour.json] --from node0 --home ../node0/cli --chain-id $CID", version.AppName, types.SubModuleName), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry) + + var misbehaviour exported.Misbehaviour + misbehaviourContentOrFileName := args[0] + if err := cdc.UnmarshalInterfaceJSON([]byte(misbehaviourContentOrFileName), &misbehaviour); err != nil { + + // check for file path if JSON input is not provided + contents, err := ioutil.ReadFile(misbehaviourContentOrFileName) + if err != nil { + return errors.Wrap(err, "neither JSON input nor path to .json file for misbehaviour were provided") + } + + if err := cdc.UnmarshalInterfaceJSON(contents, misbehaviour); err != nil { + return errors.Wrap(err, "error unmarshalling misbehaviour file") + } + } + + msg, err := types.NewMsgSubmitMisbehaviour(misbehaviour.GetClientID(), misbehaviour, clientCtx.GetFromAddress()) + if err != nil { + return err + } + + if err := msg.ValidateBasic(); err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } +} + +// NewUpgradeClientCmd defines the command to upgrade an IBC light client. +func NewUpgradeClientCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "upgrade [client-identifier] [path/to/client_state.json] [path/to/consensus_state.json] [upgrade-client-proof] [upgrade-consensus-state-proof]", + Short: "upgrade an IBC client", + Long: `upgrade the IBC client associated with the provided client identifier while providing proof committed by the counterparty chain to the new client and consensus states + - ClientState JSON example: {"@type":"/ibc.lightclients.solomachine.v1.ClientState","sequence":"1","frozen_sequence":"0","consensus_state":{"public_key":{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AtK50+5pJOoaa04qqAqrnyAqsYrwrR/INnA6UPIaYZlp"},"diversifier":"testing","timestamp":"10"},"allow_update_after_proposal":false} + - ConsensusState JSON example: {"@type":"/ibc.lightclients.solomachine.v1.ConsensusState","public_key":{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AtK50+5pJOoaa04qqAqrnyAqsYrwrR/INnA6UPIaYZlp"},"diversifier":"testing","timestamp":"10"}`, + Example: fmt.Sprintf("%s tx ibc %s upgrade [client-identifier] [path/to/client_state.json] [path/to/consensus_state.json] [client-state-proof] [consensus-state-proof] --from node0 --home ../node0/cli --chain-id $CID", version.AppName, types.SubModuleName), + Args: cobra.ExactArgs(5), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry) + clientID := args[0] + + // attempt to unmarshal client state argument + var clientState exported.ClientState + clientContentOrFileName := args[1] + if err := cdc.UnmarshalInterfaceJSON([]byte(clientContentOrFileName), &clientState); err != nil { + + // check for file path if JSON input is not provided + contents, err := ioutil.ReadFile(clientContentOrFileName) + if err != nil { + return errors.Wrap(err, "neither JSON input nor path to .json file for client state were provided") + } + + if err := cdc.UnmarshalInterfaceJSON(contents, &clientState); err != nil { + return errors.Wrap(err, "error unmarshalling client state file") + } + } + + // attempt to unmarshal consensus state argument + var consensusState exported.ConsensusState + consensusContentOrFileName := args[2] + if err := cdc.UnmarshalInterfaceJSON([]byte(consensusContentOrFileName), &consensusState); err != nil { + + // check for file path if JSON input is not provided + contents, err := ioutil.ReadFile(consensusContentOrFileName) + if err != nil { + return errors.Wrap(err, "neither JSON input nor path to .json file for consensus state were provided") + } + + if err := cdc.UnmarshalInterfaceJSON(contents, &consensusState); err != nil { + return errors.Wrap(err, "error unmarshalling consensus state file") + } + } + + proofUpgradeClient := []byte(args[3]) + proofUpgradeConsensus := []byte(args[4]) + + msg, err := types.NewMsgUpgradeClient(clientID, clientState, consensusState, proofUpgradeClient, proofUpgradeConsensus, clientCtx.GetFromAddress()) + if err != nil { + return err + } + + if err := msg.ValidateBasic(); err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} diff --git a/x/ibc/core/02-client/module.go b/x/ibc/core/02-client/module.go index add6ae5528..9523a59a39 100644 --- a/x/ibc/core/02-client/module.go +++ b/x/ibc/core/02-client/module.go @@ -18,6 +18,11 @@ func GetQueryCmd() *cobra.Command { return cli.GetQueryCmd() } +// GetTxCmd returns the root tx command for 02-client. +func GetTxCmd() *cobra.Command { + return cli.NewTxCmd() +} + // RegisterQueryService registers the gRPC query service for IBC client. func RegisterQueryService(server grpc.Server, queryServer types.QueryServer) { types.RegisterQueryServer(server, queryServer) diff --git a/x/ibc/core/client/cli/cli.go b/x/ibc/core/client/cli/cli.go index 5d94051eb8..e4a0af1503 100644 --- a/x/ibc/core/client/cli/cli.go +++ b/x/ibc/core/client/cli/cli.go @@ -8,8 +8,6 @@ import ( connection "github.com/line/lbm-sdk/x/ibc/core/03-connection" channel "github.com/line/lbm-sdk/x/ibc/core/04-channel" host "github.com/line/lbm-sdk/x/ibc/core/24-host" - solomachine "github.com/line/lbm-sdk/x/ibc/light-clients/06-solomachine" - tendermint "github.com/line/lbm-sdk/x/ibc/light-clients/99-ostracon" ) // GetTxCmd returns the transaction commands for this module @@ -23,8 +21,7 @@ func GetTxCmd() *cobra.Command { } ibcTxCmd.AddCommand( - solomachine.GetTxCmd(), - tendermint.GetTxCmd(), + ibcclient.GetTxCmd(), connection.GetTxCmd(), channel.GetTxCmd(), ) diff --git a/x/ibc/light-clients/06-solomachine/client/cli/cli.go b/x/ibc/light-clients/06-solomachine/client/cli/cli.go deleted file mode 100644 index 23c492d5a1..0000000000 --- a/x/ibc/light-clients/06-solomachine/client/cli/cli.go +++ /dev/null @@ -1,27 +0,0 @@ -package cli - -import ( - "github.com/spf13/cobra" - - "github.com/line/lbm-sdk/client" - "github.com/line/lbm-sdk/x/ibc/light-clients/06-solomachine/types" -) - -// NewTxCmd returns a root CLI command handler for all solo machine transaction commands. -func NewTxCmd() *cobra.Command { - txCmd := &cobra.Command{ - Use: types.SubModuleName, - Short: "Solo Machine transaction subcommands", - DisableFlagParsing: true, - SuggestionsMinimumDistance: 2, - RunE: client.ValidateCmd, - } - - txCmd.AddCommand( - NewCreateClientCmd(), - NewUpdateClientCmd(), - NewSubmitMisbehaviourCmd(), - ) - - return txCmd -} diff --git a/x/ibc/light-clients/06-solomachine/client/cli/tx.go b/x/ibc/light-clients/06-solomachine/client/cli/tx.go deleted file mode 100644 index 0fa8eda43c..0000000000 --- a/x/ibc/light-clients/06-solomachine/client/cli/tx.go +++ /dev/null @@ -1,169 +0,0 @@ -package cli - -import ( - "fmt" - "io/ioutil" - "strconv" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/line/lbm-sdk/client" - "github.com/line/lbm-sdk/client/flags" - "github.com/line/lbm-sdk/client/tx" - "github.com/line/lbm-sdk/codec" - "github.com/line/lbm-sdk/version" - clienttypes "github.com/line/lbm-sdk/x/ibc/core/02-client/types" - "github.com/line/lbm-sdk/x/ibc/light-clients/06-solomachine/types" -) - -const ( - flagAllowUpdateAfterProposal = "allow_update_after_proposal" -) - -// NewCreateClientCmd defines the command to create a new solo machine client. -func NewCreateClientCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "create [sequence] [path/to/consensus_state.json]", - Short: "create new solo machine client", - Long: `create a new solo machine client with the specified identifier and public key - - ConsensusState json example: {"public_key":{"@type":"/lbm.crypto.secp256k1.PubKey","key":"A/3SXL2ONYaOkxpdR5P8tHTlSlPv1AwQwSFxKRee5JQW"},"diversifier":"diversifier","timestamp":"10"}`, - Example: fmt.Sprintf("%s tx ibc %s create [sequence] [path/to/consensus_state] --from node0 --home ../node0/cli --chain-id $CID", version.AppName, types.SubModuleName), - Args: cobra.ExactArgs(2), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - sequence, err := strconv.ParseUint(args[0], 10, 64) - if err != nil { - return err - } - - cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry) - - // attempt to unmarshal consensus state argument - consensusState := &types.ConsensusState{} - if err := cdc.UnmarshalJSON([]byte(args[1]), consensusState); err != nil { - - // check for file path if JSON input is not provided - contents, err := ioutil.ReadFile(args[1]) - if err != nil { - return errors.Wrap(err, "neither JSON input nor path to .json file for consensus state were provided") - } - - if err := cdc.UnmarshalJSON(contents, consensusState); err != nil { - return errors.Wrap(err, "error unmarshalling consensus state file") - } - } - - allowUpdateAfterProposal, _ := cmd.Flags().GetBool(flagAllowUpdateAfterProposal) - - clientState := types.NewClientState(sequence, consensusState, allowUpdateAfterProposal) - msg, err := clienttypes.NewMsgCreateClient(clientState, consensusState, clientCtx.GetFromAddress()) - if err != nil { - return err - } - - if err := msg.ValidateBasic(); err != nil { - return err - } - - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) - }, - } - - cmd.Flags().Bool(flagAllowUpdateAfterProposal, false, "allow governance proposal to update client") - flags.AddTxFlagsToCmd(cmd) - - return cmd -} - -// NewUpdateClientCmd defines the command to update a solo machine client. -func NewUpdateClientCmd() *cobra.Command { - return &cobra.Command{ - Use: "update [client-id] [path/to/header.json]", - Short: "update existing client with a header", - Long: "update existing client with a solo machine header", - Example: fmt.Sprintf("%s tx ibc %s update [client-id] [path/to/header.json] --from node0 --home ../node0/cli --chain-id $CID", version.AppName, types.SubModuleName), - Args: cobra.ExactArgs(2), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - clientID := args[0] - - cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry) - - header := &types.Header{} - if err := cdc.UnmarshalJSON([]byte(args[1]), header); err != nil { - - // check for file path if JSON input is not provided - contents, err := ioutil.ReadFile(args[1]) - if err != nil { - return errors.Wrap(err, "neither JSON input nor path to .json file for header were provided") - } - - if err := cdc.UnmarshalJSON(contents, header); err != nil { - return errors.Wrap(err, "error unmarshalling header file") - } - } - - msg, err := clienttypes.NewMsgUpdateClient(clientID, header, clientCtx.GetFromAddress()) - if err != nil { - return err - } - - if err := msg.ValidateBasic(); err != nil { - return err - } - - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) - }, - } -} - -// NewSubmitMisbehaviourCmd defines the command to submit a misbehaviour to prevent -// future updates. -func NewSubmitMisbehaviourCmd() *cobra.Command { - return &cobra.Command{ - Use: "misbehaviour [path/to/misbehaviour.json]", - Short: "submit a client misbehaviour", - Long: "submit a client misbehaviour to prevent future updates", - Example: fmt.Sprintf("%s tx ibc %s misbehaviour [path/to/misbehaviour.json] --from node0 --home ../node0/cli --chain-id $CID", version.AppName, types.SubModuleName), - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry) - - m := &types.Misbehaviour{} - if err := cdc.UnmarshalJSON([]byte(args[0]), m); err != nil { - - // check for file path if JSON input is not provided - contents, err := ioutil.ReadFile(args[0]) - if err != nil { - return errors.Wrap(err, "neither JSON input nor path to .json file for misbehaviour were provided") - } - - if err := cdc.UnmarshalJSON(contents, m); err != nil { - return errors.Wrap(err, "error unmarshalling misbehaviour file") - } - } - - msg, err := clienttypes.NewMsgSubmitMisbehaviour(m.ClientId, m, clientCtx.GetFromAddress()) - if err != nil { - return err - } - - if err := msg.ValidateBasic(); err != nil { - return err - } - - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) - }, - } -} diff --git a/x/ibc/light-clients/06-solomachine/module.go b/x/ibc/light-clients/06-solomachine/module.go index dc7604c42d..b41becb189 100644 --- a/x/ibc/light-clients/06-solomachine/module.go +++ b/x/ibc/light-clients/06-solomachine/module.go @@ -1,9 +1,6 @@ package solomachine import ( - "github.com/spf13/cobra" - - "github.com/line/lbm-sdk/x/ibc/light-clients/06-solomachine/client/cli" "github.com/line/lbm-sdk/x/ibc/light-clients/06-solomachine/types" ) @@ -11,8 +8,3 @@ import ( func Name() string { return types.SubModuleName } - -// GetTxCmd returns the root tx command for the solo machine client. -func GetTxCmd() *cobra.Command { - return cli.NewTxCmd() -} diff --git a/x/ibc/light-clients/06-solomachine/types/misbehaviour.go b/x/ibc/light-clients/06-solomachine/types/misbehaviour.go index 12bc796647..42210e16fc 100644 --- a/x/ibc/light-clients/06-solomachine/types/misbehaviour.go +++ b/x/ibc/light-clients/06-solomachine/types/misbehaviour.go @@ -9,9 +9,7 @@ import ( "github.com/line/lbm-sdk/x/ibc/core/exported" ) -var ( - _ exported.Misbehaviour = (*Misbehaviour)(nil) -) +var _ exported.Misbehaviour = &Misbehaviour{} // ClientType is a Solo Machine light client. func (misbehaviour Misbehaviour) ClientType() string { diff --git a/x/ibc/light-clients/99-ostracon/module.go b/x/ibc/light-clients/99-ostracon/module.go index 49435565d4..8b36700225 100644 --- a/x/ibc/light-clients/99-ostracon/module.go +++ b/x/ibc/light-clients/99-ostracon/module.go @@ -1,9 +1,6 @@ package tendermint import ( - "github.com/spf13/cobra" - - "github.com/line/lbm-sdk/x/ibc/light-clients/99-ostracon/client/cli" "github.com/line/lbm-sdk/x/ibc/light-clients/99-ostracon/types" ) @@ -11,8 +8,3 @@ import ( func Name() string { return types.SubModuleName } - -// GetTxCmd returns the root tx command for the IBC client -func GetTxCmd() *cobra.Command { - return cli.NewTxCmd() -} From 505c04305f084e6fdf0547850caa263b3abd1c42 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Jan 2021 13:41:03 +0000 Subject: [PATCH 005/728] build(deps): bump github.com/armon/go-metrics from 0.3.5 to 0.3.6 (#8273) * build(deps): bump github.com/armon/go-metrics from 0.3.5 to 0.3.6 Bumps [github.com/armon/go-metrics](https://github.com/armon/go-metrics) from 0.3.5 to 0.3.6. - [Release notes](https://github.com/armon/go-metrics/releases) - [Commits](https://github.com/armon/go-metrics/compare/v0.3.5...v0.3.6) Signed-off-by: dependabot[bot] * go.sum Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Federico Kunze From 631b90d8df637540f565366abeda8c97a94c2171 Mon Sep 17 00:00:00 2001 From: Cory Date: Sat, 9 Jan 2021 03:59:07 -0800 Subject: [PATCH 006/728] v0.40.0 Changelog backport & docs publish (#8285) * Stargate Release PR -- v0.40.0 Final (#8284) * v0.40.0 final changelog & release notes * Trigger Build * update docs/versions to render v0.40 docs site From a267d5f41fa2307417b43affda2b66f53dfb1a32 Mon Sep 17 00:00:00 2001 From: Robert Zaremba Date: Sat, 9 Jan 2021 13:08:46 +0100 Subject: [PATCH 007/728] 8058 fix zero time checks (#8282) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> From 7515cf5442a0fef4b554cc5f92351d48ec7d664d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Jan 2021 10:16:17 +0000 Subject: [PATCH 008/728] build(deps): bump github.com/gogo/protobuf from 1.3.1 to 1.3.2 (#8291) Bumps [github.com/gogo/protobuf](https://github.com/gogo/protobuf) from 1.3.1 to 1.3.2. - [Release notes](https://github.com/gogo/protobuf/releases) - [Commits](https://github.com/gogo/protobuf/compare/v1.3.1...v1.3.2) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From a00a298343476d12f0c18aff5b3e5d790555f809 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Jan 2021 08:38:03 -0300 Subject: [PATCH 009/728] build(deps): bump gaurav-nelson/github-action-markdown-link-check (#8290) Bumps [gaurav-nelson/github-action-markdown-link-check](https://github.com/gaurav-nelson/github-action-markdown-link-check) from 1.0.11 to 1.0.12. - [Release notes](https://github.com/gaurav-nelson/github-action-markdown-link-check/releases) - [Commits](https://github.com/gaurav-nelson/github-action-markdown-link-check/compare/1.0.11...0fe4911067fa322422f325b002d2038ba5602170) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/linkchecker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linkchecker.yml b/.github/workflows/linkchecker.yml index 84937d8375..887e990358 100644 --- a/.github/workflows/linkchecker.yml +++ b/.github/workflows/linkchecker.yml @@ -6,7 +6,7 @@ jobs: markdown-link-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: gaurav-nelson/github-action-markdown-link-check@1.0.11 + - uses: actions/checkout@master + - uses: gaurav-nelson/github-action-markdown-link-check@1.0.12 with: folder-path: "docs" From 1b7c61b406e1c6ae92c04a38f5c25705d68d701c Mon Sep 17 00:00:00 2001 From: atheeshp <59333759+atheeshp@users.noreply.github.com> Date: Mon, 11 Jan 2021 17:41:22 +0530 Subject: [PATCH 010/728] x/{auth, bank, distr} docs basic cleanup (#8268) * basic code clean up * revert MsgVerifyInvariant * review changes * review changes * review changes * review changes * review changes * review changes Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- x/auth/spec/05_vesting.md | 10 ++-- x/bank/spec/03_messages.md | 2 +- x/distribution/spec/02_state.md | 8 ++-- x/distribution/spec/04_messages.md | 75 ++++++++++++++++-------------- 4 files changed, 49 insertions(+), 46 deletions(-) diff --git a/x/auth/spec/05_vesting.md b/x/auth/spec/05_vesting.md index d5e0daecba..a253f7de60 100644 --- a/x/auth/spec/05_vesting.md +++ b/x/auth/spec/05_vesting.md @@ -90,16 +90,16 @@ type VestingAccount interface { } ``` ### BaseVestingAccount -+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/vesting/v1/vesting.proto#L10-L33 ++++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/vesting/v1beta1/vesting.proto#L10-L33 ### ContinuousVestingAccount -+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/vesting/v1/vesting.proto#L35-L43 ++++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/vesting/v1beta1/vesting.proto#L35-L43 ### DelayedVestingAccount -+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/vesting/v1/vesting.proto#L45-L53 ++++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/vesting/v1beta1/vesting.proto#L45-L53 ### Period -+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/vesting/v1/vesting.proto#L56-L62 ++++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/vesting/v1beta1/vesting.proto#L56-L62 ```go // Stores all vesting periods passed as part of a PeriodicVestingAccount @@ -108,7 +108,7 @@ type Periods []Period ``` ### PeriodicVestingAccount -+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/vesting/v1/vesting.proto#L64-L73 ++++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/vesting/v1beta1/vesting.proto#L64-L73 In order to facilitate less ad-hoc type checking and assertions and to support flexibility in account balance usage, the existing `x/bank` `ViewKeeper` interface diff --git a/x/bank/spec/03_messages.md b/x/bank/spec/03_messages.md index 72a039d689..07c42f5f78 100644 --- a/x/bank/spec/03_messages.md +++ b/x/bank/spec/03_messages.md @@ -6,7 +6,7 @@ order: 3 ## MsgSend -+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1/tx.proto#L19-L28 ++++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28 `handleMsgSend` just runs `inputOutputCoins`. diff --git a/x/distribution/spec/02_state.md b/x/distribution/spec/02_state.md index 7dd01fd6cc..b8dbde4a9a 100644 --- a/x/distribution/spec/02_state.md +++ b/x/distribution/spec/02_state.md @@ -15,7 +15,7 @@ for fractions of coins to be received from operations like inflation. When coins are distributed from the pool they are truncated back to `sdk.Coins` which are non-decimal. -- FeePool: `0x00 -> ProtocolBuffer(FeePool)` +- FeePool: `0x00 -> ProtocolBuffer(FeePool)` ```go // coins with decimal @@ -27,7 +27,7 @@ type DecCoin struct { } ``` -+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/distribution/v1/distribution.proto#L94-L101 ++++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/distribution/v1beta1/distribution.proto#L94-L101 ## Validator Distribution @@ -38,7 +38,7 @@ Validator distribution information for the relevant validator is updated each ti 3. any delegator withdraws from a validator, or 4. the validator withdraws it's commission. -- ValidatorDistInfo: `0x02 | ValOperatorAddrLen (1 byte) | ValOperatorAddr -> ProtocolBuffer(validatorDistribution)` +- ValidatorDistInfo: `0x02 | ValOperatorAddr -> ProtocolBuffer(validatorDistribution)` ```go type ValidatorDistInfo struct { @@ -56,7 +56,7 @@ properties change (aka bonded tokens etc.) its properties will remain constant and the delegator's _accumulation_ factor can be calculated passively knowing only the height of the last withdrawal and its current properties. -- DelegationDistInfo: `0x02 | DelegatorAddrLen (1 byte) | DelegatorAddr | ValOperatorAddrLen (1 byte) | ValOperatorAddr -> ProtocolBuffer(delegatorDist)` +- DelegationDistInfo: `0x02 | DelegatorAddr | ValOperatorAddr -> ProtocolBuffer(delegatorDist)` ```go type DelegationDistInfo struct { diff --git a/x/distribution/spec/04_messages.md b/x/distribution/spec/04_messages.md index 26a345ba75..51f6de1b4c 100644 --- a/x/distribution/spec/04_messages.md +++ b/x/distribution/spec/04_messages.md @@ -9,7 +9,7 @@ order: 4 By default a withdrawal address is delegator address. If a delegator wants to change it's withdrawal address it must send `MsgSetWithdrawAddress`. -+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/distribution/v1/tx.proto#L29-L37 ++++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37 ```go @@ -30,36 +30,30 @@ func (k Keeper) SetWithdrawAddr(ctx sdk.Context, delegatorAddr sdk.AccAddress, w Under special circumstances a delegator may wish to withdraw rewards from only a single validator. -+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/distribution/v1/tx.proto#L42-L50 ++++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50 ```go -// withdraw rewards from a delegation -func (k Keeper) WithdrawDelegationRewards(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sdk.Coins, error) { - val := k.stakingKeeper.Validator(ctx, valAddr) - if val == nil { - return nil, types.ErrNoValidatorDistInfo - } - - del := k.stakingKeeper.Delegation(ctx, delAddr, valAddr) - if del == nil { - return nil, types.ErrEmptyDelegationDistInfo - } - - // withdraw rewards - rewards, err := k.withdrawDelegationRewards(ctx, val, del) - if err != nil { - return nil, err - } - - // reinitialize the delegation - k.initializeDelegation(ctx, valAddr, delAddr) - return rewards, nil -} +func WithdrawDelegationReward(delegatorAddr, validatorAddr, withdrawAddr sdk.AccAddress) + height = GetHeight() + + // get all distribution scenarios + pool = staking.GetPool() + feePool = GetFeePool() + delInfo = GetDelegationDistInfo(delegatorAddr, + validatorAddr) + valInfo = GetValidatorDistInfo(validatorAddr) + validator = GetValidator(validatorAddr) + + feePool, withdraw = delInfo.WithdrawRewards(feePool, valInfo, height, pool.BondedTokens, + validator.Tokens, validator.DelegatorShares, validator.Commission) + + SetFeePool(feePool) + SendCoins(distributionModuleAcc, withdrawAddr, withdraw.TruncateDecimal()) ``` -## Withdraw Validator Rewards All +### Withdraw Validator Rewards All -When a validator wishes to withdraw their rewards it must send an +When a validator wishes to withdraw their rewards it must send array of `MsgWithdrawDelegatorReward`. Note that parts of this transaction logic are also triggered each with any change in individual delegations, such as an unbond, redelegation, or delegation of additional tokens to a specific validator. This @@ -68,17 +62,26 @@ earning on their self-delegation. ```go -for _, valAddr := range validators { - val, err := sdk.ValAddressFromBech32(valAddr) - if err != nil { - return err - } - - msg := types.NewMsgWithdrawDelegatorReward(delAddr, val) - if err := msg.ValidateBasic(); err != nil { - return err +func WithdrawValidatorRewardsAll(operatorAddr, withdrawAddr sdk.AccAddress) + + height = GetHeight() + feePool = GetFeePool() + pool = GetPool() + ValInfo = GetValidatorDistInfo(delegation.ValidatorAddr) + validator = GetValidator(delegation.ValidatorAddr) + + for _, valAddr := range validators { + val, err := sdk.ValAddressFromBech32(valAddr) + if err != nil { + return err + } + + msg := types.NewMsgWithdrawDelegatorReward(delAddr, val) + if err := msg.ValidateBasic(); err != nil { + return err + } + msgs = append(msgs, msg) } - msgs = append(msgs, msg) } ``` From 3068a614bb0420376c8a984efff9d1dc3b8c1593 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Mon, 11 Jan 2021 21:08:56 +0000 Subject: [PATCH 011/728] fix reproducible builds (#8300) From 6c60a40012a7580eb02fd1b4fe417584deadf300 Mon Sep 17 00:00:00 2001 From: SaReN Date: Tue, 12 Jan 2021 13:17:47 +0530 Subject: [PATCH 012/728] Fix sequence value in auth sign signature only (#8287) * fix signature only * add test, changelog * update test * Update CHANGELOG.md * address suggestions Co-authored-by: Alessio Treglia Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> --- x/auth/client/testutil/helpers.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/x/auth/client/testutil/helpers.go b/x/auth/client/testutil/helpers.go index e50465f0f5..b5ace914db 100644 --- a/x/auth/client/testutil/helpers.go +++ b/x/auth/client/testutil/helpers.go @@ -92,18 +92,4 @@ func QueryAccountExec(clientCtx client.Context, address fmt.Stringer, extraArgs return clitestutil.ExecTestCLICmd(clientCtx, cli.GetAccountCmd(), append(args, extraArgs...)) } -func TxMultiSignBatchExec(clientCtx client.Context, filename string, from string, sigFile1 string, sigFile2 string, extraArgs ...string) (testutil.BufferWriter, error) { - args := []string{ - fmt.Sprintf("--%s=%s", flags.FlagKeyringBackend, keyring.BackendTest), - filename, - from, - sigFile1, - sigFile2, - } - - args = append(args, extraArgs...) - - return clitestutil.ExecTestCLICmd(clientCtx, cli.GetMultiSignBatchCmd(), args) -} - // DONTCOVER From 220521cf69fd3db17380a9634620f861ace4d7aa Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Tue, 12 Jan 2021 10:08:42 +0000 Subject: [PATCH 013/728] fix library file path (#8301) Co-authored-by: SaReN From 19ba2795107b48f572a23c71e98caafa99f40aa1 Mon Sep 17 00:00:00 2001 From: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Date: Tue, 12 Jan 2021 16:54:27 -0300 Subject: [PATCH 014/728] deps: bump tendermint to v0.34.2 (#8316) * deps: bump tendermint to v0.34.2 * changelog * tidy From d9941530b264be068f79012e83977f4bbff5a17c Mon Sep 17 00:00:00 2001 From: Jonathan Gimeno Date: Wed, 13 Jan 2021 10:52:54 +0100 Subject: [PATCH 015/728] add cosmovisot to running a node (#8320) --- docs/run-node/cosmovisor.md | 174 ++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 docs/run-node/cosmovisor.md diff --git a/docs/run-node/cosmovisor.md b/docs/run-node/cosmovisor.md new file mode 100644 index 0000000000..56ee5f7fa7 --- /dev/null +++ b/docs/run-node/cosmovisor.md @@ -0,0 +1,174 @@ +# Cosmosvisor Quick Start + +`cosmovisor` is a small process manager around Cosmos SDK binaries that uses the upgrade module to allow +for smooth and configurable management of upgrading binaries as a live chain is upgraded, and can be +used to simplify validator operations while doing upgrades or to make syncing a full node for genesis +simple. The `cosmovisor` program monitors the stdout of Cosmos SDK application's executable to look for +messages from the upgrade module indicating a pending or required upgrade and act appropriately. + +## Installation + +Run: + +`go get github.com/cosmos/cosmos-sdk/cosmovisor/cmd/cosmovisor` + +## Command Line Arguments And Environment Variables + +All arguments passed to the `cosmovisor` program will be passed to the current daemon binary (as a subprocess). +It will return `/dev/stdout` and `/dev/stderr` of the subprocess as its own. Because of that, it cannot accept +any command line arguments, nor print anything to output (unless it terminates unexpectedly before executing a +binary). + +`cosmovisor` reads its configuration from environment variables: + +* `DAEMON_HOME` is the location where upgrade binaries should be kept (e.g. `$HOME/.gaiad` or `$HOME/.xrnd`). +* `DAEMON_NAME` is the name of the binary itself (eg. `xrnd`, `gaiad`, `simd`, etc). +* `DAEMON_ALLOW_DOWNLOAD_BINARIES` (*optional*) if set to `true` will enable auto-downloading of new binaries +(for security reasons, this is intended for full nodes rather than validators). +* `DAEMON_RESTART_AFTER_UPGRADE` (*optional*) if set to `true` it will restart the sub-process with the same +command line arguments and flags (but new binary) after a successful upgrade. By default, `cosmovisor` dies +afterwards and allows the supervisor to restart it if needed. Note that this will not auto-restart the child +if there was an error. + +## Data Folder Layout + +`$DAEMON_HOME/cosmovisor` is expected to belong completely to `cosmovisor` and +subprocesses that are controlled by it. The folder content is organised as follows: + +``` +. +├── current -> genesis or upgrades/ +├── genesis +│   └── bin +│   └── $DAEMON_NAME +└── upgrades + └── + └── bin + └── $DAEMON_NAME +``` + +Each version of the Cosmos SDK application is stored under either `genesis` or `upgrades/`, which holds `bin/$DAEMON_NAME` +along with any other needed files such as auxiliary client programs or libraries. `current` is a symbolic link to the currently +active folder (so `current/bin/$DAEMON_NAME` is the currently active binary). + +*Note: the `name` variable in `upgrades/` holds the URI-encoded name of the upgrade as specified in the upgrade module plan.* + +Please note that `$DAEMON_HOME/cosmovisor` just stores the *binaries* and associated *program code*. +The `cosmovisor` binary can be stored in any typical location (eg `/usr/local/bin`). The actual blockchain +program will store it's data under their default data directory (e.g. `$HOME/.gaiad`) which is independent of +the `$DAEMON_HOME`. You can choose to set `$DAEMON_HOME` to the actual binary's home directory and then end up +with a configuation like the following, but this is left as a choice to the system admininstrator for best +directory layout: + +``` +.gaiad +├── config +├── data +└── cosmovisor +``` + +## Usage + +The system administrator admin is responsible for: +* installing the `cosmovisor` binary and configure the host's init system (e.g. `systemd`, `launchd`, etc) along with the environmental variables appropriately; +* installing the `genesis` folder manually; +* installing the `upgrades/` folders manually. + +`cosmovisor` will set the `current` link to point to `genesis` at first start (when no `current` link exists) and handles +binaries switch overs at the correct points in time, so that the system administrator can prepare days in advance and relax at upgrade time. + +Note that blockchain applications that wish to support upgrades may package up a genesis `cosmovisor` tarball with this information, +just as they prepare the genesis binary tarball. In fact, they may offer a tarball will all upgrades up to current point for easy download +for those who wish to sync a fullnode from start. + +The `DAEMON` specific code and operations (e.g. tendermint config, the application db, syncing blocks, etc) are performed as normal. +Application binaries' directives such as command-line flags and environment variables work normally. + +## Example: simd + +The following instructions provide a demonstration of `cosmovisor`'s integration with the `simd` application +shipped along the Cosmos SDK's source code. + +First compile `simd`: + +``` +cd cosmos-sdk/ +make build +``` + +Create a new key and setup the `simd` node: + +``` +rm -rf $HOME/.simapp +./build/simd keys --keyring-backend=test add validator +./build/simd init testing --chain-id test +./build/simd add-genesis-account --keyring-backend=test $(./build/simd keys --keyring-backend=test show validator -a) 1000000000stake,1000000000validatortoken +./build/simd gentx --keyring-backend test --chain-id test validator 100000stake +./build/simd collect-gentxs +``` + +Set the required environment variables: + +``` +export DAEMON_NAME=simd # binary name +export DAEMON_HOME=$HOME/.simapp # daemon's home directory +``` + +Create the `cosmovisor`’s genesis folders and deploy the binary: + +``` +mkdir -p $DAEMON_HOME/cosmovisor/genesis/bin +cp ./build/simd $DAEMON_HOME/cosmovisor/genesis/bin +``` + +For the sake of this demonstration, we would amend `voting_params.voting_period` in `.simapp/config/genesis.json` to a reduced time ~5 minutes (300s) and eventually launch `cosmosvisor`: + +``` +cosmovisor start +``` + +Submit a software upgrade proposal: + +``` +./build/simd tx gov submit-proposal software-upgrade test1 --title "upgrade-demo" --description "upgrade" --from validator --upgrade-height 100 --deposit 10000000stake --chain-id test --keyring-backend test -y +``` + +Query the proposal to ensure it was correctly broadcast and added to a block: + +``` +./build/simd query gov proposal 1 +``` + +Submit a `Yes` vote for the upgrade proposal: + +``` +./build/simd tx gov vote 1 yes --from validator --keyring-backend test --chain-id test -y +``` + +For the sake of this demonstration, we will hardcode a modification in `simapp` to simulate a code change. +In `simapp/app.go`, find the line containing the upgrade Keeper initialisation, it should look like +`app.UpgradeKeeper = upgradekeeper.NewKeeper(skipUpgradeHeights, keys[upgradetypes.StoreKey], appCodec, homePath)`. +After that line, add the following snippet: + + ``` + app.UpgradeKeeper.SetUpgradeHandler("test1", func(ctx sdk.Context, plan upgradetypes.Plan) { + // Add some coins to a random account + addr, err := sdk.AccAddressFromBech32("cosmos18cgkqduwuh253twzmhedesw3l7v3fm37sppt58") + if err != nil { + panic(err) + } + err = app.BankKeeper.AddCoins(ctx, addr, sdk.Coins{sdk.Coin{Denom: "stake", Amount: sdk.NewInt(345600000)}}) + if err != nil { + panic(err) + } + }) +``` + +Now recompile a new binary and place it in `$DAEMON_HOME/cosmosvisor/upgrades/test1/bin`: + +``` +make build +cp ./build/simd $DAEMON_HOME/cosmovisor/upgrades/test1/bin +``` + +The upgrade will occur automatically at height 100. From e72ab26e053f9c71174b99f4904c8a5344b55cf7 Mon Sep 17 00:00:00 2001 From: Tobias Schwarz Date: Wed, 13 Jan 2021 11:01:02 +0100 Subject: [PATCH 016/728] Chain Upgrade Guide: new_v040_genesis.json (#8313) Genesis name changes from `new_v040_genesis.json` to `new_040_genesis.json` in this command. This is probably a typo Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> From 50d31ad6fd01a00fcfa8f23b5ef5c6cd0ea75a92 Mon Sep 17 00:00:00 2001 From: atheeshp <59333759+atheeshp@users.noreply.github.com> Date: Wed, 13 Jan 2021 17:25:51 +0530 Subject: [PATCH 017/728] x/gov docs basic cleanup (#8279) * update docs * review changes * review changes * review changes * review changes * review changes Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- x/gov/spec/01_concepts.md | 16 +++++++++---- x/gov/spec/02_state.md | 48 +++++++-------------------------------- x/gov/spec/03_messages.md | 42 +++++++++++----------------------- 3 files changed, 32 insertions(+), 74 deletions(-) diff --git a/x/gov/spec/01_concepts.md b/x/gov/spec/01_concepts.md index 0acbfa3b70..becb7cc6de 100644 --- a/x/gov/spec/01_concepts.md +++ b/x/gov/spec/01_concepts.md @@ -29,18 +29,24 @@ its unique `proposalID`. ### Proposal types -In the initial version of the governance module, there are two types of -proposal: +In the initial version of the governance module, there are five types of +proposals: -- `PlainTextProposal` All the proposals that do not involve a modification of +- `TextProposal` All the proposals that do not involve a modification of the source code go under this type. For example, an opinion poll would use a - proposal of type `PlainTextProposal`. + proposal of type `TextProposal`. - `SoftwareUpgradeProposal`. If accepted, validators are expected to update their software in accordance with the proposal. They must do so by following a 2-steps process described in the [Software Upgrade](#software-upgrade) section below. Software upgrade roadmap may be discussed and agreed on via - `PlainTextProposals`, but actual software upgrades must be performed via + `TextProposals`, but actual software upgrades must be performed via `SoftwareUpgradeProposals`. +- `CommunityPoolSpendProposal` details a proposal for use of community funds, + together with how many coins are proposed to be spent, and to which recipient account. +- `ParameterChangeProposal` defines a proposal to change one or + more parameters. If accepted, the requested parameter change is updated + automatically by the proposal handler upon conclusion of the voting period. +- `CancelSoftwareUpgradeProposal` is a gov Content type for cancelling a software upgrade. Other modules may expand upon the governance module by implementing their own proposal types and handlers. These types are registered and processed through the diff --git a/x/gov/spec/02_state.md b/x/gov/spec/02_state.md index ba3b12b3f3..4175367fdf 100644 --- a/x/gov/spec/02_state.md +++ b/x/gov/spec/02_state.md @@ -11,26 +11,14 @@ be one active parameter set at any given time. If governance wants to change a parameter set, either to modify a value or add/remove a parameter field, a new parameter set has to be created and the previous one rendered inactive. -```go -type DepositParams struct { - MinDeposit sdk.Coins // Minimum deposit for a proposal to enter voting period. - MaxDepositPeriod time.Time // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months -} -``` +### DepositParams ++++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/gov/v1beta1/gov.proto#L127-L145 -```go -type VotingParams struct { - VotingPeriod time.Time // Length of the voting period. Initial value: 2 weeks -} -``` +### VotingParams ++++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/gov/v1beta1/gov.proto#L147-L156 -```go -type TallyParams struct { - Quorum sdk.Dec // Minimum percentage of stake that needs to vote for a proposal to be considered valid - Threshold sdk.Dec // Minimum proportion of Yes votes for proposal to pass. Initial value: 0.5 - Veto sdk.Dec // Minimum proportion of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 -} -``` +### TallyParams ++++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/gov/v1beta1/gov.proto#L158-L183 Parameters are stored in a global `GlobalParams` KVStore. @@ -68,12 +56,7 @@ const ( ## Deposit -```go - type Deposit struct { - Amount sdk.Coins // Amount of coins deposited by depositor - Depositor crypto.address // Address of depositor - } -``` ++++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/gov/v1beta1/gov.proto#L43-L53 ## ValidatorGovInfo @@ -92,22 +75,7 @@ This type is used in a temp map when tallying what this proposal is about, and other fields, which are the mutable state of the governance process. -```go -type Proposal struct { - Content // Proposal content interface - - ProposalID uint64 - Status ProposalStatus // Status of the Proposal {Pending, Active, Passed, Rejected} - FinalTallyResult TallyResult // Result of Tallies - - SubmitTime time.Time // Time of the block where TxGovSubmitProposal was included - DepositEndTime time.Time // Time that the Proposal would expire if deposit amount isn't met - TotalDeposit sdk.Coins // Current deposit on this proposal. Initial value is set at InitialDeposit - - VotingStartTime time.Time // Time of the block where MinDeposit was reached. -1 if MinDeposit is not reached - VotingEndTime time.Time // Time that the VotingPeriod for this proposal will end and votes will be tallied -} -``` ++++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/gov/v1beta1/gov.proto#L55-L77 ```go type Content interface { diff --git a/x/gov/spec/03_messages.md b/x/gov/spec/03_messages.md index cac55b5ed9..59aa255f4c 100644 --- a/x/gov/spec/03_messages.md +++ b/x/gov/spec/03_messages.md @@ -6,18 +6,12 @@ order: 3 ## Proposal Submission -Proposals can be submitted by any Atom holder via a `TxGovSubmitProposal` +Proposals can be submitted by any account via a `MsgSubmitProposal` transaction. -```go -type TxGovSubmitProposal struct { - Content Content - InitialDeposit sdk.Coins - Proposer sdk.AccAddress -} -``` ++++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/gov/v1beta1/tx.proto#L24-L39 -The `Content` of a `TxGovSubmitProposal` message must have an appropriate router +The `Content` of a `MsgSubmitProposal` message must have an appropriate router set in the governance module. **State modifications:** @@ -30,12 +24,12 @@ set in the governance module. - Push `proposalID` in `ProposalProcessingQueue` - Transfer `InitialDeposit` from the `Proposer` to the governance `ModuleAccount` -A `TxGovSubmitProposal` transaction can be handled according to the following +A `MsgSubmitProposal` transaction can be handled according to the following pseudocode. ```go // PSEUDOCODE // -// Check if TxGovSubmitProposal is valid. If it is, create proposal // +// Check if MsgSubmitProposal is valid. If it is, create proposal // upon receiving txGovSubmitProposal from sender do @@ -79,14 +73,9 @@ upon receiving txGovSubmitProposal from sender do Once a proposal is submitted, if `Proposal.TotalDeposit < ActiveParam.MinDeposit`, Atom holders can send -`TxGovDeposit` transactions to increase the proposal's deposit. +`MsgDeposit` transactions to increase the proposal's deposit. -```go -type TxGovDeposit struct { - ProposalID int64 // ID of the proposal - Deposit sdk.Coins // Number of Atoms to add to the proposal's deposit -} -``` ++++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/gov/v1beta1/tx.proto#L61-L72 **State modifications:** @@ -97,12 +86,12 @@ type TxGovDeposit struct { - Push `proposalID` in `ProposalProcessingQueueEnd` - Transfer `Deposit` from the `proposer` to the governance `ModuleAccount` -A `TxGovDeposit` transaction has to go through a number of checks to be valid. +A `MsgDeposit` transaction has to go through a number of checks to be valid. These checks are outlined in the following pseudocode. ```go // PSEUDOCODE // -// Check if TxGovDeposit is valid. If it is, increase deposit and check if MinDeposit is reached +// Check if MsgDeposit is valid. If it is, increase deposit and check if MinDeposit is reached upon receiving txGovDeposit from sender do // check if proposal is correctly formatted. Includes fee payment. @@ -149,15 +138,10 @@ upon receiving txGovDeposit from sender do ## Vote Once `ActiveParam.MinDeposit` is reached, voting period starts. From there, -bonded Atom holders are able to send `TxGovVote` transactions to cast their +bonded Atom holders are able to send `MsgVote` transactions to cast their vote on the proposal. -```go - type TxGovVote struct { - ProposalID int64 // proposalID of the proposal - Vote byte // option from OptionSet chosen by the voter - } -``` ++++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/gov/v1beta1/tx.proto#L46-L56 **State modifications:** @@ -165,12 +149,12 @@ vote on the proposal. _Note: Gas cost for this message has to take into account the future tallying of the vote in EndBlocker_ -Next is a pseudocode proposal of the way `TxGovVote` transactions are +Next is a pseudocode outline of the way `MsgVote` transactions are handled: ```go // PSEUDOCODE // - // Check if TxGovVote is valid. If it is, count vote// + // Check if MsgVote is valid. If it is, count vote// upon receiving txGovVote from sender do // check if proposal is correctly formatted. Includes fee payment. From 3fdce159b89bc4459b1b4da22068467aaed324a1 Mon Sep 17 00:00:00 2001 From: Marko Date: Wed, 13 Jan 2021 18:41:50 +0100 Subject: [PATCH 018/728] docs: deploy to github pages (#8321) --- .github/workflows/docs.yml | 31 +++++++++++++++++++++++++++++++ Makefile | 9 +-------- 2 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/docs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000000..9c8fc8fbae --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,31 @@ +name: Documentation +# This job builds and deploys documenation to github pages. +# It runs on every push to master. +on: + push: + branches: + - master + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + container: + image: tendermintdev/docker-website-deployment + steps: + - name: Checkout 🛎️ + uses: actions/checkout@v2.3.1 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Install and Build 🔧 + run: | + apk add rsync + make build-docs LEDGER_ENABLED=false + + - name: Deploy 🚀 + uses: JamesIves/github-pages-deploy-action@3.7.1 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH: gh-pages + FOLDER: ~/output diff --git a/Makefile b/Makefile index 79058ab295..ecb3273572 100644 --- a/Makefile +++ b/Makefile @@ -211,14 +211,7 @@ build-docs: cp -r .vuepress/dist/* ~/output/$${path_prefix}/ ; \ cp ~/output/$${path_prefix}/index.html ~/output ; \ done < versions ; - -sync-docs: - cd ~/output && \ - echo "role_arn = ${DEPLOYMENT_ROLE_ARN}" >> /root/.aws/config ; \ - echo "CI job = ${CIRCLE_BUILD_URL}" >> version.html ; \ - aws s3 sync . s3://${WEBSITE_BUCKET} --profile terraform --delete ; \ - aws cloudfront create-invalidation --distribution-id ${CF_DISTRIBUTION_ID} --profile terraform --path "/*" ; -.PHONY: sync-docs +.PHONY: build-docs ############################################################################### ### Tests & Simulation ### From d1aab447d8c57962ebd6067cd830238891634834 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Wed, 13 Jan 2021 14:18:19 -0500 Subject: [PATCH 019/728] ADR 030: Authorization Module (#7105) * Add ADR 030 * Cleanup * Updates * Updates based on ADR 031 * Add abstract and context * Updates * Update module name as per #7615 * Updates from reviews * Rename * CLI update * Update docs/architecture/adr-030-authz-module.md Co-authored-by: Robert Zaremba * Update docs/architecture/adr-030-authz-module.md Co-authored-by: Robert Zaremba * Update docs/architecture/adr-030-authz-module.md Co-authored-by: Robert Zaremba * Update docs/architecture/adr-030-authz-module.md Co-authored-by: Robert Zaremba * Code review updates * Update docs Co-authored-by: Alessio Treglia Co-authored-by: Robert Zaremba --- docs/architecture/adr-030-authz-module.md | 230 ++++++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 docs/architecture/adr-030-authz-module.md diff --git a/docs/architecture/adr-030-authz-module.md b/docs/architecture/adr-030-authz-module.md new file mode 100644 index 0000000000..c61940fce8 --- /dev/null +++ b/docs/architecture/adr-030-authz-module.md @@ -0,0 +1,230 @@ +# ADR 030: Authorization Module + +## Changelog + +- 2019-11-06: Initial Draft +- 2020-10-12: Updated Draft +- 2021-11-13: Accepted + +## Status + +Accepted + +## Abstract + +This ADR defines the `x/authz` module which allows accounts to grant authorizations to perform actions +on behalf of that account to other accounts. + +## Context + +The concrete use cases which motivated this module include: +- the desire to delegate the ability to vote on proposals to other accounts besides the account which one has +delegated stake +- "sub-keys" functionality, as originally proposed in [\#4480](https://github.com/cosmos/cosmos-sdk/issues/4480) which +is a term used to describe the functionality provided by this module together with +the `fee_grant` module from [ADR 029](./adr-029-fee-grant-module.md) and the [group module](https://github.com/regen-network/cosmos-modules/tree/master/incubator/group). + +The "sub-keys" functionality roughly refers to the ability for one account to grant some subset of its capabilities to +other accounts with possibly less robust, but easier to use security measures. For instance, a master account representing +an organization could grant the ability to spend small amounts of the organization's funds to individual employee accounts. +Or an individual (or group) with a multisig wallet could grant the ability to vote on proposals to any one of the member +keys. + +The current +implementation is based on work done by the [Gaian's team at Hackatom Berlin 2019](https://github.com/cosmos-gaians/cosmos-sdk/tree/hackatom/x/delegation). + +## Decision + +We will create a module named `authz` which provides functionality for +granting arbitrary privileges from one account (the _granter_) to another account (the _grantee_). Authorizations +must be granted for a particular `Msg` service methods one by one using an implementation +of `Authorization`. + +### Types + +Authorizations determine exactly what privileges are granted. They are extensible +and can be defined for any `Msg` service method even outside of the module where +the `Msg` method is defined. `Authorization`s use the new `ServiceMsg` type from +ADR 031. + +#### Authorization + +```go +type Authorization interface { + // MethodName returns the fully-qualified Msg service method name as described in ADR 031. + MethodName() string + + // Accept determines whether this grant permits the provided sdk.ServiceMsg to be performed, and if + // so provides an upgraded authorization instance. + // Returns: + // + allow: true if msg is authorized + // + updated: new Authorization instance which should overwrite the current one with new state + // + delete: true if Authorization has been exhausted and can be deleted from state + Accept(msg sdk.ServiceMsg, block abci.Header) (allow bool, updated Authorization, delete bool) +} +``` + +For example a `SendAuthorization` like this is defined for `MsgSend` that takes +a `SpendLimit` and updates it down to zero: + +```go +type SendAuthorization struct { + // SpendLimit specifies the maximum amount of tokens that can be spent + // by this authorization and will be updated as tokens are spent. If it is + // empty, there is no spend limit and any amount of coins can be spent. + SpendLimit sdk.Coins +} + +func (cap SendAuthorization) MethodName() string { + return "/cosmos.bank.v1beta1.Msg/Send" +} + +func (cap SendAuthorization) Accept(msg sdk.ServiceMsg, block abci.Header) (allow bool, updated Authorization, delete bool) { + switch req := msg.Request.(type) { + case bank.MsgSend: + left, invalid := cap.SpendLimit.SafeSub(req.Amount) + if invalid { + return false, nil, false + } + if left.IsZero() { + return true, nil, true + } + return true, SendAuthorization{SpendLimit: left}, false + } + return false, nil, false +} +``` + +A different type of capability for `MsgSend` could be implemented +using the `Authorization` interface with no need to change the underlying +`bank` module. + +### `Msg` Service + +```proto +service Msg { + // GrantAuthorization grants the provided authorization to the grantee on the granter's + // account with the provided expiration time. + rpc GrantAuthorization(MsgGrantAuthorization) returns (MsgGrantAuthorizationResponse); + + // ExecAuthorized attempts to execute the provided messages using + // authorizations granted to the grantee. Each message should have only + // one signer corresponding to the granter of the authorization. + // The grantee signing this message must have an authorization from the granter. + rpc ExecAuthorized(MsgExecAuthorized) returns (MsgExecAuthorizedResponse) + + + // RevokeAuthorization revokes any authorization corresponding to the provided method name on the + // granter's account that has been granted to the grantee. + rpc RevokeAuthorization(MsgRevokeAuthorization) returns (MsgRevokeAuthorizationResponse); +} + +message MsgGrantAuthorization{ + string granter = 1; + string grantee = 2; + google.protobuf.Any authorization = 3 [(cosmos_proto.accepts_interface) = "Authorization"]; + google.protobuf.Timestamp expiration = 4; +} + +message MsgExecAuthorized { + string grantee = 1; + repeated google.protobuf.Any msgs = 2; +} + +message MsgRevokeAuthorization{ + string granter = 1; + string grantee = 2; + string method_name = 3; +} +``` + +### Router Middleware + +The `authz` `Keeper` will expose a `DispatchActions` method which allows other modules to send `ServiceMsg`s +to the router based on `Authorization` grants: + +```go +type Keeper interface { + // DispatchActions routes the provided msgs to their respective handlers if the grantee was granted an authorization + // to send those messages by the first (and only) signer of each msg. + DispatchActions(ctx sdk.Context, grantee sdk.AccAddress, msgs []sdk.ServiceMsg) sdk.Result` +} +``` + +This allows the functionality provided by `authz` to be used for future inter-module object capabilities +permissions as described in [ADR 033](https://github.com/cosmos/cosmos-sdk/7459) + +### CLI + +#### `tx exec` Method + +When a CLI user wants to run a transaction on behalf of another account using `MsgExecAuthorized`, they +can use the `exec` method. For instance `gaiacli tx gov vote 1 yes --from --generate-only | gaiacli tx authz exec --send-as --from ` +would send a transaction like this: + +```go +MsgExecAuthorized { + Grantee: mykey, + Msgs: []sdk.SericeMsg{ + ServiceMsg { + MethodName:"/cosmos.gov.v1beta1.Msg/Vote" + Request: MsgVote { + ProposalID: 1, + Voter: cosmos3thsdgh983egh823 + Option: Yes + } + } + } +} +``` + +#### `tx grant --from ` + +This CLI command will send a `MsgGrantAuthorization` transaction. `authorization` should be encoded as +JSON on the CLI. + +#### `tx revoke --from ` + +This CLI command will send a `MsgRevokeAuthorization` transaction. + +### Built-in Authorizations + +#### `SendAuthorization` + +```proto +// SendAuthorization allows the grantee to spend up to spend_limit coins from +// the granter's account. +message SendAuthorization { + repeated cosmos.base.v1beta1.Coin spend_limit = 1; +} +``` + +#### `GenericAuthorization` + +```proto +// GenericAuthorization gives the grantee unrestricted permissions to execute +// the provide method on behalf of the granter's account. +message GenericAuthorization { + string method_name = 1; +} +``` + +## Consequences + +### Positive + +- Users will be able to authorize arbitrary actions on behalf of their accounts to other +users, improving key management for many use cases +- The solution is more generic than previously considered approaches and the +`Authorization` interface approach can be extended to cover other use cases by +SDK users + +### Negative + +### Neutral + +## References + +- Initial Hackatom implementation: https://github.com/cosmos-gaians/cosmos-sdk/tree/hackatom/x/delegation +- Post-Hackatom spec: https://gist.github.com/aaronc/b60628017352df5983791cad30babe56#delegation-module +- B-Harvest subkeys spec: https://github.com/cosmos/cosmos-sdk/issues/4480 From ac99a10e53e7120d7892f7b6489e834dc344e7b5 Mon Sep 17 00:00:00 2001 From: MD Aleem <72057206+aleem1314@users.noreply.github.com> Date: Mon, 11 Jan 2021 18:21:42 +0530 Subject: [PATCH 020/728] [docs] x/crisis - general audit and cleanup (#8269) * update docs * review changes Co-authored-by: atheeshp <59333759+atheeshp@users.noreply.github.com> --- x/crisis/spec/02_messages.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/crisis/spec/02_messages.md b/x/crisis/spec/02_messages.md index 646ca08059..8634c8f9f5 100644 --- a/x/crisis/spec/02_messages.md +++ b/x/crisis/spec/02_messages.md @@ -11,7 +11,7 @@ corresponding updates to the state. Blockchain invariants can be checked using the `MsgVerifyInvariant` message. -+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/crisis/v1/tx.proto#L14-L22 ++++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0-rc7/proto/cosmos/crisis/v1beta1/tx.proto#L14-L22 This message is expected to fail if: - the sender does not have enough coins for the constant fee From 9bde4feb15c05e8616cea3ce2dff4dcc9e9f6141 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Jan 2021 08:41:03 -0300 Subject: [PATCH 021/728] build(deps): bump github.com/stretchr/testify from 1.6.1 to 1.7.0 (#8330) Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.6.1 to 1.7.0. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.6.1...v1.7.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From c94db73c977bed50d150dcfeb8e0073d157adbe3 Mon Sep 17 00:00:00 2001 From: Marie Gauthier Date: Fri, 15 Jan 2021 14:30:17 +0100 Subject: [PATCH 022/728] x/params docs general audit & cleanup (#8295) * Update doc.go * Update spec/ * More updates * Update README Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- x/params/doc.go | 21 ++++++++++----------- x/params/spec/01_keeper.md | 18 +++++------------- x/params/spec/02_subspace.md | 16 +++++++--------- x/params/spec/README.md | 5 ++--- 4 files changed, 24 insertions(+), 36 deletions(-) diff --git a/x/params/doc.go b/x/params/doc.go index 68ab4dee0e..2a79b0fd51 100644 --- a/x/params/doc.go +++ b/x/params/doc.go @@ -6,9 +6,8 @@ namespace for a parameter store, where keys are prefixed by pre-configured subspace names which modules provide. The Keeper has a permission to access all existing subspaces. -Subspace can be used by the individual keepers, who needs a private parameter store -that the other keeper cannot modify. Keeper can be used by the Governance keeper, -who need to modify any parameter in case of the proposal passes. +Subspace can be used by the individual keepers, which need a private parameter store +that the other keepers cannot modify. Basic Usage: @@ -23,11 +22,11 @@ Basic Usage: KeyParameter2 = "myparameter2" ) -2. Create a concrete parameter struct and define the validation functions: +2. Define parameters as proto message and define the validation functions: - type MyParams struct { - MyParam1 int64 `json:"my_param1" yaml:"my_param1"` - MyParam2 bool `json:"my_param2" yaml:"my_param2"` + message MyParams { + int64 my_param1 = 1; + bool my_param2 = 2; } func validateMyParam1(i interface{}) error { @@ -56,12 +55,12 @@ Basic Usage: func (p *MyParams) ParamSetPairs() params.ParamSetPairs { return params.ParamSetPairs{ - {KeyParameter1, &p.MyParam1, validateMyParam1}, - {KeyParameter2, &p.MyParam2, validateMyParam2}, + params.NewParamSetPair(KeyParameter1, &p.MyParam1, validateMyParam1), + params.NewParamSetPair(KeyParameter2, &p.MyParam2, validateMyParam2), } } - func paramKeyTable() params.KeyTable { + func ParamKeyTable() params.KeyTable { return params.NewKeyTable().RegisterParamSet(&MyParams{}) } @@ -70,7 +69,7 @@ Basic Usage: func NewKeeper(..., paramSpace params.Subspace, ...) Keeper { // set KeyTable if it has not already been set if !paramSpace.HasKeyTable() { - paramSpace = paramSpace.WithKeyTable(paramKeyTable()) + paramSpace = paramSpace.WithKeyTable(ParamKeyTable()) } return Keeper { diff --git a/x/params/spec/01_keeper.md b/x/params/spec/01_keeper.md index 8cf7e2a53f..fa97ec5b37 100644 --- a/x/params/spec/01_keeper.md +++ b/x/params/spec/01_keeper.md @@ -4,24 +4,16 @@ order: 1 # Keeper -In the app initialization stage, `Keeper.Subspace(Paramspace)` is passed to the -user modules, and the subspaces are stored in `Keeper.spaces`. Later it can be -retrieved with `Keeper.GetSubspace`, so the keepers holding `Keeper` can access -to any subspace. For example, Gov module can take `Keeper` as its argument and -modify parameter of any subspace when a `ParameterChangeProposal` is accepted. +In the app initialization stage, [subspaces](02_subspace.md) can be allocated for other modules' keeper using `Keeper.Subspace` and are stored in `Keeper.spaces`. Then, those modules can have a reference to their specific parameter store through `Keeper.GetSubspace`. Example: ```go -type MasterKeeper struct { - pk params.Keeper +type ExampleKeeper struct { + paramSpace paramtypes.Subspace } -func (k MasterKeeper) SetParam(ctx sdk.Context, space string, key string, param interface{}) { - space, ok := k.ps.GetSubspace(space) - if !ok { - return - } - space.Set(ctx, key, param) +func (k ExampleKeeper) SetParams(ctx sdk.Context, params types.Params) { + k.paramSpace.SetParamSet(ctx, ¶ms) } ``` diff --git a/x/params/spec/02_subspace.md b/x/params/spec/02_subspace.md index 5e36eaf498..e6ffa760d2 100644 --- a/x/params/spec/02_subspace.md +++ b/x/params/spec/02_subspace.md @@ -4,8 +4,8 @@ order: 2 # Subspace -`Subspace` is a prefixed subspace of the parameter store. Each module who use the -parameter store will take a `Subspace`, not the `Keeper`, to isolate permission to access. +`Subspace` is a prefixed subspace of the parameter store. Each module which uses the +parameter store will take a `Subspace` to isolate permission to access. ## Key @@ -21,20 +21,18 @@ Subkeys can be used for grouping or dynamic parameter key generation during runt All of the parameter keys that will be used should be registered at the compile time. `KeyTable` is essentially a `map[string]attribute`, where the `string` is a parameter key. -Currently, `attribute` only consists of `reflect.Type`, which indicates the parameter -type. It is needed even if the state machine has no error, because the paraeter -can be modified externally, for example via the governance. +Currently, `attribute` consists of a `reflect.Type`, which indicates the parameter +type to check that provided key and value are compatible and registered, as well as a function `ValueValidatorFn` to validate values. Only primary keys have to be registered on the `KeyTable`. Subkeys inherit the attribute of the primary key. ## ParamSet -Modules often define a struct of parameters. Instead of calling methods with -each of those parameters, when the struct implements `ParamSet`, it can be used -with the following methods: +Modules often define parameters as a proto message. The generated struct can implement +`ParamSet` interface to be used with the following methods: * `KeyTable.RegisterParamSet()`: registers all parameters in the struct * `Subspace.{Get, Set}ParamSet()`: Get to & Set from the struct -The implementor should be a pointer in order to use `GetParamSet()` +The implementor should be a pointer in order to use `GetParamSet()`. diff --git a/x/params/spec/README.md b/x/params/spec/README.md index c84ad6e5bc..4a136cce57 100644 --- a/x/params/spec/README.md +++ b/x/params/spec/README.md @@ -15,9 +15,8 @@ There are two main types, Keeper and Subspace. Subspace is an isolated namespace paramstore, where keys are prefixed by preconfigured spacename. Keeper has a permission to access all existing spaces. -Subspace can be used by the individual keepers, who needs a private parameter store -that the other keeper cannot modify. Keeper can be used by the Governance keeper, -who need to modify any parameter in case of the proposal passes. +Subspace can be used by the individual keepers, which need a private parameter store +that the other keepers cannot modify. The params Keeper can be used to add a route to `x/gov` router in order to modify any parameter in case a proposal passes. The following contents explains how to use params module for master and user modules. From 6baaee676886c0d1c458a1d70427a3223fbf0867 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Fri, 15 Jan 2021 14:45:34 -0500 Subject: [PATCH 023/728] Update gogo proto deps with v1.3.2 security fixes (#8350) * Update gogo proto deps with v1.3.2 security fixes * Regenerate proto files From e2c6300fa638cce807462a7259093ab11bccc409 Mon Sep 17 00:00:00 2001 From: Cyrus Goh Date: Fri, 15 Jan 2021 13:37:47 -0800 Subject: [PATCH 024/728] Update ga id (#8351) From 4741a03e72490f517561e5e67906b3227fce2ca3 Mon Sep 17 00:00:00 2001 From: Robert Zaremba Date: Sat, 16 Jan 2021 00:03:08 +0100 Subject: [PATCH 025/728] Robert/docs slashing (#8329) * update slashing docs * x/slashing docs general audit & cleanup * Update x/slashing/spec/02_state.md Co-authored-by: Amaury * Update x/slashing/spec/05_hooks.md Co-authored-by: Alessio Treglia * use code insertion widget * review update * Update x/slashing/spec/05_hooks.md Co-authored-by: Alessio Treglia * update link and location for fee distribution spec Co-authored-by: Amaury Co-authored-by: Alessio Treglia --- docs/spec/fee_distribution/f1_fee_distr.pdf | Bin 0 -> 185175 bytes docs/spec/fee_distribution/f1_fee_distr.tex | 245 ++++++++++++++++++++ x/slashing/spec/02_state.md | 49 ++-- x/slashing/spec/03_messages.md | 9 +- x/slashing/spec/05_hooks.md | 13 +- x/slashing/spec/06_events.md | 30 ++- x/slashing/spec/07_tombstone.md | 10 +- x/slashing/spec/08_params.md | 14 +- 8 files changed, 313 insertions(+), 57 deletions(-) create mode 100644 docs/spec/fee_distribution/f1_fee_distr.pdf create mode 100644 docs/spec/fee_distribution/f1_fee_distr.tex diff --git a/docs/spec/fee_distribution/f1_fee_distr.pdf b/docs/spec/fee_distribution/f1_fee_distr.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b9995386957cb1be5fe5c21551b0645009063045 GIT binary patch literal 185175 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íw? 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˾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®cJ;^V-pd>T?lQ>;=Q>%vyx5? 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

#-Q)Qd~0k!|-IzD88Bl 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 hEwLT^?}N!;k^-5b!fOWIAe*u0V5q|&x literal 0 HcmV?d00001 diff --git a/docs/spec/fee_distribution/f1_fee_distr.tex b/docs/spec/fee_distribution/f1_fee_distr.tex new file mode 100644 index 0000000000..b6bb6b3265 --- /dev/null +++ b/docs/spec/fee_distribution/f1_fee_distr.tex @@ -0,0 +1,245 @@ +\documentclass[]{article} +\usepackage{hyperref} + +%opening +\title{F1 Fee Distribution Draft-02} +\author{Dev Ojha} + +\begin{document} + +\maketitle + +\begin{abstract} + In a proof of stake blockchain, validators need to split the rewards gained from transaction fees each block. Furthermore, these fees must be fairly distributed to each of a validator's constituent delegators. They accrue this reward throughout the entire time they are delegated, and they have a special operation to withdraw accrued rewards. + + The F1 fee distribution scheme works for any algorithm to split funds between validators each block, with minimal iteration, and the only approximations being due to finite decimal precision. Per block there is a single iteration over the validator set, to enable reward algorithms that differ by validator. No iteration is required to delegate, or withdraw. The state usage is one state update per validator per block, and one state entry per active delegation. It can optionally handle arbitrary inflation schemes, and auto-bonding of rewards. +\end{abstract} + +\section{F1 Fee Distribution} + +\subsection{Context} +In a proof of stake blockchain, each validator has an associated stake. +Transaction fees get rewarded to validators based on the incentive scheme of the underlying proof of stake model. +The fee distribution problem occurs in proof of stake blockchains supporting delegation, as there is a need to distribute a validator's fee rewards to its delegators. +The trivial solution of just giving the rewards to each delegator every block is too expensive to perform on-chain. +So instead fee distribution algorithms have delegators perform a withdraw action, which when performed yields the same total amount of fees as if they had received them at every block. + +This details F1, an approximation-free, slash-tolerant fee distribution algorithm which allows validator commission-rates, inflation rates, and fee proportions, which can all efficiently change per validator, every block. +The algorithm requires iterating over the bonded validators every block, and withdraws require no iteration. +This is cheap, due to staking logic already requiring iteration over all validators, which causes the expensive state-reads to be cached. + +The key point of how F1 works is that it tracks how much rewards a delegator with 1 stake for a given validator would be entitled to if it had bonded at block 0 until the latest block. +When a delegator bonds at block $b$, the amount of rewards a delegator with 1 stake would have if bonded at block 0 until block $b$ is also persisted to state. +When the delegator withdraws, they receive the difference of these two values. +Since rewards are distributed according to stake-weighting, this amount of rewards can be scaled by the amount of stake a delegator had. +Section 1.2 describes this in more detail, with an argument for it being approximation free. +Section 2 details how to adapt this algorithm to handle commission rates, slashing, and inflation. + +\subsection{Base algorithm} +In this section, we show that the F1 base algorithm gives each delegator rewards identical to that which they'd receive in the naive and correct fee distribution algorithm that iterated over all delegators every block. + +Even distribution of a validators rewards amongst its validators weighted by stake means the following: +Suppose a delegator delegates $x$ stake to a validator $v$ at block $h$. +Let the amount of stake the validator has at block $i$ be $s_i$ and the amount of fees they receive at this height be $f_i$. +Then if a delegator contributing $x$ stake decides to withdraw at block $n$, the rewards they receive are +$$\sum_{i = h}^{n} \frac{x}{s_i}f_i = x \sum_{i = h}^{n} \frac{f_i}{s_i}$$ + +Note that $s_i$ does not change every block, +it only changes if the validator gets slashed, +or if any delegator alters the amount they have delegated. +We'll relegate handling of slashes to \autoref{ssec:slashing}, +and only consider the case with no slashing here. +We can change the iteration from being over every block, to instead being over the set of blocks between two changes in validator $v$'s total stake. +Let each of these set of blocks be called a period. +A new period begins every time that validator's total stake changes. +Let the total amount of stake for the validator in period $p$ be $n_p$. +Let $T_p$ be the total fees that validator $v$ accrued in period $p$. +Let $h$ be the start of period $p_{init}$, and height $n$ be the end of $p_{final}$. +It follows that +$$x \sum_{i = h}^{n} \frac{f_i}{s_i} = x \sum_{p = p_{init}}^{p_{final}} \frac{T_p}{n_p}$$ + +Let $p_0$ represent the period which begins when the validator first bonds. +The central idea to the F1 model is that at the end of the $k$th period, +the following is stored at a state location indexable by $k$: $\sum_{i=0}^{k}\frac{T_i}{n_i}$. +Let the index of the current period be $f$. +When a delegator wants to delegate or withdraw their reward, they first create a new entry in state to end the current period. +Then this entry is created using the previous entry as follows: +$$Entry_f = \sum_{i=0}^{f}\frac{T_i}{n_i} = \sum_{i=0}^{f-1}\frac{T_i}{n_i} + \frac{T_f}{n_f} = Entry_{f-1} + \frac{T_f}{n_f}$$ +Where $T_f$ is the fees the validator has accrued in period $f$, and $n_f$ is the validators total amount of stake in period $f$. + +The withdrawer's delegation object has the index $k$ for the period which they ended by bonding. (They start receiving rewards for period $k + 1$) +The reward they should receive when withdrawing is: + +$$x \sum_{i = k + 1}^{f} \frac{T_i}{n_i} = x\left(\left(\sum_{i=0}^{f}\frac{T_i}{n_i}\right) - \left(\sum_{i=0}^{k}\frac{T_i}{n_i}\right)\right) = x\left(Entry_f - Entry_k\right)$$ + +It is clear from the equations that this payout mechanism maintains correctness, and requires no iterations. It just needed the two state reads for these entries. + +$T_f$ is a separate variable in state for the amount of fees this validator has accrued since the last update to its power. +This variable is incremented at every block by however much fees this validator received that block. +On the update to the validators power, this variable is used to create the entry in state at $f$, and is then reset to 0. + +This fee distribution proposal is agnostic to how all of the blocks fees are divied up between validators. +This creates many nice properties, for example it is possible to only rewarding validators who signed that block. + +\section{Additional add-ons} +\subsection{Commission Rates} +Commission rates are the idea that a validator can take a fixed $x\%$ cut of all of their received fees, before redistributing evenly to the constituent delegators. +This can easily be done as follows: + +In block $h$ a validator receives $f_h$ fees. +Instead of incrementing that validators ``total accrued fees this period variable" by $f_h$, it is instead incremented by $(1 - commission\_rate) * f_p$. +Then $commission\_rate * f_p$ is deposited directly to the validator's account. +This allows for efficient updates to a validator's commission rate every block if desired. +More generally, each validator could have a function which takes their fees as input, and outputs a set of outputs to pay these fees too. (i.e. x\% going to themselves, y\% to delegators, z\% burnt) + +\subsection{Slashing} +\label{ssec:slashing} +Slashing is distinct from withdrawals, since it lowers the stake of all of the delegator's by a fixed percentage. +Since no one is charged gas for slashes, a slash cannot iterate over all delegators. +Thus we can no longer just multiply by $x$ over the difference in stake. +This section describes a simple solution that should suffice for most chains needs. An asymptotically optimal solution is provided in section 2.4. +TODO: Consider removing this section in favor of just using the current section 2.4? + +The solution here is to instead store each period created by a slash in the validators state. +Then when withdrawing, you must iterate over all slashes between when you started and ended. +Suppose you delegated at period $0$, a y\% slash occured at period $2$, and your withdrawal creates period $4$. +Then you receive funds from periods $0$ to $2$ as normal. +The equations for funds you receive for periods $2$ to $4$ now uses $(1 - y)x$ for your stake instead of just $x$ stake. +When there are multiple slashes, you just account for the accumulated slash factor. + +In practice this will not really be an efficiency hit, as the number of slashes is expected to be 0 or 1 for most validators. +Validators that get slashed more will naturally lose their delegators. +A malicious validator that gets itself slashed many times would increase the gas to withdraw linearly, but the economic loss of funds due to the slashes is expected to far out-weigh the extra overhead the honest withdrawer must pay for due to the gas. +(TODO: frame that above sentence in terms of griefing factors, as thats more correct) + +\subsection{Inflation} +Inflation is the idea that we want every staked coin to create more staking tokens as time progresses. +The purpose being to drive down the relative worth of unstaked tokens. +Each block, every staked token should produce $x$ staking tokens as inflation, where $x$ is calculated from a function $inflation$ which takes state and the block information as input. +Let $x_i$ represent the evaluation of $inflation$ in the $i$th block. +The goal of this section is to auto-bond inflation in the fee distribution model without iteration. +This is done by preserving the invariant that every state entry contains the rewards one would have if they had bonded one stake at genesis until that corresponding block. + +In state a variable should be kept for the number of tokens one would have now due to inflation, +given that they bonded one token at genesis. +This is $\prod_{0}^{now} (1 + x_i)$. +Each period now stores this total inflation product along with what it already stores per-period. + +Let $R_i$ be the fee rewards in block $i$, and $n_i$ be the total amount bonded to that validator in that block. +The correct amount of rewards which 1 token at genesis should have now is: +$$Reward(now) = \sum_{i = 0}^{now}\left(\prod_{j = 0}^{i} 1 + x_j \right) * \frac{R_i}{n_i}$$ +The term in the sum is the amount of stake one stake becomes due to inflation, multiplied by the amount of fees per stake. + +Now we cast this into the period frame of view. +Recall that we build the rewards by creating a state entry for the rewards of the previous period, and keeping track of the rewards within this period. +Thus we first define the correct amount of rewards for each successive period, proving correctness of this via induction. +We then show that the state entry that gets efficiently built up block by block is equal to this value for the latest period. + +Let $start, end$ denote the start/end of a period. + +Suppose that $\forall f > 0$, $Reward(end(f))$ is correctly constructed as +$$Reward(end(f)) = Reward(end(f-1)) + \sum_{i = start(f)}^{end(f)}\left(\prod_{j = 0}^{i} 1 + x_j \right) \frac{R_i}{n_i}$$ +and that for $f = 0$, $Reward(end(0)) = 0$. +(With period 1 being defined as the period that has the first bond into it) +It must be shown that assuming the supposition $\forall f \leq f_0$, $$Reward(end(f_0 + 1)) = Reward(end(f_0)) + \sum_{i = start(f_0 + 1)}^{end(f_0 + 1)}\left(\prod_{j = 0}^{i} 1 + x_j \right) \frac{R_i}{n_i}$$ +Using the definition of $Reward$, it follows that: +$$\sum_{i = 0}^{end(f_0 + 1)}\left(\prod_{j = 0}^{i} 1 + x_j \right) * \frac{R_i}{n_i} = \sum_{i = 0}^{end(f_0)}\left(\prod_{j = 0}^{i} 1 + x_j \right) * \frac{R_i}{n_i} + \sum_{i = start(f_0 + 1)}^{end(f_0 + 1)}\left(\prod_{j = 0}^{i} 1 + x_j \right) \frac{R_i}{n_i}$$ + +Since the first summation on the right hand side is $Reward(end(f_0))$, the supposition is proven true. +Consequently, the reward for just period $f$ adjusted for the amount of inflation 1 token at genesis would produce, is: +$$\sum_{i = start(f)}^{end(f)}\left(\prod_{j = 0}^{i} 1 + x_j \right) \frac{R_i}{n_i}$$ + +TODO: make this proof + pre-amble less verbose, and just wrap up into a lemma. +Maybe just leave this proof or the last part to the reader, since it easily follows from summation bounds. + +Now note that +$$\sum_{i = start(f)}^{end(f)}\left(\prod_{j = 0}^{i} 1 + x_j \right) \frac{R_i}{n_i} = \left(\prod_{j = 0}^{end(f - 1)} 1 + x_j \right)\sum_{i = start(f)}^{end(f)}\left(\prod_{j = start(f)}^{i} 1 + x_j \right) \frac{R_i}{n_i}$$ +By definition of period, and inflation being applied every block, \\ +$n_i = n_{start(f)}\left(\prod_{j = start(f)}^{i} 1 + x_j \right)$. This cancels out the product in the summation, therefore +$$\sum_{i = start(f)}^{end(f)}\left(\prod_{j = 0}^{i} 1 + x_j \right) \frac{R_i}{n_i} = \left(\prod_{j = 0}^{end(f - 1)} 1 + x_j \right)\frac{\sum_{i = start(f)}^{end(f)}R_i}{n_{start(f)}}$$ + +Thus every block, each validator just has to add the total amount of fees (The $R_i$ term) that goes to delegates to some per-period term. +When creating a new period, $n_{start(f)}$ can be cached in state, and the product is already stored in the previous periods state entry. +You then get the next period's $n_{start(f)}$ from the consensus' power entry for this validator. +This is thus extremely efficient per block. + +When withdrawing, you take the difference as before, +which yields the amount of rewards you would have obtained with $(\prod_0^{begin\ bonding\ period}1 + x)$ stake from the block you began bonding at until now. +$(\prod_0^{begin\ bonding\ period}1 + x)$ is known, since its included in the state entry for when you bonded. +You then divide the entitled fees by $(\prod_0^{begin\ bonding\ period}1 + x)$ to normalize it to being the amount of rewards you're entitled to from 1 stake at that block to now. +Then as before, you multiply by the amount of stake you had initially bonded. +\\TODO: (Does the difference equating to that make sense, or should it be shown explicitly) +\\TODO: Does this need to explain how the originally bonded tokens are refunded, or is that clear? + +The inflation function could vary per block, +and per validator if ever a need rose. +If the inflation rate is the same for everyone then there can be a single global store for the entries corresponding to the product of inflations. +Inflation creation can trivially be epoched as long as inflation isn't required within the epoch, through changes to the $inflation$ function. + +\subsection{Withdrawing with no iteration over slashes} +Notice that a slash is the same as a negative inflation rate for a validator in one block. +For example a $20\%$ slash is equivalent to a $-20\%$ inflation for a validator in a block. +Given correctness of auto-bonding inflation with different inflation rates per-validator, +it follows that handling slashes can be correctly done by simply subtracting the validators inflation factor in that block to be the negative of the slash factor. +This significantly simplifies the withdrawal procedure. + +\subsection{Auto bonding fees} +TODO: Fill this out. +Core idea: you use the same mechanism as previously, but you just don't take that optimization with $n_{i}$ and the $n_{start}$ relation. +Fairly simple to do. + +\subsection{Delegation updates} +Updating your delegation amount is equivalent to withdrawing earned rewards and a fully independent new delegation occurring in the same block. +The same applies for redelegation. +From the view of fee distribution, partial redelegation is the same as a delegation update + a new delegation. + +\subsection{Jailing / being kicked out of the validator set} +This basically requires no change. +In each block you only iterate over the currently bonded validators. +So you simply don't update the "total accrued fees this period" variable for jailed / non-bonded validators. +Withdrawing requires \textit{no} special casing here! + +\section{State Requirements} +State entries can be pruned quite effectively. +Suppose for the sake of exposition that there is at most one delegation / withdrawal to a particular validator in any given block. +Then each delegation is responsible for one addition to state. +Only the next period, and this delegator's withdrawal could depend on this entry. Thus once this delegator withdraws, this state entry can be pruned. +For the entry created by the delegator's withdrawal, that is only required by the creation of the next period. +Thus once the next period is created, that withdrawal's period can be deleted. + +This can be easily adapted to the case where there are multiple delegations / withdrawals per block, by maintaining a reference count in each period starting state entry. + +The slash entries for a validator can only be pruned when all of that validator's delegators have their bonding period starting after the slash. +This seems ineffective to keep track of, thus it is not worth it. +Each slash should instead remain in state until the validator unbonds and all delegators have their fees withdrawn. + +\section{Implementers Considerations} +TODO: Convert this section into a proper conclusion + +This is an extremely simple scheme with many nice benefits. +\begin{itemize} + \item The overhead per block is a simple iteration over the bonded validator set, which occurs anyway. (Thus it can be implemented ``for-free" with an optimized code-base) + \item Withdrawing earned fees only requires iterating over slashes since when you bonded. (Which is a negligible iteration) + \item There are no approximations in any of the calculations. (modulo minor errata resulting from fixed precision decimals used in divisions) + \item Supports arbitrary inflation models. (Thus could even vary upon block signers) + \item Supports arbitrary fee distribution amongst the validator set. (Thus can account for things like only online validators get fees, which has important incentivization impacts) + \item The above two can change on a live chain with no issues. + \item Validator commission rates can be changed every block + \item The simplicity of this scheme lends itself well to implementation +\end{itemize} + +Thus this scheme has efficiency improvements, simplicity improvements, and expressiveness improvements over the currently proposed schemes. With a correct fee distribution amongst the validator set, this solves the existing problem where one could withhold their signature for risk-free gain. + +\section{TO DOs} + +\begin{itemize} + \item A global fee pool can be described. + \item Mention storage optimization for how to prune slashing entries in the uniform inflation and iteration over slashing case + \item Add equation numbers + \item perhaps re-organize so that the no iteration + \item Section on decimal precision considerations (would unums help?), and mitigating errors in calculation with floats and decimals. -- This probably belongs in a corrollary markdown file in the implementation + \item Consider indicating that the withdraw action need not be a tx type and could instead happen 'transparently' when more coins are needed, if a chain desired this for UX / p2p efficiency. +\end{itemize} + + +\end{document} diff --git a/x/slashing/spec/02_state.md b/x/slashing/spec/02_state.md index ff83537b51..3644ff9651 100644 --- a/x/slashing/spec/02_state.md +++ b/x/slashing/spec/02_state.md @@ -10,9 +10,16 @@ Every block includes a set of precommits by the validators for the previous bloc known as the `LastCommitInfo` provided by Tendermint. A `LastCommitInfo` is valid so long as it contains precommits from +2/3 of total voting power. -Proposers are incentivized to include precommits from all validators in the `LastCommitInfo` +Proposers are incentivized to include precommits from all validators in the Tendermint `LastCommitInfo` by receiving additional fees proportional to the difference between the voting -power included in the `LastCommitInfo` and +2/3 (see [TODO](https://github.com/cosmos/cosmos-sdk/issues/967)). +power included in the `LastCommitInfo` and +2/3 (see [fee distribution](https://github.com/cosmos/cosmos-sdk/blob/master/docs/spec/fee_distribution)). + +``` +type LastCommitInfo struct { + Round int32 + Votes []VoteInfo +} +``` Validators are penalized for failing to be included in the `LastCommitInfo` for some number of blocks by being automatically jailed, potentially slashed, and unbonded. @@ -20,15 +27,16 @@ number of blocks by being automatically jailed, potentially slashed, and unbonde Information about validator's liveness activity is tracked through `ValidatorSigningInfo`. It is indexed in the store as follows: -- ValidatorSigningInfo: ` 0x01 | ConsAddrLen (1 byte) | ConsAddress -> ProtocolBuffer(ValSigningInfo)` -- MissedBlocksBitArray: ` 0x02 | ConsAddrLen (1 byte) | ConsAddress | LittleEndianUint64(signArrayIndex) -> VarInt(didMiss)` (varint is a number encoding format) +- ValidatorSigningInfo: ` 0x01 | ConsAddress -> ProtocolBuffer(ValSigningInfo)` +- MissedBlocksBitArray: ` 0x02 | ConsAddress | LittleEndianUint64(signArrayIndex) -> VarInt(didMiss)` (varint is a number encoding format) The first mapping allows us to easily lookup the recent signing info for a -validator based on the validator's consensus address. The second mapping acts +validator based on the validator's consensus address. + +The second mapping (`MissedBlocksBitArray`) acts as a bit-array of size `SignedBlocksWindow` that tells us if the validator missed the block for a given index in the bit-array. The index in the bit-array is given as little endian uint64. - The result is a `varint` that takes on `0` or `1`, where `0` indicates the validator did not miss (did sign) the corresponding block, and `1` indicates they missed the block (did not sign). @@ -40,31 +48,4 @@ bonded validator. The `SignedBlocksWindow` parameter defines the size The information stored for tracking validator liveness is as follows: -```protobuf -// ValidatorSigningInfo defines a validator's signing info for monitoring their -// liveness activity. -message ValidatorSigningInfo { - string address = 1; - // timestamp validator cannot be unjailed until - google.protobuf.Timestamp jailed_until = 2; - // whether or not a validator has been tombstoned (killed out of validator - // set) - bool tombstoned = 3; - // missed blocks counter (to avoid scanning the array every time) - int64 missed_blocks_counter = 4; - // how many times the validator joined to voter set - int64 voter_set_counter = 5; -} -``` - -Where: - -- **Address**: The validator's consensus address. -- **JailedUntil**: Time for which the validator is jailed until due to liveness downtime. -- **Tombstoned**: Desribes if the validator is tombstoned or not. It is set once the - validator commits an equivocation or for any other configured misbehiavor. -- **MissedBlocksCounter**: A counter kept to avoid unnecessary array reads. Note - that `Sum(MissedBlocksBitArray)` equals `MissedBlocksCounter` always. -- **VoterSetCounter**: A counter keeps tracking the number of incidents the validator - joins to the voter set. This in conjunction with the `SignedBlocksWindow` param - determines the index in the `MissedBlocksBitArray`. ++++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/slashing/v1beta1/slashing.proto#L11-L33 diff --git a/x/slashing/spec/03_messages.md b/x/slashing/spec/03_messages.md index 6e7d168ffd..d680b83cdf 100644 --- a/x/slashing/spec/03_messages.md +++ b/x/slashing/spec/03_messages.md @@ -20,14 +20,17 @@ message MsgUnjail { } ``` -And below is its corresponding handler: +Below is a pseudocode of the `MsgSrv/Unjail` RPC: ``` -handleMsgUnjail(tx MsgUnjail) +unjail(tx MsgUnjail) validator = getValidator(tx.ValidatorAddr) if validator == nil fail with "No validator found" + if getSelfDelegation(validator) == 0 + fail with "validator must self delegate before unjailing" + if !validator.Jailed fail with "Validator not jailed, cannot unjail" @@ -43,6 +46,6 @@ handleMsgUnjail(tx MsgUnjail) return ``` -If the validator has enough stake to be in the top `n = MaximumBondedValidators`, they will be automatically rebonded, +If the validator has enough stake to be in the top `n = MaximumBondedValidators`, it will be automatically rebonded, and all delegators still delegated to the validator will be rebonded and begin to again collect provisions and rewards. diff --git a/x/slashing/spec/05_hooks.md b/x/slashing/spec/05_hooks.md index d87f6d289a..90c5f7fe8f 100644 --- a/x/slashing/spec/05_hooks.md +++ b/x/slashing/spec/05_hooks.md @@ -4,7 +4,18 @@ order: 5 # Hooks -In this section we describe the "hooks" - slashing module code that runs when other events happen. +This section contains a description of the module's `hooks`. Hooks are operations that are executed automatically when events are raised. + +## Staking hooks + +The slashing module implements the `StakingHooks` defined in `x/staking` and are used as record-keeping of validators information. During the app initialization, these hooks should be registered in the staking module struct. + +The following hooks impact the slashing state: + ++ `AfterValidatorBonded` creates a `ValidatorSigningInfo` instance as described in the following section. ++ `AfterValidatorCreated` stores a validator's consensus key. ++ `AfterValidatorRemoved` removes a validator's consensus key. + ## Validator Bonded diff --git a/x/slashing/spec/06_events.md b/x/slashing/spec/06_events.md index c5fceda519..cd79a65272 100644 --- a/x/slashing/spec/06_events.md +++ b/x/slashing/spec/06_events.md @@ -6,7 +6,19 @@ order: 6 The slashing module emits the following events/tags: -## BeginBlocker +## MsgServer + +### MsgUnjail + +| Type | Attribute Key | Attribute Value | +| ------- | ------------- | --------------- | +| message | module | slashing | +| message | sender | {validatorAddress} | + + +## Keeper + +## BeginBlocker: HandleValidatorSignature | Type | Attribute Key | Attribute Value | | ----- | ------------- | --------------------------- | @@ -23,12 +35,14 @@ The slashing module emits the following events/tags: | liveness | missed_blocks | {missedBlocksCounter} | | liveness | height | {blockHeight} | -## Handlers -### MsgUnjail +### Slash -| Type | Attribute Key | Attribute Value | -| ------- | ------------- | --------------- | -| message | module | slashing | -| message | action | unjail | -| message | sender | {senderAddress} | ++ same as `"slash"` event from `HandleValidatorSignature`, but without the `jailed` attribute. + +### Jail + + +| Type | Attribute Key | Attribute Value | +| ----- | ------------- | ------------------ | +| slash | jailed | {validatorAddress} | diff --git a/x/slashing/spec/07_tombstone.md b/x/slashing/spec/07_tombstone.md index 4759a89b71..216d1836f5 100644 --- a/x/slashing/spec/07_tombstone.md +++ b/x/slashing/spec/07_tombstone.md @@ -87,15 +87,17 @@ Currently, in the jail period implementation, once a validator unjails, all of their delegators who are delegated to them (haven't unbonded / redelegated away), stay with them. Given that consensus safety faults are so egregious (way more so than liveness faults), it is probably prudent to have delegators not -"auto-rebond" to the validator. Thus, we propose setting the "jail time" for a +"auto-rebond" to the validator. + +### Proposal: infinite jail + +We propose setting the "jail time" for a validator who commits a consensus safety fault, to `infinite` (i.e. a tombstone state). This essentially kicks the validator out of the validator set and does not allow them to re-enter the validator set. All of their delegators (including the operator themselves) have to either unbond or redelegate away. The validator operator can create a new validator if they would like, with a new operator key and consensus key, but they -have to "re-earn" their delegations back. To put the validator in the tombstone -state, we set `DoubleSignJailEndTime` to `time.Unix(253402300800)`, the maximum -time supported by Amino. +have to "re-earn" their delegations back. Implementing the tombstone system and getting rid of the slashing period tracking will make the `slashing` module way simpler, especially because we can remove all diff --git a/x/slashing/spec/08_params.md b/x/slashing/spec/08_params.md index 0ebfb9e27e..defed189ab 100644 --- a/x/slashing/spec/08_params.md +++ b/x/slashing/spec/08_params.md @@ -6,10 +6,10 @@ order: 8 The slashing module contains the following parameters: -| Key | Type | Example | -| ----------------------- | ---------------- | ---------------------- | -| SignedBlocksWindow | string (int64) | "100" | -| MinSignedPerWindow | string (dec) | "0.500000000000000000" | -| DowntimeJailDuration | string (time ns) | "600000000000" | -| SlashFractionDoubleSign | string (dec) | "0.050000000000000000" | -| SlashFractionDowntime | string (dec) | "0.010000000000000000" | +| Key | Type | Example | +| ----------------------- | -------------- | ---------------------- | +| SignedBlocksWindow | string (int64) | "100" | +| MinSignedPerWindow | string (dec) | "0.500000000000000000" | +| DowntimeJailDuration | string (ns) | "600000000000" | +| SlashFractionDoubleSign | string (dec) | "0.050000000000000000" | +| SlashFractionDowntime | string (dec) | "0.010000000000000000" | From 89a8a50cb257818103ec57782c465dc11aa30d1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?colin=20axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Sat, 16 Jan 2021 13:48:48 +0100 Subject: [PATCH 026/728] Revisions to Unfreezing Clients via Governance ADR (#8343) * revisions to adr 26 decision * Update docs/architecture/adr-026-ibc-client-recovery-mechanisms.md Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Co-authored-by: Christopher Goes From 8ac0390b9a733ea686df66f486b5a92f01712af8 Mon Sep 17 00:00:00 2001 From: Segue Date: Mon, 18 Jan 2021 19:45:06 +0800 Subject: [PATCH 027/728] Fix ibc client (#8341) * fix-ibc-client * add changelog From b799aa6f2090ab154be430a3769171c1a967d5e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Jan 2021 12:47:24 +0000 Subject: [PATCH 028/728] build(deps): bump vuepress-theme-cosmos from 1.0.179 to 1.0.180 in /docs (#8356) Bumps [vuepress-theme-cosmos](https://github.com/cosmos/vuepress-theme-cosmos) from 1.0.179 to 1.0.180. - [Release notes](https://github.com/cosmos/vuepress-theme-cosmos/releases) - [Commits](https://github.com/cosmos/vuepress-theme-cosmos/commits) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> From b6b56d7749bf0ba9fd8dee135c85e602af10fe6c Mon Sep 17 00:00:00 2001 From: Marko Date: Mon, 18 Jan 2021 16:41:34 +0100 Subject: [PATCH 029/728] fix proto generation (#8361) * fix proto generation * merge grpc_gateway into gocosmos_out * change env variable names From 43dde9db5e2377cc8cba35660ade81fdb66f5d76 Mon Sep 17 00:00:00 2001 From: dudong2 Date: Thu, 3 Mar 2022 17:04:11 +0900 Subject: [PATCH 030/728] build fix --- codec/amino_codec_test.go | 2 +- x/auth/client/rest/rest_test.go | 4 ++-- x/feegrant/simulation/decoder_test.go | 3 +-- x/ibc/core/02-client/client/cli/tx.go | 8 ++++---- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/codec/amino_codec_test.go b/codec/amino_codec_test.go index bff4868c4e..f1239596cd 100644 --- a/codec/amino_codec_test.go +++ b/codec/amino_codec_test.go @@ -120,7 +120,7 @@ func TestAminoCodecUnpackAnyFails(t *testing.T) { func TestAminoCodecFullDecodeAndEncode(t *testing.T) { // This tx comes from https://github.com/cosmos/cosmos-sdk/issues/8117. - txSigned := `{"type":"lbm-sdk/StdTx","value":{"msg":[{"type":"lbm-sdk/MsgCreateValidator","value":{"description":{"moniker":"fulltest","identity":"satoshi","website":"example.com","details":"example inc"},"commission":{"rate":"0.500000000000000000","max_rate":"1.000000000000000000","max_change_rate":"0.200000000000000000"},"min_self_delegation":"1000000","delegator_address":"link14pt0q5cwf38zt08uu0n6yrstf3rndzr5057jys","validator_address":"linkvaloper14pt0q5cwf38zt08uu0n6yrstf3rndzr52q28gr","pubkey":{"type":"tendermint/PubKeyEd25519","value":"CYrOiM3HtS7uv1B1OAkknZnFYSRpQYSYII8AtMMtev0="},"value":{"denom":"umuon","amount":"700000000"}}}],"fee":{"amount":[{"denom":"umuon","amount":"6000"}],"gas":"160000"},"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AwAOXeWgNf1FjMaayrSnrOOKz+Fivr6DiI/i0x0sZCHw"},"signature":"RcnfS/u2yl7uIShTrSUlDWvsXo2p2dYu6WJC8VDVHMBLEQZWc8bsINSCjOnlsIVkUNNe1q/WCA9n3Gy1+0zhYA=="}],"memo":"","timeout_height":"0"}}` + txSigned := `{"type":"lbm-sdk/StdTx","value":{"msg":[{"type":"lbm-sdk/MsgCreateValidator","value":{"description":{"moniker":"fulltest","identity":"satoshi","website":"example.com","details":"example inc"},"commission":{"rate":"0.500000000000000000","max_rate":"1.000000000000000000","max_change_rate":"0.200000000000000000"},"min_self_delegation":"1000000","delegator_address":"link14pt0q5cwf38zt08uu0n6yrstf3rndzr5057jys","validator_address":"linkvaloper14pt0q5cwf38zt08uu0n6yrstf3rndzr52q28gr","pubkey":{"type":"ostracon/PubKeyEd25519","value":"CYrOiM3HtS7uv1B1OAkknZnFYSRpQYSYII8AtMMtev0="},"value":{"denom":"umuon","amount":"700000000"}}}],"fee":{"amount":[{"denom":"umuon","amount":"6000"}],"gas":"160000"},"signatures":[{"pub_key":{"type":"ostracon/PubKeySecp256k1","value":"AwAOXeWgNf1FjMaayrSnrOOKz+Fivr6DiI/i0x0sZCHw"},"signature":"RcnfS/u2yl7uIShTrSUlDWvsXo2p2dYu6WJC8VDVHMBLEQZWc8bsINSCjOnlsIVkUNNe1q/WCA9n3Gy1+0zhYA=="}],"memo":"","timeout_height":"0"}}` var legacyCdc = simapp.MakeTestEncodingConfig().Amino var tx legacytx.StdTx err := legacyCdc.UnmarshalJSON([]byte(txSigned), &tx) diff --git a/x/auth/client/rest/rest_test.go b/x/auth/client/rest/rest_test.go index c1ffcfe6e7..459387f082 100644 --- a/x/auth/client/rest/rest_test.go +++ b/x/auth/client/rest/rest_test.go @@ -32,8 +32,8 @@ import ( "github.com/line/lbm-sdk/x/auth/legacy/legacytx" bankcli "github.com/line/lbm-sdk/x/bank/client/testutil" "github.com/line/lbm-sdk/x/bank/types" + ibcclientcli "github.com/line/lbm-sdk/x/ibc/core/02-client/client/cli" ibccli "github.com/line/lbm-sdk/x/ibc/core/04-channel/client/cli" - ibcsolomachinecli "github.com/line/lbm-sdk/x/ibc/light-clients/06-solomachine/client/cli" ) type IntegrationTestSuite struct { @@ -501,7 +501,7 @@ func (s *IntegrationTestSuite) TestLegacyRestErrMessages() { // a solo machine client state clientStateJSON := testutil.WriteToNewTempFile( s.T(), - `{"@type":"/ibc.lightclients.solomachine.v1.ClientState","sequence":"1","frozen_sequence":"0","consensus_state":{"public_key":{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AtK50+5pJOoaa04qqAqrnyAqsYrwrR/INnA6UPIaYZlp"},"diversifier":"testing","timestamp":"10"},"allow_update_after_proposal":false}`, + `{"@type":"/ibc.lightclients.solomachine.v1.ClientState","sequence":"1","frozen_sequence":"0","consensus_state":{"public_key":{"@type":"/lbm.crypto.secp256k1.PubKey","key":"AtK50+5pJOoaa04qqAqrnyAqsYrwrR/INnA6UPIaYZlp"},"diversifier":"testing","timestamp":"10"},"allow_update_after_proposal":false}`, ) // Write consensus json to temp file, used for an IBC message. diff --git a/x/feegrant/simulation/decoder_test.go b/x/feegrant/simulation/decoder_test.go index be5a0b5bcf..13cc70bf20 100644 --- a/x/feegrant/simulation/decoder_test.go +++ b/x/feegrant/simulation/decoder_test.go @@ -18,12 +18,11 @@ import ( var ( granterPk = ed25519.GenPrivKey().PubKey() granterAddr = sdk.AccAddress(granterPk.Address()) - granteePk = ed25519.GenPrivKey().PubKey() granteeAddr = sdk.AccAddress(granterPk.Address()) ) func TestDecodeStore(t *testing.T) { - cdc, _ := simapp.MakeCodecs() + cdc := simapp.MakeTestEncodingConfig().Marshaler dec := simulation.NewDecodeStore(cdc) grant, err := types.NewGrant(granterAddr, granteeAddr, &types.BasicAllowance{ diff --git a/x/ibc/core/02-client/client/cli/tx.go b/x/ibc/core/02-client/client/cli/tx.go index 28cdf2e05a..b8d66c7b9f 100644 --- a/x/ibc/core/02-client/client/cli/tx.go +++ b/x/ibc/core/02-client/client/cli/tx.go @@ -22,8 +22,8 @@ func NewCreateClientCmd() *cobra.Command { Use: "create [path/to/client_state.json] [path/to/consensus_state.json]", Short: "create new IBC client", Long: `create a new IBC client with the specified client state and consensus state - - ClientState JSON example: {"@type":"/ibc.lightclients.solomachine.v1.ClientState","sequence":"1","frozen_sequence":"0","consensus_state":{"public_key":{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AtK50+5pJOoaa04qqAqrnyAqsYrwrR/INnA6UPIaYZlp"},"diversifier":"testing","timestamp":"10"},"allow_update_after_proposal":false} - - ConsensusState JSON example: {"@type":"/ibc.lightclients.solomachine.v1.ConsensusState","public_key":{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AtK50+5pJOoaa04qqAqrnyAqsYrwrR/INnA6UPIaYZlp"},"diversifier":"testing","timestamp":"10"}`, + - ClientState JSON example: {"@type":"/ibc.lightclients.solomachine.v1.ClientState","sequence":"1","frozen_sequence":"0","consensus_state":{"public_key":{"@type":"/lbm.crypto.secp256k1.PubKey","key":"AtK50+5pJOoaa04qqAqrnyAqsYrwrR/INnA6UPIaYZlp"},"diversifier":"testing","timestamp":"10"},"allow_update_after_proposal":false} + - ConsensusState JSON example: {"@type":"/ibc.lightclients.solomachine.v1.ConsensusState","public_key":{"@type":"/lbm.crypto.secp256k1.PubKey","key":"AtK50+5pJOoaa04qqAqrnyAqsYrwrR/INnA6UPIaYZlp"},"diversifier":"testing","timestamp":"10"}`, Example: fmt.Sprintf("%s tx ibc %s create [path/to/client_state.json] [path/to/consensus_state.json] --from node0 --home ../node0/cli --chain-id $CID", version.AppName, types.SubModuleName), Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { @@ -180,8 +180,8 @@ func NewUpgradeClientCmd() *cobra.Command { Use: "upgrade [client-identifier] [path/to/client_state.json] [path/to/consensus_state.json] [upgrade-client-proof] [upgrade-consensus-state-proof]", Short: "upgrade an IBC client", Long: `upgrade the IBC client associated with the provided client identifier while providing proof committed by the counterparty chain to the new client and consensus states - - ClientState JSON example: {"@type":"/ibc.lightclients.solomachine.v1.ClientState","sequence":"1","frozen_sequence":"0","consensus_state":{"public_key":{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AtK50+5pJOoaa04qqAqrnyAqsYrwrR/INnA6UPIaYZlp"},"diversifier":"testing","timestamp":"10"},"allow_update_after_proposal":false} - - ConsensusState JSON example: {"@type":"/ibc.lightclients.solomachine.v1.ConsensusState","public_key":{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AtK50+5pJOoaa04qqAqrnyAqsYrwrR/INnA6UPIaYZlp"},"diversifier":"testing","timestamp":"10"}`, + - ClientState JSON example: {"@type":"/ibc.lightclients.solomachine.v1.ClientState","sequence":"1","frozen_sequence":"0","consensus_state":{"public_key":{"@type":"/lbm.crypto.secp256k1.PubKey","key":"AtK50+5pJOoaa04qqAqrnyAqsYrwrR/INnA6UPIaYZlp"},"diversifier":"testing","timestamp":"10"},"allow_update_after_proposal":false} + - ConsensusState JSON example: {"@type":"/ibc.lightclients.solomachine.v1.ConsensusState","public_key":{"@type":"/lbm.crypto.secp256k1.PubKey","key":"AtK50+5pJOoaa04qqAqrnyAqsYrwrR/INnA6UPIaYZlp"},"diversifier":"testing","timestamp":"10"}`, Example: fmt.Sprintf("%s tx ibc %s upgrade [client-identifier] [path/to/client_state.json] [path/to/consensus_state.json] [client-state-proof] [consensus-state-proof] --from node0 --home ../node0/cli --chain-id $CID", version.AppName, types.SubModuleName), Args: cobra.ExactArgs(5), RunE: func(cmd *cobra.Command, args []string) error { From 3ef5cea8c29988cce6959ef89e8096108179aa7e Mon Sep 17 00:00:00 2001 From: Tess Rinearson Date: Mon, 18 Jan 2021 17:48:19 +0100 Subject: [PATCH 031/728] simapp: add testnet instructions (#8342) --- simapp/README.md | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 simapp/README.md diff --git a/simapp/README.md b/simapp/README.md new file mode 100644 index 0000000000..fc449f7f2d --- /dev/null +++ b/simapp/README.md @@ -0,0 +1,51 @@ +--- +order: false +--- + +# simapp + +simapp is an application built using the Cosmos SDK for testing and educational purposes. + +## Running testnets with `simd` + +If you want to spin up a quick testnet with your friends, you can follow these steps. +Unless otherwise noted, every step must be done by everyone who wants to participate +in this testnet. + +1. `$ make build`. This will build the `simd` binary and install it in your Cosmos SDK repo, + inside a new `build` directory. The following instructions are run from inside + that directory. +2. If you've run `simd` before, you may need to reset your database before starting a new + testnet: `$ ./simd unsafe-reset-all` +3. `$ ./simd init [moniker]`. This will initialize a new working directory, by default at + `~/.simapp`. You need a provide a "moniker," but it doesn't matter what it is. +4. `$ ./simd keys add [key_name]`. This will create a new key, with a name of your choosing. + Save the output of this command somewhere; you'll need the address generated here later. +5. `$ ./simd add-genesis-account $(simd keys show [key_name] -a) [amount]`, where `key_name` + is the same key name as before; and `amount` is something like `10000000000000000000000000stake`. +6. `$ ./simd gentx [key_name] [amount] --chain-id [chain-id]`. This will create the + genesis transaction for your new chain. +7. Now, one person needs to create the genesis file `genesis.json` using the genesis transactions + from every participant, by gathering all the genesis transactions under `config/gentx` and then + calling `./simd collect-gentxs`. This will create a new `genesis.json` file that includes data + from all the validators (we sometimes call it the "super genesis file" to distinguish it from + single-validator genesis files). +8. Once you've received the super genesis file, overwrite your original `genesis.json` file with + the new super `genesis.json`. +9. Modify your `config/config.toml` (in the simapp working directory) to include the other participants as + persistent peers: + + ``` + # Comma separated list of nodes to keep persistent connections to + persistent_peers = "[validator address]@[ip address]:[port],[validator address]@[ip address]:[port]" + ``` + + You can find `validator address` by running `./simd tendermint show-node-id`. (It will be hex-encoded.) + By default, `port` is 26656. +10. Now you can start your nodes: `$ ./simd start`. + +Now you have a small testnet that you can use to try out changes to the Cosmos SDK or Tendermint! + +NOTE: Sometimes creating the network through the `collect-gentxs` will fail, and validators will start +in a funny state (and then panic). If this happens, you can try to create and start the network first +with a single validator and then add additional validators using a `create-validator` transaction. \ No newline at end of file From 82ddfe5a0bdde76b9b1b4af84349e36c80419483 Mon Sep 17 00:00:00 2001 From: Robert Zaremba Date: Mon, 18 Jan 2021 18:00:04 +0100 Subject: [PATCH 032/728] fix: wrappedError.Is (#8355) * fix: wrappedError.Is Is method should return true when used on 2 same errors. * Changelog update Co-authored-by: Alessio Treglia Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> From 1f74c9f54f9e79bae3d2f50edc6111ed86fa4985 Mon Sep 17 00:00:00 2001 From: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Date: Mon, 18 Jan 2021 15:15:16 -0300 Subject: [PATCH 033/728] x/bank: client denom metadata gRPC (#8317) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * deps: bump tendermint to v0.34.2 * x/bank: denom metadata gRPC * cli: add cmd and tests * gRPC test' * changelog * fix test * Apply suggestions from code review Co-authored-by: Robert Zaremba Co-authored-by: Aditya * fix panic * use cmd.Context() * update tests * Update x/bank/types/errors.go Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> * test fix Co-authored-by: Robert Zaremba Co-authored-by: Aditya Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- x/bank/client/rest/grpc_query_test.go | 1 + x/bank/client/rest/query_test.go | 1 + x/bank/keeper/genesis_test.go | 3 +- x/bank/keeper/grpc_query.go | 4 +- x/bank/keeper/grpc_query_test.go | 179 ++++++++++++++++++++++++++ x/bank/keeper/keeper.go | 8 +- x/bank/keeper/keeper_test.go | 3 +- 7 files changed, 191 insertions(+), 8 deletions(-) diff --git a/x/bank/client/rest/grpc_query_test.go b/x/bank/client/rest/grpc_query_test.go index 33e43bef93..455bdcdae7 100644 --- a/x/bank/client/rest/grpc_query_test.go +++ b/x/bank/client/rest/grpc_query_test.go @@ -1,3 +1,4 @@ +//go:build norace // +build norace package rest_test diff --git a/x/bank/client/rest/query_test.go b/x/bank/client/rest/query_test.go index 3706e60121..618cbf6e66 100644 --- a/x/bank/client/rest/query_test.go +++ b/x/bank/client/rest/query_test.go @@ -1,3 +1,4 @@ +//go:build norace // +build norace package rest_test diff --git a/x/bank/keeper/genesis_test.go b/x/bank/keeper/genesis_test.go index d365c44957..bb8402781d 100644 --- a/x/bank/keeper/genesis_test.go +++ b/x/bank/keeper/genesis_test.go @@ -50,6 +50,7 @@ func (suite *IntegrationTestSuite) TestInitGenesis() { bk := suite.app.BankKeeper bk.InitGenesis(suite.ctx, g) - m2 := bk.GetDenomMetaData(suite.ctx, m.Base) + m2, found := bk.GetDenomMetaData(suite.ctx, m.Base) + suite.Require().True(found) suite.Require().Equal(m, m2) } diff --git a/x/bank/keeper/grpc_query.go b/x/bank/keeper/grpc_query.go index 19ef67296a..c4393e50ac 100644 --- a/x/bank/keeper/grpc_query.go +++ b/x/bank/keeper/grpc_query.go @@ -160,8 +160,8 @@ func (k BaseKeeper) DenomMetadata(c context.Context, req *types.QueryDenomMetada ctx := sdk.UnwrapSDKContext(c) - metadata := k.GetDenomMetaData(ctx, req.Denom) - if metadata.Base == "" && metadata.Display == "" && metadata.Description == "" && len(metadata.DenomUnits) == 0 { + metadata, found := k.GetDenomMetaData(ctx, req.Denom) + if !found { return nil, status.Errorf(codes.NotFound, "client metadata for denom %s", req.Denom) } diff --git a/x/bank/keeper/grpc_query_test.go b/x/bank/keeper/grpc_query_test.go index c13a705fc1..b0731e5d62 100644 --- a/x/bank/keeper/grpc_query_test.go +++ b/x/bank/keeper/grpc_query_test.go @@ -306,3 +306,182 @@ func (suite *IntegrationTestSuite) QueryDenomMetadataRequest() { }) } } + +func (suite *IntegrationTestSuite) QueryDenomsMetadataRequest() { + var ( + req *types.QueryDenomsMetadataRequest + expMetadata = []types.Metadata{} + ) + + testCases := []struct { + msg string + malleate func() + expPass bool + }{ + { + "empty pagination", + func() { + req = &types.QueryDenomsMetadataRequest{} + }, + true, + }, + { + "success, no results", + func() { + req = &types.QueryDenomsMetadataRequest{ + Pagination: &query.PageRequest{ + Limit: 3, + CountTotal: true, + }, + } + }, + true, + }, + { + "success", + func() { + metadataAtom := types.Metadata{ + Description: "The native staking token of the Cosmos Hub.", + DenomUnits: []*types.DenomUnit{ + { + Denom: "uatom", + Exponent: 0, + Aliases: []string{"microatom"}, + }, + { + Denom: "atom", + Exponent: 6, + Aliases: []string{"ATOM"}, + }, + }, + Base: "uatom", + Display: "atom", + } + + metadataEth := types.Metadata{ + Description: "Ethereum native token", + DenomUnits: []*types.DenomUnit{ + { + Denom: "wei", + Exponent: 0, + }, + { + Denom: "eth", + Exponent: 18, + Aliases: []string{"ETH", "ether"}, + }, + }, + Base: "wei", + Display: "eth", + } + + suite.app.BankKeeper.SetDenomMetaData(suite.ctx, metadataAtom) + suite.app.BankKeeper.SetDenomMetaData(suite.ctx, metadataEth) + expMetadata = []types.Metadata{metadataAtom, metadataEth} + req = &types.QueryDenomsMetadataRequest{ + Pagination: &query.PageRequest{ + Limit: 7, + CountTotal: true, + }, + } + }, + true, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := sdk.WrapSDKContext(suite.ctx) + + res, err := suite.queryClient.DenomsMetadata(ctx, req) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expMetadata, res.Metadatas) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *IntegrationTestSuite) QueryDenomMetadataRequest() { + var ( + req *types.QueryDenomMetadataRequest + expMetadata = types.Metadata{} + ) + + testCases := []struct { + msg string + malleate func() + expPass bool + }{ + { + "empty denom", + func() { + req = &types.QueryDenomMetadataRequest{} + }, + false, + }, + { + "not found denom", + func() { + req = &types.QueryDenomMetadataRequest{ + Denom: "foo", + } + }, + false, + }, + { + "success", + func() { + expMetadata := types.Metadata{ + Description: "The native staking token of the Cosmos Hub.", + DenomUnits: []*types.DenomUnit{ + { + Denom: "uatom", + Exponent: 0, + Aliases: []string{"microatom"}, + }, + { + Denom: "atom", + Exponent: 6, + Aliases: []string{"ATOM"}, + }, + }, + Base: "uatom", + Display: "atom", + } + + suite.app.BankKeeper.SetDenomMetaData(suite.ctx, expMetadata) + req = &types.QueryDenomMetadataRequest{ + Denom: expMetadata.Base, + } + }, + true, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := sdk.WrapSDKContext(suite.ctx) + + res, err := suite.queryClient.DenomMetadata(ctx, req) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expMetadata, res.Metadata) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/x/bank/keeper/keeper.go b/x/bank/keeper/keeper.go index 192892a9eb..6277e37870 100644 --- a/x/bank/keeper/keeper.go +++ b/x/bank/keeper/keeper.go @@ -27,7 +27,7 @@ type Keeper interface { GetSupply(ctx sdk.Context) exported.SupplyI SetSupply(ctx sdk.Context, supply exported.SupplyI) - GetDenomMetaData(ctx sdk.Context, denom string) types.Metadata + GetDenomMetaData(ctx sdk.Context, denom string) (types.Metadata, bool) SetDenomMetaData(ctx sdk.Context, denomMetaData types.Metadata) IterateAllDenomMetaData(ctx sdk.Context, cb func(types.Metadata) bool) @@ -180,13 +180,13 @@ func (k BaseKeeper) SetSupply(ctx sdk.Context, supply exported.SupplyI) { } // GetDenomMetaData retrieves the denomination metadata -func (k BaseKeeper) GetDenomMetaData(ctx sdk.Context, denom string) types.Metadata { +func (k BaseKeeper) GetDenomMetaData(ctx sdk.Context, denom string) (types.Metadata, bool) { store := ctx.KVStore(k.storeKey) store = prefix.NewStore(store, types.DenomMetadataKey(denom)) bz := store.Get([]byte(denom)) if bz == nil { - return types.Metadata{} + return types.Metadata{}, false } var metadata types.Metadata @@ -194,7 +194,7 @@ func (k BaseKeeper) GetDenomMetaData(ctx sdk.Context, denom string) types.Metada panic(err) } - return metadata + return metadata, true } // GetAllDenomMetaData retrieves all denominations metadata diff --git a/x/bank/keeper/keeper_test.go b/x/bank/keeper/keeper_test.go index a356e29427..b701546b1b 100644 --- a/x/bank/keeper/keeper_test.go +++ b/x/bank/keeper/keeper_test.go @@ -1015,7 +1015,8 @@ func (suite *IntegrationTestSuite) TestSetDenomMetaData() { app.BankKeeper.SetDenomMetaData(ctx, metadata[i]) } - actualMetadata := app.BankKeeper.GetDenomMetaData(ctx, metadata[1].Base) + actualMetadata, found := app.BankKeeper.GetDenomMetaData(ctx, metadata[1].Base) + suite.Require().True(found) suite.Require().Equal(metadata[1].GetBase(), actualMetadata.GetBase()) suite.Require().Equal(metadata[1].GetDisplay(), actualMetadata.GetDisplay()) suite.Require().Equal(metadata[1].GetDescription(), actualMetadata.GetDescription()) From 54cbddbfbe76b20dc8f1feb7fcb1cc3d5ef7b7a5 Mon Sep 17 00:00:00 2001 From: MD Aleem <72057206+aleem1314@users.noreply.github.com> Date: Mon, 18 Jan 2021 23:55:22 +0530 Subject: [PATCH 034/728] gRPC-web proxy (#8077) * init * WIP config * WIP add proxy server * fmt * WIP * setup proxy server * clean go.mod * lint * lint * lint * custom codec * lint * add comments * change grpc-proxy port * add grpc-web * update server/start.go * add tests * add test with client * Update server/start.go Co-authored-by: Anil Kumar Kammari * Update server/start.go Co-authored-by: Amaury * Update server/start.go Co-authored-by: Amaury * review changes * review changes * Update server/start.go Co-authored-by: Anil Kumar Kammari Co-authored-by: Amaury Co-authored-by: Aleksandr Bezobchuk Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- go.sum | 3 + server/config/config.go | 23 ++- server/config/toml.go | 13 ++ server/grpc/grpc_web.go | 25 +++ server/grpc/grpc_web_test.go | 313 +++++++++++++++++++++++++++++++++++ server/start.go | 25 ++- testutil/network/network.go | 17 +- testutil/network/util.go | 18 ++ 8 files changed, 430 insertions(+), 7 deletions(-) create mode 100644 server/grpc/grpc_web.go create mode 100644 server/grpc/grpc_web_test.go diff --git a/go.sum b/go.sum index 708737863a..0d5dac2a58 100644 --- a/go.sum +++ b/go.sum @@ -455,6 +455,7 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -529,6 +530,7 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= @@ -665,6 +667,7 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= diff --git a/server/config/config.go b/server/config/config.go index 0ae14ccb87..7d64c14e2d 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -17,8 +17,11 @@ import ( const ( defaultMinGasPrices = "" - // DefaultGRPCAddress is the default address the gRPC server binds to. + // DefaultGRPCAddress defines the default address to bind the gRPC server to. DefaultGRPCAddress = "0.0.0.0:9090" + + // DefaultGRPCWebAddress defines the default address to bind the gRPC-web server to. + DefaultGRPCWebAddress = "0.0.0.0:9091" ) // BaseConfig defines the server's basic configuration @@ -126,6 +129,15 @@ type GRPCConfig struct { Address string `mapstructure:"address"` } +// GRPCWebConfig defines configuration for the gRPC-web server. +type GRPCWebConfig struct { + // Enable defines if the gRPC-web should be enabled. + Enable bool `mapstructure:"enable"` + + // Address defines the gRPC-web server to listen on + Address string `mapstructure:"address"` +} + // StateSyncConfig defines the state sync snapshot configuration. type StateSyncConfig struct { // SnapshotInterval sets the interval at which state sync snapshots are taken. @@ -145,6 +157,7 @@ type Config struct { Telemetry telemetry.Config `mapstructure:"telemetry"` API APIConfig `mapstructure:"api"` GRPC GRPCConfig `mapstructure:"grpc"` + GRPCWeb GRPCWebConfig `mapstructure:"grpc-web"` StateSync StateSyncConfig `mapstructure:"state-sync"` } @@ -209,6 +222,10 @@ func DefaultConfig() *Config { Enable: true, Address: DefaultGRPCAddress, }, + GRPCWeb: GRPCWebConfig{ + Enable: true, + Address: DefaultGRPCWebAddress, + }, StateSync: StateSyncConfig{ SnapshotInterval: 0, SnapshotKeepRecent: 2, @@ -264,6 +281,10 @@ func GetConfig(v *viper.Viper) Config { Enable: v.GetBool("grpc.enable"), Address: v.GetString("grpc.address"), }, + GRPCWeb: GRPCWebConfig{ + Enable: v.GetBool("grpc-web.enable"), + Address: v.GetString("grpc-web.address"), + }, StateSync: StateSyncConfig{ SnapshotInterval: v.GetUint64("state-sync.snapshot-interval"), SnapshotKeepRecent: v.GetUint32("state-sync.snapshot-keep-recent"), diff --git a/server/config/toml.go b/server/config/toml.go index de057ebdfa..21aed8b34f 100644 --- a/server/config/toml.go +++ b/server/config/toml.go @@ -163,6 +163,19 @@ enable = {{ .GRPC.Enable }} # Address defines the gRPC server address to bind to. address = "{{ .GRPC.Address }}" +############################################################################### +### gRPC Web Configuration ### +############################################################################### + +[grpc-web] + +# GRPCWebEnable defines if the gRPC-web should be enabled. +# NOTE: gRPC must also be enabled, otherwise, this configuration is a no-op. +enable = {{ .GRPCWeb.Enable }} + +# Address defines the gRPC-web server address to bind to. +address = "{{ .GRPCWeb.Address }}" + ############################################################################### ### State Sync Configuration ### ############################################################################### diff --git a/server/grpc/grpc_web.go b/server/grpc/grpc_web.go new file mode 100644 index 0000000000..30c71808e4 --- /dev/null +++ b/server/grpc/grpc_web.go @@ -0,0 +1,25 @@ +package grpc + +import ( + "net/http" + + "github.com/cosmos/cosmos-sdk/server/config" + "github.com/improbable-eng/grpc-web/go/grpcweb" + "google.golang.org/grpc" +) + +// StartGRPCWeb starts a gRPC-Web server on the given address. +func StartGRPCWeb(grpcSrv *grpc.Server, config config.Config) (*http.Server, error) { + wrappedServer := grpcweb.WrapServer(grpcSrv) + handler := func(resp http.ResponseWriter, req *http.Request) { + wrappedServer.ServeHTTP(resp, req) + } + grpcWebSrv := &http.Server{ + Addr: config.GRPCWeb.Address, + Handler: http.HandlerFunc(handler), + } + if err := grpcWebSrv.ListenAndServe(); err != nil { + return nil, err + } + return grpcWebSrv, nil +} diff --git a/server/grpc/grpc_web_test.go b/server/grpc/grpc_web_test.go new file mode 100644 index 0000000000..8f563becc6 --- /dev/null +++ b/server/grpc/grpc_web_test.go @@ -0,0 +1,313 @@ +package grpc_test + +import ( + "bufio" + "bytes" + "encoding/base64" + "encoding/binary" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/textproto" + "strconv" + "strings" + "testing" + + "github.com/golang/protobuf/proto" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "google.golang.org/grpc/codes" + + "github.com/cosmos/cosmos-sdk/client/grpc/tmservice" + "github.com/cosmos/cosmos-sdk/codec" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/testutil/network" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +// https://github.com/improbable-eng/grpc-web/blob/master/go/grpcweb/wrapper_test.go used as a reference +// to setup grpcRequest config. + +const grpcWebContentType = "application/grpc-web" + +type GRPCWebTestSuite struct { + suite.Suite + + cfg network.Config + network *network.Network + protoCdc *codec.ProtoCodec +} + +func (s *GRPCWebTestSuite) SetupSuite() { + s.T().Log("setting up integration test suite") + + cfg := network.DefaultConfig() + cfg.NumValidators = 1 + s.cfg = cfg + s.network = network.New(s.T(), s.cfg) + s.Require().NotNil(s.network) + + _, err := s.network.WaitForHeight(2) + s.Require().NoError(err) + + s.protoCdc = codec.NewProtoCodec(s.cfg.InterfaceRegistry) +} + +func (s *GRPCWebTestSuite) TearDownSuite() { + s.T().Log("tearing down integration test suite") + s.network.Cleanup() +} + +func (s *GRPCWebTestSuite) Test_Latest_Validators() { + val := s.network.Validators[0] + for _, contentType := range []string{grpcWebContentType} { + headers, trailers, responses, err := s.makeGrpcRequest( + "/cosmos.base.tendermint.v1beta1.Service/GetLatestValidatorSet", + headerWithFlag(), + serializeProtoMessages([]proto.Message{&tmservice.GetLatestValidatorSetRequest{}}), false) + + s.Require().NoError(err) + s.Require().Equal(1, len(responses)) + s.assertTrailerGrpcCode(trailers, codes.OK, "") + s.assertContentTypeSet(headers, contentType) + var valsSet tmservice.GetLatestValidatorSetResponse + err = s.protoCdc.UnmarshalBinaryBare(responses[0], &valsSet) + s.Require().NoError(err) + pubKey, ok := valsSet.Validators[0].PubKey.GetCachedValue().(cryptotypes.PubKey) + s.Require().Equal(true, ok) + s.Require().Equal(pubKey, val.PubKey) + } +} + +func (s *GRPCWebTestSuite) Test_Total_Supply() { + for _, contentType := range []string{grpcWebContentType} { + headers, trailers, responses, err := s.makeGrpcRequest( + "/cosmos.bank.v1beta1.Query/TotalSupply", + headerWithFlag(), + serializeProtoMessages([]proto.Message{&banktypes.QueryTotalSupplyRequest{}}), false) + + s.Require().NoError(err) + s.Require().Equal(1, len(responses)) + s.assertTrailerGrpcCode(trailers, codes.OK, "") + s.assertContentTypeSet(headers, contentType) + var totalSupply banktypes.QueryTotalSupplyResponse + _ = s.protoCdc.UnmarshalBinaryBare(responses[0], &totalSupply) + } +} + +func (s *GRPCWebTestSuite) assertContentTypeSet(headers http.Header, contentType string) { + s.Require().Equal(contentType, headers.Get("content-type"), `Expected there to be content-type=%v`, contentType) +} + +func (s *GRPCWebTestSuite) assertTrailerGrpcCode(trailers Trailer, code codes.Code, desc string) { + s.Require().NotEmpty(trailers.Get("grpc-status"), "grpc-status must not be empty in trailers") + statusCode, err := strconv.Atoi(trailers.Get("grpc-status")) + s.Require().NoError(err, "no error parsing grpc-status") + s.Require().EqualValues(code, statusCode, "grpc-status must match expected code") + s.Require().EqualValues(desc, trailers.Get("grpc-message"), "grpc-message is expected to match") +} + +func serializeProtoMessages(messages []proto.Message) [][]byte { + out := [][]byte{} + for _, m := range messages { + b, _ := proto.Marshal(m) + out = append(out, b) + } + return out +} + +func (s *GRPCWebTestSuite) makeRequest( + verb string, method string, headers http.Header, body io.Reader, isText bool, +) (*http.Response, error) { + val := s.network.Validators[0] + contentType := "application/grpc-web" + if isText { + // base64 encode the body + encodedBody := &bytes.Buffer{} + encoder := base64.NewEncoder(base64.StdEncoding, encodedBody) + _, err := io.Copy(encoder, body) + if err != nil { + return nil, err + } + err = encoder.Close() + if err != nil { + return nil, err + } + body = encodedBody + contentType = "application/grpc-web-text" + } + + url := fmt.Sprintf("http://%s%s", val.AppConfig.GRPCWeb.Address, method) + req, err := http.NewRequest(verb, url, body) + s.Require().NoError(err, "failed creating a request") + req.Header = headers + + req.Header.Set("Content-Type", contentType) + client := &http.Client{} + resp, err := client.Do(req) + return resp, err +} + +func decodeMultipleBase64Chunks(b []byte) ([]byte, error) { + // grpc-web allows multiple base64 chunks: the implementation may send base64-encoded + // "chunks" with potential padding whenever the runtime needs to flush a byte buffer. + // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md + output := make([]byte, base64.StdEncoding.DecodedLen(len(b))) + outputEnd := 0 + + for inputEnd := 0; inputEnd < len(b); { + chunk := b[inputEnd:] + paddingIndex := bytes.IndexByte(chunk, '=') + if paddingIndex != -1 { + // find the consecutive = + for { + paddingIndex += 1 + if paddingIndex >= len(chunk) || chunk[paddingIndex] != '=' { + break + } + } + chunk = chunk[:paddingIndex] + } + inputEnd += len(chunk) + + n, err := base64.StdEncoding.Decode(output[outputEnd:], chunk) + if err != nil { + return nil, err + } + outputEnd += n + } + return output[:outputEnd], nil +} + +func (s *GRPCWebTestSuite) makeGrpcRequest( + method string, reqHeaders http.Header, requestMessages [][]byte, isText bool, +) (headers http.Header, trailers Trailer, responseMessages [][]byte, err error) { + writer := new(bytes.Buffer) + for _, msgBytes := range requestMessages { + grpcPreamble := []byte{0, 0, 0, 0, 0} + binary.BigEndian.PutUint32(grpcPreamble[1:], uint32(len(msgBytes))) + writer.Write(grpcPreamble) + writer.Write(msgBytes) + } + resp, err := s.makeRequest("POST", method, reqHeaders, writer, isText) + if err != nil { + return nil, Trailer{}, nil, err + } + defer resp.Body.Close() + contents, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, Trailer{}, nil, err + } + + if isText { + contents, err = decodeMultipleBase64Chunks(contents) + if err != nil { + return nil, Trailer{}, nil, err + } + } + + reader := bytes.NewReader(contents) + for { + grpcPreamble := []byte{0, 0, 0, 0, 0} + readCount, err := reader.Read(grpcPreamble) + if err == io.EOF { + break + } + if readCount != 5 || err != nil { + return nil, Trailer{}, nil, fmt.Errorf("Unexpected end of body in preamble: %v", err) + } + payloadLength := binary.BigEndian.Uint32(grpcPreamble[1:]) + payloadBytes := make([]byte, payloadLength) + + readCount, err = reader.Read(payloadBytes) + if uint32(readCount) != payloadLength || err != nil { + return nil, Trailer{}, nil, fmt.Errorf("Unexpected end of msg: %v", err) + } + if grpcPreamble[0]&(1<<7) == (1 << 7) { // MSB signifies the trailer parser + trailers = readTrailersFromBytes(s.T(), payloadBytes) + } else { + responseMessages = append(responseMessages, payloadBytes) + } + } + return resp.Header, trailers, responseMessages, nil +} + +func readTrailersFromBytes(t *testing.T, dataBytes []byte) Trailer { + bufferReader := bytes.NewBuffer(dataBytes) + tp := textproto.NewReader(bufio.NewReader(bufferReader)) + + // First, read bytes as MIME headers. + // However, it normalizes header names by textproto.CanonicalMIMEHeaderKey. + // In the next step, replace header names by raw one. + mimeHeader, err := tp.ReadMIMEHeader() + if err == nil { + return Trailer{} + } + + trailers := make(http.Header) + bufferReader = bytes.NewBuffer(dataBytes) + tp = textproto.NewReader(bufio.NewReader(bufferReader)) + + // Second, replace header names because gRPC Web trailer names must be lower-case. + for { + line, err := tp.ReadLine() + if err == io.EOF { + break + } + require.NoError(t, err, "failed to read header line") + + i := strings.IndexByte(line, ':') + if i == -1 { + require.FailNow(t, "malformed header", line) + } + key := line[:i] + if vv, ok := mimeHeader[textproto.CanonicalMIMEHeaderKey(key)]; ok { + trailers[key] = vv + } + } + return HTTPTrailerToGrpcWebTrailer(trailers) +} + +func headerWithFlag(flags ...string) http.Header { + h := http.Header{} + for _, f := range flags { + h.Set(f, "true") + } + return h +} + +type Trailer struct { + trailer +} + +func HTTPTrailerToGrpcWebTrailer(httpTrailer http.Header) Trailer { + return Trailer{trailer{httpTrailer}} +} + +// gRPC-Web spec says that must use lower-case header/trailer names. +// See "HTTP wire protocols" section in +// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md#protocol-differences-vs-grpc-over-http2 +type trailer struct { + http.Header +} + +func (t trailer) Add(key, value string) { + key = strings.ToLower(key) + t.Header[key] = append(t.Header[key], value) +} + +func (t trailer) Get(key string) string { + if t.Header == nil { + return "" + } + v := t.Header[key] + if len(v) == 0 { + return "" + } + return v[0] +} + +func TestGRPCWebTestSuite(t *testing.T) { + suite.Run(t, new(GRPCWebTestSuite)) +} diff --git a/server/start.go b/server/start.go index d2c7425c35..7397d4d662 100644 --- a/server/start.go +++ b/server/start.go @@ -4,6 +4,7 @@ package server import ( "fmt" + "net/http" "os" "runtime/pprof" "time" @@ -60,8 +61,10 @@ const ( // GRPC-related flags. const ( - flagGRPCEnable = "grpc.enable" - flagGRPCAddress = "grpc.address" + flagGRPCEnable = "grpc.enable" + flagGRPCAddress = "grpc.address" + flagGRPCWebEnable = "grpc-web.enable" + flagGRPCWebAddress = "grpc-web.address" ) // State sync-related flags. @@ -159,6 +162,9 @@ which accepts a path for the resulting pprof file. cmd.Flags().Bool(flagGRPCEnable, true, "Define if the gRPC server should be enabled") cmd.Flags().String(flagGRPCAddress, config.DefaultGRPCAddress, "the gRPC server address to listen on") + cmd.Flags().Bool(flagGRPCWebEnable, true, "Define if the gRPC-Web server should be enabled. (Note: gRPC must also be enabled.)") + cmd.Flags().String(flagGRPCWebAddress, config.DefaultGRPCAddress, "The gRPC-Web server address to listen on") + cmd.Flags().Uint64(FlagStateSyncSnapshotInterval, 0, "State sync snapshot interval") cmd.Flags().Uint32(FlagStateSyncSnapshotKeepRecent, 2, "State sync snapshot to keep") @@ -317,12 +323,22 @@ func startInProcess(ctx *Context, clientCtx client.Context, appCreator types.App } } - var grpcSrv *grpc.Server + var ( + grpcSrv *grpc.Server + grpcWebSrv *http.Server + ) if config.GRPC.Enable { grpcSrv, err = servergrpc.StartGRPCServer(clientCtx, app, config.GRPC.Address) if err != nil { return err } + if config.GRPCWeb.Enable { + grpcWebSrv, err = servergrpc.StartGRPCWeb(grpcSrv, config) + if err != nil { + ctx.Logger.Error("failed to start grpc-web http server: ", err) + return err + } + } } defer func() { @@ -340,6 +356,9 @@ func startInProcess(ctx *Context, clientCtx client.Context, appCreator types.App if grpcSrv != nil { grpcSrv.Stop() + if grpcWebSrv != nil { + grpcWebSrv.Close() + } } ctx.Logger.Info("exiting...") diff --git a/testutil/network/network.go b/testutil/network/network.go index 978a832237..cdaffb466b 100644 --- a/testutil/network/network.go +++ b/testutil/network/network.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "io/ioutil" + "net/http" "net/url" "os" "path/filepath" @@ -158,9 +159,10 @@ type ( ValAddress sdk.ValAddress RPCClient ostclient.Client - tmNode *node.Node - api *api.Server - grpc *grpc.Server + tmNode *node.Node + api *api.Server + grpc *grpc.Server + grpcWeb *http.Server } ) @@ -213,6 +215,7 @@ func New(t *testing.T, cfg Config) *Network { apiAddr := "" tmCfg.RPC.ListenAddress = "" appCfg.GRPC.Enable = false + appCfg.GRPCWeb.Enable = false if i == 0 { apiListenAddr, _, err := server.FreeTCPAddr() require.NoError(t, err) @@ -230,6 +233,11 @@ func New(t *testing.T, cfg Config) *Network { require.NoError(t, err) appCfg.GRPC.Address = fmt.Sprintf("0.0.0.0:%s", grpcPort) appCfg.GRPC.Enable = true + + _, grpcWebPort, err := server.FreeTCPAddr() + require.NoError(t, err) + appCfg.GRPCWeb.Address = fmt.Sprintf("0.0.0.0:%s", grpcWebPort) + appCfg.GRPCWeb.Enable = true } logger := log.NewNopLogger() @@ -506,6 +514,9 @@ func (n *Network) Cleanup() { if v.grpc != nil { v.grpc.Stop() + if v.grpcWeb != nil { + _ = v.grpcWeb.Close() + } } } diff --git a/testutil/network/util.go b/testutil/network/util.go index 1bcf89a8aa..deb5e60841 100644 --- a/testutil/network/util.go +++ b/testutil/network/util.go @@ -103,6 +103,24 @@ func startInProcess(cfg Config, val *Validator) error { } val.grpc = grpcSrv + + if val.AppConfig.GRPCWeb.Enable { + errCh1 := make(chan error) + go func() { + grpcWeb, err := servergrpc.StartGRPCWeb(grpcSrv, *val.AppConfig) + if err != nil { + errCh1 <- err + } + + val.grpcWeb = grpcWeb + }() + select { + case err := <-errCh1: + return err + case <-time.After(5 * time.Second): // assume server started successfully + } + + } } return nil From c73f20d2308b831faa1fb5333a324a11a0a2e45e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?colin=20axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Mon, 18 Jan 2021 21:15:25 +0100 Subject: [PATCH 035/728] Add missing unpack interfaces functions to IBC (#8359) * add missing UnpackInterfaces functions * fix build * add tests cc @fedekunze Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> --- x/ibc/core/03-connection/types/query.go | 5 +++++ x/ibc/core/04-channel/types/query.go | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/x/ibc/core/03-connection/types/query.go b/x/ibc/core/03-connection/types/query.go index b99eeb55f5..fe93659ed9 100644 --- a/x/ibc/core/03-connection/types/query.go +++ b/x/ibc/core/03-connection/types/query.go @@ -11,6 +11,11 @@ var ( _ codectypes.UnpackInterfacesMessage = QueryConnectionConsensusStateResponse{} ) +var ( + _ codectypes.UnpackInterfacesMessage = QueryConnectionClientStateResponse{} + _ codectypes.UnpackInterfacesMessage = QueryConnectionConsensusStateResponse{} +) + // NewQueryConnectionResponse creates a new QueryConnectionResponse instance func NewQueryConnectionResponse( connection ConnectionEnd, proof []byte, height clienttypes.Height, diff --git a/x/ibc/core/04-channel/types/query.go b/x/ibc/core/04-channel/types/query.go index 6a02c01f9d..a77a0867e0 100644 --- a/x/ibc/core/04-channel/types/query.go +++ b/x/ibc/core/04-channel/types/query.go @@ -11,6 +11,11 @@ var ( _ codectypes.UnpackInterfacesMessage = QueryChannelConsensusStateResponse{} ) +var ( + _ codectypes.UnpackInterfacesMessage = QueryChannelClientStateResponse{} + _ codectypes.UnpackInterfacesMessage = QueryChannelConsensusStateResponse{} +) + // NewQueryChannelResponse creates a new QueryChannelResponse instance func NewQueryChannelResponse(channel Channel, proof []byte, height clienttypes.Height) *QueryChannelResponse { return &QueryChannelResponse{ From 4605d09395d7ceb3964e669c43afed7ce5db272f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?colin=20axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Tue, 19 Jan 2021 11:59:36 +0100 Subject: [PATCH 036/728] Update changelog (#8371) From e4f7cbc301dd0ece6fabddab6c3de7ef312ffd62 Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 19 Jan 2021 12:21:57 +0100 Subject: [PATCH 037/728] proto: docker deployment (#8367) --- .github/workflows/proto-docker.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/proto-docker.yml b/.github/workflows/proto-docker.yml index 2ce75d33d3..e7675718ce 100644 --- a/.github/workflows/proto-docker.yml +++ b/.github/workflows/proto-docker.yml @@ -1,5 +1,6 @@ name: Build & Push SDK Proto Builder on: + pull_request: push: branches: - main @@ -38,7 +39,7 @@ jobs: - name: Publish to Docker Hub uses: docker/build-push-action@v2 with: - context: ./docker-build-sdk-proto - file: ./docker-build-sdk-proto/Dockerfile + context: ./contrib/devtools + file: ./contrib/devtools/dockerfile push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.prep.outputs.tags }} From ea6a7232bc005b3955def0762f6667570e30af29 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Tue, 19 Jan 2021 16:49:38 +0000 Subject: [PATCH 038/728] update to tendermint v0.34.3 (#8388) * update to tendermint v0.34.3 * go.mod replace with grpc v1.33.2 Co-authored-by: Amaury Martiny From 10ccee97c98b14049904dfde961c42cda5b2120a Mon Sep 17 00:00:00 2001 From: Robert Zaremba Date: Wed, 20 Jan 2021 10:26:39 +0100 Subject: [PATCH 039/728] Update changelog after 0.40.1 release (#8394) From 0d97e15d1aa7b9701509c9cec46174510184bd55 Mon Sep 17 00:00:00 2001 From: Aditya Date: Wed, 20 Jan 2021 09:25:32 -0500 Subject: [PATCH 040/728] IBC Upgrade docs (#8298) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * start upgrade guide and some organization * add client developer guide * remove top-level upgrade doc * Apply suggestions from code review Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> * address @colin-axner suggestions * add order and synopsis * address @fedekunze review * address final @colin-axner comments Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> Co-authored-by: Christopher Goes Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- docs/ibc/upgrades/README.md | 14 ++++++++ docs/ibc/upgrades/developer-guide.md | 50 ++++++++++++++++++++++++++ docs/ibc/upgrades/quick-guide.md | 54 ++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 docs/ibc/upgrades/README.md create mode 100644 docs/ibc/upgrades/developer-guide.md create mode 100644 docs/ibc/upgrades/quick-guide.md diff --git a/docs/ibc/upgrades/README.md b/docs/ibc/upgrades/README.md new file mode 100644 index 0000000000..11ccabe237 --- /dev/null +++ b/docs/ibc/upgrades/README.md @@ -0,0 +1,14 @@ + + +### Upgrading IBC Chains Overview + +This directory contains information on how to upgrade an IBC chain without breaking counterparty clients and connections. + +IBC-connnected chains must be able to upgrade without breaking connections to other chains. Otherwise there would be a massive disincentive towards upgrading and disrupting high-value IBC connections, thus preventing chains in the IBC ecosystem from evolving and improving. Many chain upgrades may be irrelevant to IBC, however some upgrades could potentially break counterparty clients if not handled correctly. Thus, any IBC chain that wishes to perform a IBC-client-breaking upgrade must perform an IBC upgrade in order to allow counterparty clients to securely upgrade to the new light client. + +1. The [quick-guide](./quick-guide.md) describes how IBC-connected chains can perform client-breaking upgrades and how relayers can securely upgrade counterparty clients using the SDK. +2. The [developer-guide](./developer-guide.md) is a guide for developers intending to develop IBC client implementations with upgrade functionality. diff --git a/docs/ibc/upgrades/developer-guide.md b/docs/ibc/upgrades/developer-guide.md new file mode 100644 index 0000000000..998cb276e7 --- /dev/null +++ b/docs/ibc/upgrades/developer-guide.md @@ -0,0 +1,50 @@ + + +# IBC Client Developer Guide to Upgrades + +Learn how to implement upgrade functionality for your custom IBC client. {synopsis} + +As mentioned in the [README](./README.md), it is vital that high-value IBC clients can upgrade along with their underlying chains to avoid disruption to the IBC ecosystem. Thus, IBC client developers will want to implement upgrade functionality to enable clients to maintain connections and channels even across chain upgrades. + +The IBC protocol allows client implementations to provide a path to upgrading clients given the upgraded client state, upgraded consensus state and proofs for each. + +```go +// Upgrade functions +// NOTE: proof heights are not included as upgrade to a new revision is expected to pass only on the last +// height committed by the current revision. Clients are responsible for ensuring that the planned last +// height of the current revision is somehow encoded in the proof verification process. +// This is to ensure that no premature upgrades occur, since upgrade plans committed to by the counterparty +// may be cancelled or modified before the last planned height. +VerifyUpgradeAndUpdateState( + ctx sdk.Context, + cdc codec.BinaryMarshaler, + store sdk.KVStore, + newClient ClientState, + newConsState ConsensusState, + proofUpgradeClient, + proofUpgradeConsState []byte, +) (upgradedClient ClientState, upgradedConsensus ConsensusState, err error) +``` + +Note that the clients should have prior knowledge of the merkle path that the upgraded client and upgraded consensus states will use. The height at which the upgrade has occurred should also be encoded in the proof. The Tendermint client implementation accomplishes this by including an `UpgradePath` in the ClientState itself, which is used along with the upgrade height to construct the merkle path under which the client state and consensus state are committed. + +Developers must ensure that the `UpgradeClientMsg` does not pass until the last height of the old chain has been committed, and after the chain upgrades, the `UpgradeClientMsg` should pass once and only once on all counterparty clients. + +Developers must ensure that the new client adopts all of the new Client parameters that must be uniform across every valid light client of a chain (chain-chosen parameters), while maintaining the Client parameters that are customizable by each individual client (client-chosen parameters) from the previous version of the client. + +Upgrades must adhere to the IBC Security Model. IBC does not rely on the assumption of honest relayers for correctness. Thus users should not have to rely on relayers to maintain client correctness and security (though honest relayers must exist to maintain relayer liveness). While relayers may choose any set of client parameters while creating a new `ClientState`, this still holds under the security model since users can always choose a relayer-created client that suits their security and correctness needs or create a Client with their desired parameters if no such client exists. + +However, when upgrading an existing client, one must keep in mind that there are already many users who depend on this client's particular parameters. We cannot give the upgrading relayer free choice over these parameters once they have already been chosen. This would violate the security model since users who rely on the client would have to rely on the upgrading relayer to maintain the same level of security. Thus, developers must make sure that their upgrade mechanism allows clients to upgrade the chain-specified parameters whenever a chain upgrade changes these parameters (examples in the Tendermint client include `UnbondingPeriod`, `ChainID`, `UpgradePath`, etc.), while ensuring that the relayer submitting the `UpgradeClientMsg` cannot alter the client-chosen parameters that the users are relying upon (examples in Tendermint client include `TrustingPeriod`, `TrustLevel`, `MaxClockDrift`, etc). + +Developers should maintain the distinction between Client parameters that are uniform across every valid light client of a chain (chain-chosen parameters), and Client parameters that are customizable by each individual client (client-chosen parameters); since this distinction is necessary to implement the `ZeroCustomFields` method in the `ClientState` interface: + +```go +// Utility function that zeroes out any client customizable fields in client state +// Ledger enforced fields are maintained while all custom fields are zero values +// Used to verify upgrades +ZeroCustomFields() ClientState +``` + +Counterparty clients can upgrade securely by using all of the chain-chosen parameters from the chain-committed `UpgradedClient` and preserving all of the old client-chosen parameters. This enables chains to securely upgrade without relying on an honest relayer, however it can in some cases lead to an invalid final `ClientState` if the new chain-chosen parameters clash with the old client-chosen parameter. This can happen in the Tendermint client case if the upgrading chain lowers the `UnbondingPeriod` (chain-chosen) to a duration below that of a counterparty client's `TrustingPeriod` (client-chosen). Such cases should be clearly documented by developers, so that chains know which upgrades should be avoided to prevent this problem. The final upgraded client should also be validated in `VerifyUpgradeAndUpdateState` before returning to ensure that the client does not upgrade to an invalid `ClientState`. diff --git a/docs/ibc/upgrades/quick-guide.md b/docs/ibc/upgrades/quick-guide.md new file mode 100644 index 0000000000..4717e52f44 --- /dev/null +++ b/docs/ibc/upgrades/quick-guide.md @@ -0,0 +1,54 @@ + + +# How to Upgrade IBC Chains and their Clients + +Learn how to upgrade your chain and counterparty clients. {synopsis} + +The information in this doc for upgrading chains is relevant to SDK chains. However, the guide for counterparty clients is relevant to any Tendermint client that enables upgrades. + +### IBC Client Breaking Upgrades + +IBC-connected chains must perform an IBC upgrade if their upgrade will break counterparty IBC clients. The current IBC protocol supports upgrading tendermint chains for a specific subset of IBC-client-breaking upgrades. Here is the exhaustive list of IBC client-breaking upgrades and whether the IBC protocol currently supports such upgrades. + +IBC currently does **NOT** support unplanned upgrades. All of the following upgrades must be planned and committed to in advance by the upgrading chain, in order for counterparty clients to maintain their connections securely. + +Note: Since upgrades are only implemented for Tendermint clients, this doc only discusses upgrades on Tendermint chains that would break counterparty IBC Tendermint Clients. + +1. Changing the Chain-ID: **Supported** +2. Changing the UnbondingPeriod: **Partially Supported**, chains may increase the unbonding period with no issues. However, decreasing the unbonding period may irreversibly break some counterparty clients. Thus, it is **not recommended** that chains reduce the unbonding period. +3. Changing the height (resetting to 0): **Supported**, so long as chains remember to increment the revision number in their chain-id. +4. Changing the ProofSpecs: **Supported**, this should be changed if the proof structure needed to verify IBC proofs is changed across the upgrade. Ex: Switching from an IAVL store, to a SimpleTree Store +5. Changing the UpgradePath: **Supported**, this might involve changing the key under which upgraded clients and consensus states are stored in the upgrade store, or even migrating the upgrade store itself. +6. Migrating the IBC store: **Unsupported**, as the IBC store location is negotiated by the connection. +7. Upgrading to a backwards compatible version of IBC: Supported +8. Upgrading to a non-backwards compatible version of IBC: **Unsupported**, as IBC version is negotiated on connection handshake. +9. Changing the Tendermint LightClient algorithm: **Partially Supported**. Changes to the light client algorithm that do not change the ClientState or ConsensusState struct may be supported, provided that the counterparty is also upgraded to support the new light client algorithm. Changes that require updating the ClientState and ConsensusState structs themselves are theoretically possible by providing a path to translate an older ClientState struct into the new ClientState struct; however this is not currently implemented. + +### Step-by-Step Upgrade Process for SDK chains + +If the IBC-connected chain is conducting an upgrade that will break counterparty clients, it must ensure that the upgrade is first supported by IBC using the list above and then execute the upgrade process described below in order to prevent counterparty clients from breaking. + +1. Create a `SoftwareUpgradeProposal` with an `UpgradePlan` that includes the new IBC ClientState in the `UpgradedClientState`. Note that the `UpgradePlan` must specify an upgrade height **only** (no upgrade time), and the `ClientState` should only include the fields common to all valid clients and zero out any client-customizable fields (such as TrustingPeriod). +2. Vote on and pass the `SoftwareUpgradeProposal` + +Upon the `SoftwareUpgradeProposal` passing, the upgrade module will commit the UpgradedClient under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedClient`. On the block right before the upgrade height, the upgrade module will also commit an initial consensus state for the next chain under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedConsState`. + +Once the chain reaches the upgrade height and halts, a relayer can upgrade the counterparty clients to the last block of the old chain. They can then submit the proofs of the `UpgradedClient` and `UpgradedConsensusState` against this last block and upgrade the counterparty client. + +### Step-by-Step Upgrade Process for Relayers Upgrading Counterparty Clients + +Once the upgrading chain has committed to upgrading, relayers must wait till the chain halts at the upgrade height before upgrading counterparty clients. This is because chains may reschedule or cancel upgrade plans before they occur. Thus, relayers must wait till the chain reaches the upgrade height and halts before they can be sure the upgrade will take place. + +Thus, the upgrade process for relayers trying to upgrade the counterparty clients is as follows: + +1. Wait for the upgrading chain to reach the upgrade height and halt +2. Query a full node for the proofs of `UpgradedClient` and `UpgradedConsensusState` at the last height of the old chain. +3. Update the counterparty client to the last height of the old chain using the `UpdateClient` msg. +4. Submit an `UpgradeClient` msg to the counterparty chain with the `UpgradedClient`, `UpgradedConsensusState` and their respective proofs. +5. Submit an `UpdateClient` msg to the counterparty chain with a header from the new upgraded chain. + +The Tendermint client on the counterparty chain will verify that the upgrading chain did indeed commit to the upgraded client and upgraded consensus state at the upgrade height (since the upgrade height is included in the key). If the proofs are verified against the upgrade height, then the client will upgrade to the new client while retaining all of its client-customized fields. Thus, it will retain its old TrustingPeriod, TrustLevel, MaxClockDrift, etc; while adopting the new chain-specified fields such as UnbondingPeriod, ChainId, UpgradePath, etc. Note, this can lead to an invalid client since the old client-chosen fields may no longer be valid given the new chain-chosen fields. Upgrading chains should try to avoid these situations by not altering parameters that can break old clients. For an example, see the UnbondingPeriod example in the supported upgrades section. + +The upgraded consensus state will serve purely as a basis of trust for future `UpdateClientMsgs` and will not contain a consensus root to perform proof verification against. Thus, relayers must submit an `UpdateClientMsg` with a header from the new chain so that the connection can be used for proof verification again. \ No newline at end of file From c52670b66e4dd4ce37dd9266a7eadec9c7a06c57 Mon Sep 17 00:00:00 2001 From: Cyrus Goh Date: Thu, 21 Jan 2021 00:42:42 -0800 Subject: [PATCH 041/728] docs: add enhanceApp.js to .vuepress for redirects (#8354) --- docs/.vuepress/enhanceApp.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/.vuepress/enhanceApp.js b/docs/.vuepress/enhanceApp.js index 36cee6366f..e47a1f8237 100644 --- a/docs/.vuepress/enhanceApp.js +++ b/docs/.vuepress/enhanceApp.js @@ -2,7 +2,5 @@ export default ({ router }) => { router.addRoutes([ { path: '/master/spec/*', redirect: '/master/modules/' }, { path: '/master/spec/governance/', redirect: '/master/modules/gov/' }, - { path: '/v0.41/', redirect: '/v0.42/' }, - { path: '/v0.43/', redirect: '/v0.44/' }, ]) } From e30df8597106213430391514ecaf6df345504f46 Mon Sep 17 00:00:00 2001 From: SaReN Date: Thu, 21 Jan 2021 15:03:02 +0530 Subject: [PATCH 042/728] Rosetta API implementation (#7695) Ref: #7492 Co-authored-by: Jonathan Gimeno Co-authored-by: Alessio Treglia Co-authored-by: Frojdi Dymylja <33157909+fdymylja@users.noreply.github.com> Co-authored-by: Robert Zaremba Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> --- Makefile | 17 + contrib/rosetta/README.md | 24 + contrib/rosetta/configuration/bootstrap.json | 12 + contrib/rosetta/configuration/data.sh | 58 +++ contrib/rosetta/configuration/faucet.py | 25 ++ contrib/rosetta/configuration/rosetta.json | 51 +++ contrib/rosetta/configuration/run_tests.sh | 29 ++ contrib/rosetta/configuration/send_funds.sh | 5 + contrib/rosetta/configuration/staking.json | 30 ++ contrib/rosetta/configuration/staking.ros | 147 ++++++ contrib/rosetta/configuration/transfer.ros | 109 +++++ contrib/rosetta/docker-compose.yaml | 39 ++ contrib/rosetta/node/Dockerfile | 32 ++ contrib/rosetta/node/data.tar.gz | Bin 0 -> 34626 bytes contrib/rosetta/rosetta-cli/Dockerfile | 18 + docs/run-node/rosetta.md | 83 ++++ go.mod | 5 +- go.sum | 147 +++++- server/config/config.go | 40 ++ server/config/toml.go | 24 + server/grpc/grpc_web.go | 2 +- server/grpc/grpc_web_test.go | 10 +- server/rosetta.go | 42 ++ server/rosetta/client_offline.go | 221 +++++++++ server/rosetta/client_online.go | 447 +++++++++++++++++++ server/rosetta/codec.go | 22 + server/rosetta/config.go | 203 +++++++++ server/rosetta/conv_from_rosetta.go | 211 +++++++++ server/rosetta/conv_to_rosetta.go | 95 ++++ server/rosetta/types.go | 41 ++ server/rosetta/util.go | 112 +++++ server/start.go | 47 +- simapp/simd/cmd/root.go | 3 + types/tx/types.go | 33 ++ x/auth/client/cli/cli_test.go | 2 +- x/auth/client/cli/validate_sigs.go | 4 +- x/bank/types/msgs.go | 86 ++++ x/distribution/types/msg.go | 50 +++ x/genutil/client/cli/gentx_test.go | 2 +- x/ibc/core/03-connection/types/msgs_test.go | 4 +- x/slashing/client/rest/rest.go | 3 +- x/staking/client/rest/rest.go | 3 +- x/staking/types/msg.go | 281 ++++++++++++ x/upgrade/client/rest/rest.go | 3 +- 44 files changed, 2796 insertions(+), 26 deletions(-) create mode 100644 contrib/rosetta/README.md create mode 100644 contrib/rosetta/configuration/bootstrap.json create mode 100644 contrib/rosetta/configuration/data.sh create mode 100644 contrib/rosetta/configuration/faucet.py create mode 100644 contrib/rosetta/configuration/rosetta.json create mode 100755 contrib/rosetta/configuration/run_tests.sh create mode 100644 contrib/rosetta/configuration/send_funds.sh create mode 100644 contrib/rosetta/configuration/staking.json create mode 100644 contrib/rosetta/configuration/staking.ros create mode 100644 contrib/rosetta/configuration/transfer.ros create mode 100644 contrib/rosetta/docker-compose.yaml create mode 100644 contrib/rosetta/node/Dockerfile create mode 100644 contrib/rosetta/node/data.tar.gz create mode 100644 contrib/rosetta/rosetta-cli/Dockerfile create mode 100644 docs/run-node/rosetta.md create mode 100644 server/rosetta.go create mode 100644 server/rosetta/client_offline.go create mode 100644 server/rosetta/client_online.go create mode 100644 server/rosetta/codec.go create mode 100644 server/rosetta/config.go create mode 100644 server/rosetta/conv_from_rosetta.go create mode 100644 server/rosetta/conv_to_rosetta.go create mode 100644 server/rosetta/types.go create mode 100644 server/rosetta/util.go diff --git a/Makefile b/Makefile index ecb3273572..27825f25a5 100644 --- a/Makefile +++ b/Makefile @@ -345,6 +345,11 @@ test-cover: @export VERSION=$(VERSION); bash -x contrib/test_cover.sh .PHONY: test-cover +test-rosetta: + docker build -t rosetta-ci:latest -f contrib/rosetta/node/Dockerfile . + docker-compose -f contrib/rosetta/docker-compose.yaml up --abort-on-container-exit --exit-code-from test_rosetta --build +.PHONY: test-rosetta + benchmark: @go test -mod=readonly -bench=. $(PACKAGES_NOSIMULATION) .PHONY: benchmark @@ -523,3 +528,15 @@ localnet-stop: docker-compose down .PHONY: localnet-start localnet-stop + +############################################################################### +### rosetta ### +############################################################################### +# builds rosetta test data dir +rosetta-data: + -docker container rm data_dir_build + docker build -t rosetta-ci:latest -f contrib/rosetta/node/Dockerfile . + docker run --name data_dir_build -t rosetta-ci:latest sh /rosetta/data.sh + docker cp data_dir_build:/tmp/data.tar.gz "$(CURDIR)/contrib/rosetta/node/data.tar.gz" + docker container rm data_dir_build +.PHONY: rosetta-data \ No newline at end of file diff --git a/contrib/rosetta/README.md b/contrib/rosetta/README.md new file mode 100644 index 0000000000..b1d33659fe --- /dev/null +++ b/contrib/rosetta/README.md @@ -0,0 +1,24 @@ +# rosetta + +This directory contains the files required to run the rosetta CI. It builds `simapp` based on the current codebase. + +## docker-compose.yaml + +Builds: +- cosmos-sdk simapp node, with prefixed data directory, keys etc. This is required to test historical balances. +- faucet is required so we can test construction API, it was literally impossible to put there a deterministic address to request funds for +- rosetta is the rosetta node used by rosetta-cli to interact with the cosmos-sdk app +- test_rosetta runs the rosetta-cli test against construction API and data API + +## configuration + +Contains the required files to set up rosetta cli and make it work against its workflows + +## node + +Contains the files for a deterministic network, with fixed keys and some actions on there, to test parsing of msgs and historical balances. + +## Notes + +- Keyring password is 12345678 +- data.sh creates node data, it's required in case consensus breaking changes are made to quickly recreate replicable node data for rosetta diff --git a/contrib/rosetta/configuration/bootstrap.json b/contrib/rosetta/configuration/bootstrap.json new file mode 100644 index 0000000000..15b75b5508 --- /dev/null +++ b/contrib/rosetta/configuration/bootstrap.json @@ -0,0 +1,12 @@ +[ + { + "account_identifier": { + "address":"cosmos1hdmjfmqmf8ck4pv4evu0s3up0ucm0yjjqfl87e" + }, + "currency":{ + "symbol":"stake", + "decimals":0 + }, + "value": "999900000000" + } +] \ No newline at end of file diff --git a/contrib/rosetta/configuration/data.sh b/contrib/rosetta/configuration/data.sh new file mode 100644 index 0000000000..dc4f2cb59a --- /dev/null +++ b/contrib/rosetta/configuration/data.sh @@ -0,0 +1,58 @@ +#!/bin/sh + +set -e + +wait_simd() { + timeout 30 sh -c 'until nc -z $0 $1; do sleep 1; done' localhost 9090 +} +# this script is used to recreate the data dir +echo clearing /root/.simapp +rm -rf /root/.simapp +echo initting new chain +# init config files +simd init simd --chain-id testing + +# create accounts +simd keys add fd --keyring-backend=test + +addr=$(simd keys show fd -a --keyring-backend=test) + +# give the accounts some money +simd add-genesis-account "$addr" 1000000000000stake --keyring-backend=test + +# save configs for the daemon +simd gentx fd --chain-id testing --keyring-backend=test + +# input genTx to the genesis file +simd collect-gentxs +# verify genesis file is fine +simd validate-genesis +echo changing network settings +sed -i 's/127.0.0.1/0.0.0.0/g' /root/.simapp/config/config.toml + +# start simd +echo starting simd... +simd start --pruning=nothing & +pid=$! +echo simd started with PID $pid + +echo awaiting for simd to be ready +wait_simd +echo simd is ready +sleep 10 + + +# send transaction to deterministic address +echo sending transaction with addr $addr +simd tx bank send "$addr" cosmos1wjmt63j4fv9nqda92nsrp2jp2vsukcke4va3pt 100stake --yes --keyring-backend=test --broadcast-mode=block --chain-id=testing + +sleep 10 + +echo stopping simd... +kill -9 $pid + +echo zipping data dir and saving to /tmp/data.tar.gz + +tar -czvf /tmp/data.tar.gz /root/.simapp + +echo new address for bootstrap.json "$addr" diff --git a/contrib/rosetta/configuration/faucet.py b/contrib/rosetta/configuration/faucet.py new file mode 100644 index 0000000000..44536a84bb --- /dev/null +++ b/contrib/rosetta/configuration/faucet.py @@ -0,0 +1,25 @@ +from http.server import HTTPServer, BaseHTTPRequestHandler +import subprocess + +import os + + +class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): + + def do_POST(self): + try: + content_len = int(self.headers.get('Content-Length')) + addr = self.rfile.read(content_len).decode("utf-8") + print("sending funds to " + addr) + subprocess.call(['sh', './send_funds.sh', addr]) + self.send_response(200) + self.end_headers() + except Exception as e: + print("failed " + str(e)) + os._exit(1) + + +if __name__ == "__main__": + print("starting faucet server...") + httpd = HTTPServer(('0.0.0.0', 8000), SimpleHTTPRequestHandler) + httpd.serve_forever() diff --git a/contrib/rosetta/configuration/rosetta.json b/contrib/rosetta/configuration/rosetta.json new file mode 100644 index 0000000000..39a0bb3811 --- /dev/null +++ b/contrib/rosetta/configuration/rosetta.json @@ -0,0 +1,51 @@ +{ + "network": { + "blockchain": "app", + "network": "network" + }, + "online_url": "http://rosetta:8080", + "data_directory": "", + "http_timeout": 300, + "max_retries": 5, + "retry_elapsed_time": 0, + "max_online_connections": 0, + "max_sync_concurrency": 0, + "tip_delay": 60, + "log_configuration": true, + "construction": { + "offline_url": "http://rosetta:8080", + "max_offline_connections": 0, + "stale_depth": 0, + "broadcast_limit": 0, + "ignore_broadcast_failures": false, + "clear_broadcasts": false, + "broadcast_behind_tip": false, + "block_broadcast_limit": 0, + "rebroadcast_all": false, + "constructor_dsl_file": "transfer.ros", + "end_conditions": { + "create_account": 1, + "transfer": 3 + } + }, + "data": { + "active_reconciliation_concurrency": 0, + "inactive_reconciliation_concurrency": 0, + "inactive_reconciliation_frequency": 0, + "log_blocks": false, + "log_transactions": false, + "log_balance_changes": false, + "log_reconciliations": false, + "ignore_reconciliation_error": false, + "exempt_accounts": "", + "bootstrap_balances": "bootstrap.json", + "interesting_accounts": "", + "reconciliation_disabled": false, + "inactive_discrepency_search_disabled": false, + "balance_tracking_disabled": false, + "coin_tracking_disabled": false, + "end_conditions": { + "tip": true + } + } +} \ No newline at end of file diff --git a/contrib/rosetta/configuration/run_tests.sh b/contrib/rosetta/configuration/run_tests.sh new file mode 100755 index 0000000000..cd7af92acd --- /dev/null +++ b/contrib/rosetta/configuration/run_tests.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +set -e + +addr="abcd" + +send_tx() { + echo '12345678' | simd tx bank send $addr "$1" "$2" +} + +detect_account() { + line=$1 +} + +wait_for_rosetta() { + timeout 30 sh -c 'until nc -z $0 $1; do sleep 1; done' rosetta 8080 +} + +echo "waiting for rosetta instance to be up" +wait_for_rosetta + +echo "checking data API" +rosetta-cli check:data --configuration-file ./config/rosetta.json + +echo "checking construction API" +rosetta-cli check:construction --configuration-file ./config/rosetta.json + +echo "checking staking API" +rosetta-cli check:construction --configuration-file ./config/staking.json diff --git a/contrib/rosetta/configuration/send_funds.sh b/contrib/rosetta/configuration/send_funds.sh new file mode 100644 index 0000000000..3a897539d2 --- /dev/null +++ b/contrib/rosetta/configuration/send_funds.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +set -e +addr=$(simd keys show fd -a --keyring-backend=test) +echo "12345678" | simd tx bank send "$addr" "$1" 100stake --chain-id="testing" --node tcp://cosmos:26657 --yes --keyring-backend=test \ No newline at end of file diff --git a/contrib/rosetta/configuration/staking.json b/contrib/rosetta/configuration/staking.json new file mode 100644 index 0000000000..9c5e5da3ba --- /dev/null +++ b/contrib/rosetta/configuration/staking.json @@ -0,0 +1,30 @@ +{ + "network": { + "blockchain": "app", + "network": "network" + }, + "online_url": "http://rosetta:8080", + "data_directory": "", + "http_timeout": 300, + "max_retries": 5, + "retry_elapsed_time": 0, + "max_online_connections": 0, + "max_sync_concurrency": 0, + "tip_delay": 60, + "log_configuration": true, + "construction": { + "offline_url": "http://rosetta:8080", + "max_offline_connections": 0, + "stale_depth": 0, + "broadcast_limit": 0, + "ignore_broadcast_failures": false, + "clear_broadcasts": false, + "broadcast_behind_tip": false, + "block_broadcast_limit": 0, + "rebroadcast_all": false, + "constructor_dsl_file": "staking.ros", + "end_conditions": { + "staking": 3 + } + } +} \ No newline at end of file diff --git a/contrib/rosetta/configuration/staking.ros b/contrib/rosetta/configuration/staking.ros new file mode 100644 index 0000000000..1d9de1d180 --- /dev/null +++ b/contrib/rosetta/configuration/staking.ros @@ -0,0 +1,147 @@ +request_funds(1){ + find_account{ + currency = {"symbol":"stake", "decimals":0}; + random_account = find_balance({ + "minimum_balance":{ + "value": "0", + "currency": {{currency}} + }, + "create_limit":1 + }); + }, + send_funds{ + account_identifier = {{random_account.account_identifier}}; + address = {{account_identifier.address}}; + idk = http_request({ + "method": "POST", + "url": "http:\/\/faucet:8000", + "timeout": 10, + "body": {{random_account.account_identifier.address}} + }); + }, + // Create a separate scenario to request funds so that + // the address we are using to request funds does not + // get rolled back if funds do not yet exist. + request{ + loaded_account = find_balance({ + "account_identifier": {{random_account.account_identifier}}, + "minimum_balance":{ + "value": "100", + "currency": {{currency}} + } + }); + } +} +create_account(1){ + create{ + network = {"network":"network", "blockchain":"app"}; + key = generate_key({"curve_type": "secp256k1"}); + account = derive({ + "network_identifier": {{network}}, + "public_key": {{key.public_key}} + }); + // If the account is not saved, the key will be lost! + save_account({ + "account_identifier": {{account.account_identifier}}, + "keypair": {{key}} + }); + } +} + +staking(1){ + stake{ + stake.network = {"network":"network", "blockchain":"app"}; + currency = {"symbol":"stake", "decimals":0}; + sender = find_balance({ + "minimum_balance":{ + "value": "100", + "currency": {{currency}} + } + }); + // Set the recipient_amount as some value <= sender.balance-max_fee + max_fee = "0"; + fee_amount = "1"; + fee_value = 0 - {{fee_amount}}; + available_amount = {{sender.balance.value}} - {{max_fee}}; + recipient_amount = "1"; + print_message({"recipient_amount":{{recipient_amount}}}); + // Find recipient and construct operations + recipient = {{sender.account_identifier}}; + sender_amount = 0 - {{recipient_amount}}; + stake.confirmation_depth = "1"; + stake.operations = [ + { + "operation_identifier":{"index":0}, + "type":"fee", + "account":{{sender.account_identifier}}, + "amount":{ + "value":{{fee_value}}, + "currency":{{currency}} + } + }, + { + "operation_identifier":{"index":1}, + "type":"cosmos.staking.v1beta1.MsgDelegate", + "account":{{sender.account_identifier}}, + "amount":{ + "value":{{sender_amount}}, + "currency":{{currency}} + } + }, + { + "operation_identifier":{"index":2}, + "type":"cosmos.staking.v1beta1.MsgDelegate", + "account": { + "address": "staking_account", + "sub_account": { + "address" : "cosmosvaloper1hdmjfmqmf8ck4pv4evu0s3up0ucm0yjj9atjj2" + } + }, + "amount":{ + "value":{{recipient_amount}}, + "currency":{{currency}} + } + } + ]; + }, + undelegate{ + print_message({"undelegate":{{sender}}}); + + undelegate.network = {"network":"network", "blockchain":"app"}; + undelegate.confirmation_depth = "1"; + undelegate.operations = [ + { + "operation_identifier":{"index":0}, + "type":"fee", + "account":{{sender.account_identifier}}, + "amount":{ + "value":{{fee_value}}, + "currency":{{currency}} + } + }, + { + "operation_identifier":{"index":1}, + "type":"cosmos.staking.v1beta1.MsgUndelegate", + "account":{{sender.account_identifier}}, + "amount":{ + "value":{{recipient_amount}}, + "currency":{{currency}} + } + }, + { + "operation_identifier":{"index":2}, + "type":"cosmos.staking.v1beta1.MsgUndelegate", + "account": { + "address": "staking_account", + "sub_account": { + "address" : "cosmosvaloper1hdmjfmqmf8ck4pv4evu0s3up0ucm0yjj9atjj2" + } + }, + "amount":{ + "value":{{sender_amount}}, + "currency":{{currency}} + } + } + ]; + } +} diff --git a/contrib/rosetta/configuration/transfer.ros b/contrib/rosetta/configuration/transfer.ros new file mode 100644 index 0000000000..a1cb3f8caf --- /dev/null +++ b/contrib/rosetta/configuration/transfer.ros @@ -0,0 +1,109 @@ +request_funds(1){ + find_account{ + currency = {"symbol":"stake", "decimals":0}; + random_account = find_balance({ + "minimum_balance":{ + "value": "0", + "currency": {{currency}} + }, + "create_limit":1 + }); + }, + send_funds{ + account_identifier = {{random_account.account_identifier}}; + address = {{account_identifier.address}}; + idk = http_request({ + "method": "POST", + "url": "http:\/\/faucet:8000", + "timeout": 10, + "body": {{random_account.account_identifier.address}} + }); + }, + // Create a separate scenario to request funds so that + // the address we are using to request funds does not + // get rolled back if funds do not yet exist. + request{ + loaded_account = find_balance({ + "account_identifier": {{random_account.account_identifier}}, + "minimum_balance":{ + "value": "100", + "currency": {{currency}} + } + }); + } +} +create_account(1){ + create{ + network = {"network":"network", "blockchain":"app"}; + key = generate_key({"curve_type": "secp256k1"}); + account = derive({ + "network_identifier": {{network}}, + "public_key": {{key.public_key}} + }); + // If the account is not saved, the key will be lost! + save_account({ + "account_identifier": {{account.account_identifier}}, + "keypair": {{key}} + }); + } +} +transfer(3){ + transfer{ + transfer.network = {"network":"network", "blockchain":"app"}; + currency = {"symbol":"stake", "decimals":0}; + sender = find_balance({ + "minimum_balance":{ + "value": "100", + "currency": {{currency}} + } + }); + // Set the recipient_amount as some value <= sender.balance-max_fee + max_fee = "0"; + fee_amount = "1"; + fee_value = 0 - {{fee_amount}}; + available_amount = {{sender.balance.value}} - {{max_fee}}; + recipient_amount = random_number({"minimum": "1", "maximum": {{available_amount}}}); + print_message({"recipient_amount":{{recipient_amount}}}); + // Find recipient and construct operations + sender_amount = 0 - {{recipient_amount}}; + recipient = find_balance({ + "not_account_identifier":[{{sender.account_identifier}}], + "minimum_balance":{ + "value": "0", + "currency": {{currency}} + }, + "create_limit": 100, + "create_probability": 50 + }); + transfer.confirmation_depth = "1"; + transfer.operations = [ + { + "operation_identifier":{"index":0}, + "type":"fee", + "account":{{sender.account_identifier}}, + "amount":{ + "value":{{fee_value}}, + "currency":{{currency}} + } + }, + { + "operation_identifier":{"index":1}, + "type":"cosmos.bank.v1beta1.MsgSend", + "account":{{sender.account_identifier}}, + "amount":{ + "value":{{sender_amount}}, + "currency":{{currency}} + } + }, + { + "operation_identifier":{"index":2}, + "type":"cosmos.bank.v1beta1.MsgSend", + "account":{{recipient.account_identifier}}, + "amount":{ + "value":{{recipient_amount}}, + "currency":{{currency}} + } + } + ]; + } +} diff --git a/contrib/rosetta/docker-compose.yaml b/contrib/rosetta/docker-compose.yaml new file mode 100644 index 0000000000..0a9e82de8a --- /dev/null +++ b/contrib/rosetta/docker-compose.yaml @@ -0,0 +1,39 @@ +version: "3" + +services: + cosmos: + image: rosetta-ci:latest + command: ["simd", "start", "--pruning", "nothing", "--grpc-web.enable", "true", "--grpc-web.address", "0.0.0.0:9091"] + ports: + - 9090:9090 + - 26657:26657 + logging: + driver: "none" + + rosetta: + image: rosetta-ci:latest + command: [ + "simd", + "rosetta", + "--blockchain", "app", + "--network", "network", + "--tendermint", "cosmos:26657", + "--grpc", "cosmos:9090", + "--addr", ":8080", + ] + ports: + - 8080:8080 + + faucet: + image: rosetta-ci:latest + working_dir: /rosetta + command: ["python3", "faucet.py"] + expose: + - 8080 + + test_rosetta: + image: tendermintdev/rosetta-cli:v0.6.6 + volumes: + - ./configuration:/rosetta/config:z + command: ["./config/run_tests.sh"] + working_dir: /rosetta diff --git a/contrib/rosetta/node/Dockerfile b/contrib/rosetta/node/Dockerfile new file mode 100644 index 0000000000..0887f522f6 --- /dev/null +++ b/contrib/rosetta/node/Dockerfile @@ -0,0 +1,32 @@ +FROM golang:1.15-alpine as build + +RUN apk add --no-cache tar + +# prepare node data +WORKDIR /node +COPY ./contrib/rosetta/node/data.tar.gz data.tar.gz +RUN tar -zxvf data.tar.gz -C . + +# build simd +WORKDIR /simd +COPY . ./ +RUN go build -o simd ./simapp/simd/ + +FROM alpine +RUN apk add gcc libc-dev python3 --no-cache + +ENV PATH=$PATH:/bin + +COPY --from=build /simd/simd /bin/simd + +WORKDIR /rosetta +COPY ./contrib/rosetta/configuration ./ +RUN chmod +x run_tests.sh +RUN chmod +x send_funds.sh +RUN chmod +x faucet.py + +COPY --from=build /node/root /root/ +WORKDIR /root/.simapp + +RUN chmod -R 0777 ./ + diff --git a/contrib/rosetta/node/data.tar.gz b/contrib/rosetta/node/data.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..a7bed60579ea098baa390af3a29e05b08d16dcc8 GIT binary patch literal 34626 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