From 6211be616da8786872681b815b32e182f1386b1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Toledano?= Date: Mon, 7 Oct 2024 15:16:20 +0200 Subject: [PATCH 1/6] docs(x/distribution): updates (#22148) --- x/distribution/README.md | 6 ++---- x/distribution/keeper/delegation.go | 5 +++-- x/distribution/keeper/hooks.go | 14 +++++++------- x/distribution/keeper/keeper.go | 4 ++-- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/x/distribution/README.md b/x/distribution/README.md index d49b309f50fe..8cd03dc5e459 100644 --- a/x/distribution/README.md +++ b/x/distribution/README.md @@ -74,7 +74,7 @@ which is considered computationally expensive. In conclusion, we can only have Atom commission and unbonded atoms provisions or bonded atom provisions with no Atom commission, and we elect to -implement the former. Stakeholders wishing to rebond their provisions may elect +implement the former. Stakeholders wishing to rebond their provisions, may elect to set up a script to periodically withdraw and rebond rewards. ## Contents @@ -99,7 +99,7 @@ In Proof of Stake (PoS) blockchains, rewards gained from transaction fees are pa Rewards are calculated per period. The period is updated each time a validator's delegation changes, for example, when the validator receives a new delegation. The rewards for a single validator can then be calculated by taking the total rewards for the period before the delegation started, minus the current total rewards. -To learn more, see the [F1 Fee Distribution paper](https://github.com/cosmos/cosmos-sdk/tree/main/docs/spec/fee_distribution/f1_fee_distr.pdf). +To learn more, see the [F1 Fee Distribution paper](https://github.com/cosmos/cosmos-sdk/blob/release/v0.52.x/docs/spec/fee_distribution/f1_fee_distr.pdf). The commission to the validator is paid when the validator is removed or when the validator requests a withdrawal. The commission is calculated and incremented at every `BeginBlock` operation to update accumulated fee amounts. @@ -438,8 +438,6 @@ The distribution module emits the following events: | Type | Attribute Key | Attribute Value | |-----------------|---------------|--------------------| -| proposer_reward | validator | {validatorAddress} | -| proposer_reward | reward | {proposerReward} | | commission | amount | {commissionAmount} | | commission | validator | {validatorAddress} | | rewards | amount | {rewardAmount} | diff --git a/x/distribution/keeper/delegation.go b/x/distribution/keeper/delegation.go index 7bf2091edb89..1959d3a109f2 100644 --- a/x/distribution/keeper/delegation.go +++ b/x/distribution/keeper/delegation.go @@ -46,7 +46,7 @@ func (k Keeper) initializeDelegation(ctx context.Context, val sdk.ValAddress, de return k.DelegatorStartingInfo.Set(ctx, collections.Join(val, del), types.NewDelegatorStartingInfo(previousPeriod, stake, uint64(headerinfo.Height))) } -// calculate the rewards accrued by a delegation between two periods +// calculateDelegationRewardsBetween calculates the rewards accrued by a delegation between two periods func (k Keeper) calculateDelegationRewardsBetween(ctx context.Context, val sdk.ValidatorI, startingPeriod, endingPeriod uint64, stake math.LegacyDec, ) (sdk.DecCoins, error) { @@ -85,7 +85,7 @@ func (k Keeper) calculateDelegationRewardsBetween(ctx context.Context, val sdk.V return rewards, nil } -// calculate the total rewards accrued by a delegation +// CalculateDelegationRewards calculate the total rewards accrued by a delegation func (k Keeper) CalculateDelegationRewards(ctx context.Context, val sdk.ValidatorI, del sdk.DelegationI, endingPeriod uint64) (rewards sdk.DecCoins, err error) { addrCodec := k.authKeeper.AddressCodec() delAddr, err := addrCodec.StringToBytes(del.GetDelegatorAddr()) @@ -200,6 +200,7 @@ func (k Keeper) CalculateDelegationRewards(ctx context.Context, val sdk.Validato return rewards, nil } +// withdrawDelegationRewards withdraws the rewards accrued by a delegation. func (k Keeper) withdrawDelegationRewards(ctx context.Context, val sdk.ValidatorI, del sdk.DelegationI) (sdk.Coins, error) { addrCodec := k.authKeeper.AddressCodec() delAddr, err := addrCodec.StringToBytes(del.GetDelegatorAddr()) diff --git a/x/distribution/keeper/hooks.go b/x/distribution/keeper/hooks.go index 46605690c26f..47a21d32dec4 100644 --- a/x/distribution/keeper/hooks.go +++ b/x/distribution/keeper/hooks.go @@ -20,12 +20,12 @@ type Hooks struct { var _ stakingtypes.StakingHooks = Hooks{} -// Create new distribution hooks +// Hooks creates new distribution hooks func (k Keeper) Hooks() Hooks { return Hooks{k} } -// initialize validator distribution record +// AfterValidatorCreated initialize validator distribution record func (h Hooks) AfterValidatorCreated(ctx context.Context, valAddr sdk.ValAddress) error { val, err := h.k.stakingKeeper.Validator(ctx, valAddr) if err != nil { @@ -129,8 +129,8 @@ func (h Hooks) AfterValidatorRemoved(ctx context.Context, _ sdk.ConsAddress, val return nil } -// increment period -func (h Hooks) BeforeDelegationCreated(ctx context.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) error { +// BeforeDelegationCreated increment period +func (h Hooks) BeforeDelegationCreated(ctx context.Context, _ sdk.AccAddress, valAddr sdk.ValAddress) error { val, err := h.k.stakingKeeper.Validator(ctx, valAddr) if err != nil { return err @@ -140,7 +140,7 @@ func (h Hooks) BeforeDelegationCreated(ctx context.Context, delAddr sdk.AccAddre return err } -// withdraw delegation rewards (which also increments period) +// BeforeDelegationSharesModified withdraws delegation rewards (which also increments period) func (h Hooks) BeforeDelegationSharesModified(ctx context.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) error { val, err := h.k.stakingKeeper.Validator(ctx, valAddr) if err != nil { @@ -159,12 +159,12 @@ func (h Hooks) BeforeDelegationSharesModified(ctx context.Context, delAddr sdk.A return nil } -// create new delegation period record +// AfterDelegationModified create new delegation period record func (h Hooks) AfterDelegationModified(ctx context.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) error { return h.k.initializeDelegation(ctx, valAddr, delAddr) } -// record the slash event +// BeforeValidatorSlashed record the slash event func (h Hooks) BeforeValidatorSlashed(ctx context.Context, valAddr sdk.ValAddress, fraction sdkmath.LegacyDec) error { return h.k.updateValidatorSlashFraction(ctx, valAddr, fraction) } diff --git a/x/distribution/keeper/keeper.go b/x/distribution/keeper/keeper.go index 0ded047e93d6..6ee76320f982 100644 --- a/x/distribution/keeper/keeper.go +++ b/x/distribution/keeper/keeper.go @@ -178,7 +178,7 @@ func (k Keeper) SetWithdrawAddr(ctx context.Context, delegatorAddr, withdrawAddr return k.DelegatorsWithdrawAddress.Set(ctx, delegatorAddr, withdrawAddr) } -// withdraw rewards from a delegation +// WithdrawDelegationRewards withdraw rewards from a delegation func (k Keeper) WithdrawDelegationRewards(ctx context.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sdk.Coins, error) { val, err := k.stakingKeeper.Validator(ctx, valAddr) if err != nil { @@ -212,7 +212,7 @@ func (k Keeper) WithdrawDelegationRewards(ctx context.Context, delAddr sdk.AccAd return rewards, nil } -// withdraw validator commission +// WithdrawValidatorCommission withdraw validator commission func (k Keeper) WithdrawValidatorCommission(ctx context.Context, valAddr sdk.ValAddress) (sdk.Coins, error) { // fetch validator accumulated commission accumCommission, err := k.ValidatorsAccumulatedCommission.Get(ctx, valAddr) From b33484a31eb947ce78dd9ad0b7f0063dfc7a3b5e Mon Sep 17 00:00:00 2001 From: Alexander Peters Date: Mon, 7 Oct 2024 15:18:52 +0200 Subject: [PATCH 2/6] test(systemtests): fix failing tests (#22145) --- tests/systemtests/cometbft_client_test.go | 45 +++++++++++++---------- tests/systemtests/rest_cli.go | 4 +- tests/systemtests/system.go | 5 +++ tests/systemtests/testnet_init.go | 14 +++++++ tests/systemtests/upgrade_test.go | 9 +++-- 5 files changed, 52 insertions(+), 25 deletions(-) diff --git a/tests/systemtests/cometbft_client_test.go b/tests/systemtests/cometbft_client_test.go index 0c9fd62bb5bd..cee97baa0c61 100644 --- a/tests/systemtests/cometbft_client_test.go +++ b/tests/systemtests/cometbft_client_test.go @@ -5,6 +5,7 @@ package systemtests import ( "context" "fmt" + "net/http" "net/url" "testing" "time" @@ -82,6 +83,10 @@ func TestQueryBlockByHeight(t *testing.T) { } func TestQueryLatestValidatorSet(t *testing.T) { + if sut.NodesCount() < 2 { + t.Skip("not enough nodes") + return + } baseurl := fmt.Sprintf("http://localhost:%d", apiPortStart) sut.ResetChain(t) sut.StartChain(t) @@ -103,7 +108,7 @@ func TestQueryLatestValidatorSet(t *testing.T) { assert.NoError(t, err) assert.Equal(t, len(res.Validators), 2) - restRes := GetRequest(t, mustV(url.JoinPath(baseurl, "/cosmos/base/tendermint/v1beta1/validatorsets/latest?pagination.offset=0&pagination.limit=2"))) + restRes := GetRequest(t, fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/validatorsets/latest?pagination.offset=%d&pagination.limit=%d", baseurl, 0, 2)) assert.Equal(t, len(gjson.GetBytes(restRes, "validators").Array()), 2) } @@ -161,13 +166,14 @@ func TestLatestValidatorSet_GRPCGateway(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - rsp := GetRequest(t, mustV(url.JoinPath(baseurl, tc.url))) if tc.expErr { + rsp := GetRequestWithHeaders(t, baseurl+tc.url, nil, http.StatusBadRequest) errMsg := gjson.GetBytes(rsp, "message").String() assert.Contains(t, errMsg, tc.expErrMsg) - } else { - assert.Equal(t, len(vals), int(gjson.GetBytes(rsp, "pagination.total").Int())) + return } + rsp := GetRequest(t, baseurl+tc.url) + assert.Equal(t, len(vals), int(gjson.GetBytes(rsp, "pagination.total").Int())) }) } } @@ -204,41 +210,40 @@ func TestValidatorSetByHeight(t *testing.T) { } } -func TestValidatorSetByHeight_GRPCGateway(t *testing.T) { +func TestValidatorSetByHeight_GRPCRestGateway(t *testing.T) { sut.ResetChain(t) sut.StartChain(t) vals := sut.RPCClient(t).Validators() - baseurl := fmt.Sprintf("http://localhost:%d", apiPortStart) - + baseurl := sut.APIAddress() block := sut.AwaitNextBlock(t, time.Second*3) testCases := []struct { - name string - url string - expErr bool - expErrMsg string + name string + url string + expErr bool + expErrMsg string + expHttpCode int }{ - {"invalid height", fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/validatorsets/%d", baseurl, -1), true, "height must be greater than 0"}, - {"no pagination", fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/validatorsets/%d", baseurl, block), false, ""}, - {"pagination invalid fields", fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/validatorsets/%d?pagination.offset=-1&pagination.limit=-2", baseurl, block), true, "strconv.ParseUint"}, - {"with pagination", fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/validatorsets/%d?pagination.limit=2", baseurl, 1), false, ""}, + {"invalid height", fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/validatorsets/%d", baseurl, -1), true, "height must be greater than 0", http.StatusInternalServerError}, + {"no pagination", fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/validatorsets/%d", baseurl, block), false, "", http.StatusOK}, + {"pagination invalid fields", fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/validatorsets/%d?pagination.offset=-1&pagination.limit=-2", baseurl, block), true, "strconv.ParseUint", http.StatusBadRequest}, + {"with pagination", fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/validatorsets/%d?pagination.limit=2", baseurl, 1), false, "", http.StatusOK}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - rsp := GetRequest(t, tc.url) + rsp := GetRequestWithHeaders(t, tc.url, nil, tc.expHttpCode) if tc.expErr { errMsg := gjson.GetBytes(rsp, "message").String() assert.Contains(t, errMsg, tc.expErrMsg) - } else { - assert.Equal(t, len(vals), int(gjson.GetBytes(rsp, "pagination.total").Int())) + return } + assert.Equal(t, len(vals), int(gjson.GetBytes(rsp, "pagination.total").Int())) }) } } func TestABCIQuery(t *testing.T) { - sut.ResetChain(t) sut.StartChain(t) qc := cmtservice.NewServiceClient(sut.RPCClient(t)) @@ -312,7 +317,7 @@ func TestABCIQuery(t *testing.T) { } else { assert.NoError(t, err) assert.NotNil(t, res) - assert.Equal(t, res.Code, tc.expectedCode) + assert.Equal(t, tc.expectedCode, res.Code) } if tc.validQuery { diff --git a/tests/systemtests/rest_cli.go b/tests/systemtests/rest_cli.go index a7609949387f..3ad1e30dda68 100644 --- a/tests/systemtests/rest_cli.go +++ b/tests/systemtests/rest_cli.go @@ -79,9 +79,9 @@ func GetRequestWithHeaders(t *testing.T, url string, headers map[string]string, defer func() { _ = res.Body.Close() }() - require.Equal(t, expCode, res.StatusCode, "status code should be %d, got: %d", expCode, res.StatusCode) - body, err := io.ReadAll(res.Body) require.NoError(t, err) + require.Equal(t, expCode, res.StatusCode, "status code should be %d, got: %d, %s", expCode, res.StatusCode, body) + return body } diff --git a/tests/systemtests/system.go b/tests/systemtests/system.go index 346f39e761b4..f42bf96a79cd 100644 --- a/tests/systemtests/system.go +++ b/tests/systemtests/system.go @@ -766,6 +766,11 @@ func (s *SystemUnderTest) CurrentHeight() int64 { return s.currentHeight.Load() } +// NodesCount returns the number of node instances used +func (s *SystemUnderTest) NodesCount() int { + return s.nodesCount +} + type Node struct { ID string IP string diff --git a/tests/systemtests/testnet_init.go b/tests/systemtests/testnet_init.go index bdb2a85d25da..9033c283e43a 100644 --- a/tests/systemtests/testnet_init.go +++ b/tests/systemtests/testnet_init.go @@ -51,6 +51,20 @@ func NewSingleHostTestnetCmdInitializer( } } +// InitializerWithBinary creates new SingleHostTestnetCmdInitializer from sut with given binary +func InitializerWithBinary(binary string, sut *SystemUnderTest) TestnetInitializer { + return NewSingleHostTestnetCmdInitializer( + binary, + WorkDir, + sut.chainID, + sut.outputDir, + sut.initialNodesCount, + sut.minGasPrice, + sut.CommitTimeout(), + sut.Log, + ) +} + func (s SingleHostTestnetCmdInitializer) Initialize() { args := []string{ "testnet", diff --git a/tests/systemtests/upgrade_test.go b/tests/systemtests/upgrade_test.go index bd719d1e7962..c7de9e6dfa99 100644 --- a/tests/systemtests/upgrade_test.go +++ b/tests/systemtests/upgrade_test.go @@ -17,25 +17,28 @@ import ( ) func TestChainUpgrade(t *testing.T) { + // err> panic: failed to load latest version: failed to load store: initial version set to 22, but found earlier version 1 [cosmossdk.io/store@v1.1.1/rootmulti/store.go:256] + t.Skip("Skipped until any v052 artifact is available AND main branch handles the store upgrade proper") + // Scenario: // start a legacy chain with some state // when a chain upgrade proposal is executed // then the chain upgrades successfully sut.StopChain() - legacyBinary := FetchExecutable(t, "v0.50") + legacyBinary := FetchExecutable(t, "v0.52") t.Logf("+++ legacy binary: %s\n", legacyBinary) currentBranchBinary := sut.execBinary currentInitializer := sut.testnetInitializer sut.SetExecBinary(legacyBinary) - sut.SetTestnetInitializer(NewModifyConfigYamlInitializer(legacyBinary, sut)) + sut.SetTestnetInitializer(InitializerWithBinary(legacyBinary, sut)) sut.SetupChain() votingPeriod := 5 * time.Second // enough time to vote sut.ModifyGenesisJSON(t, SetGovVotingPeriod(t, votingPeriod)) const ( upgradeHeight int64 = 22 - upgradeName = "v050-to-v051" + upgradeName = "v052-to-v054" // must match UpgradeName in simapp/upgrades.go ) sut.StartChain(t, fmt.Sprintf("--halt-height=%d", upgradeHeight+1)) From 326545d442c80490e11a93958a76da8f9f4632b3 Mon Sep 17 00:00:00 2001 From: Artur Troian Date: Mon, 7 Oct 2024 09:27:55 -0400 Subject: [PATCH 3/6] feat(tools/cosmovisor): create current symlink as relative (#21891) Signed-off-by: Artur Troian --- tools/cosmovisor/CHANGELOG.md | 1 + tools/cosmovisor/args.go | 39 ++-- tools/cosmovisor/args_test.go | 58 +++--- tools/cosmovisor/cmd/cosmovisor/init.go | 8 +- tools/cosmovisor/cmd/cosmovisor/init_test.go | 109 ++++++---- tools/cosmovisor/cmd/cosmovisor/root.go | 2 +- tools/cosmovisor/cmd/cosmovisor/run.go | 7 + tools/cosmovisor/process_test.go | 205 ++++++++++++++----- tools/cosmovisor/upgrade_test.go | 137 +++++++++---- 9 files changed, 391 insertions(+), 175 deletions(-) diff --git a/tools/cosmovisor/CHANGELOG.md b/tools/cosmovisor/CHANGELOG.md index 3db3e4ea9954..1285007e6d84 100644 --- a/tools/cosmovisor/CHANGELOG.md +++ b/tools/cosmovisor/CHANGELOG.md @@ -42,6 +42,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements +* [#21891](https://github.com/cosmos/cosmos-sdk/pull/21891) create `current` symlink as relative * [#21462](https://github.com/cosmos/cosmos-sdk/pull/21462) Pass `stdin` to binary. ### Features diff --git a/tools/cosmovisor/args.go b/tools/cosmovisor/args.go index 6c6b5b7b0572..577a16f251fc 100644 --- a/tools/cosmovisor/args.go +++ b/tools/cosmovisor/args.go @@ -113,14 +113,19 @@ func (cfg *Config) UpgradeInfoFilePath() string { // SymLinkToGenesis creates a symbolic link from "./current" to the genesis directory. func (cfg *Config) SymLinkToGenesis() (string, error) { - genesis := filepath.Join(cfg.Root(), genesisDir) - link := filepath.Join(cfg.Root(), currentLink) + // workdir is set to cosmovisor directory so relative + // symlinks are getting resolved correctly + if err := os.Symlink(genesisDir, currentLink); err != nil { + return "", err + } - if err := os.Symlink(genesis, link); err != nil { + res, err := filepath.EvalSymlinks(cfg.GenesisBin()) + if err != nil { return "", err } + // and return the genesis binary - return cfg.GenesisBin(), nil + return res, nil } // WaitRestartDelay will block and wait until the RestartDelay has elapsed. @@ -134,27 +139,24 @@ func (cfg *Config) WaitRestartDelay() { // This will resolve the symlink to the underlying directory to make it easier to debug func (cfg *Config) CurrentBin() (string, error) { cur := filepath.Join(cfg.Root(), currentLink) + // if nothing here, fallback to genesis - info, err := os.Lstat(cur) - if err != nil { - // Create symlink to the genesis - return cfg.SymLinkToGenesis() - } // if it is there, ensure it is a symlink - if info.Mode()&os.ModeSymlink == 0 { + info, err := os.Lstat(cur) + if err != nil || (info.Mode()&os.ModeSymlink == 0) { // Create symlink to the genesis return cfg.SymLinkToGenesis() } - // resolve it - dest, err := os.Readlink(cur) + res, err := filepath.EvalSymlinks(cur) if err != nil { // Create symlink to the genesis return cfg.SymLinkToGenesis() } // and return the binary - binpath := filepath.Join(dest, "bin", cfg.Name) + binpath := filepath.Join(res, "bin", cfg.Name) + return binpath, nil } @@ -385,24 +387,23 @@ func (cfg *Config) SetCurrentUpgrade(u upgradetypes.Plan) (rerr error) { } // set a symbolic link - link := filepath.Join(cfg.Root(), currentLink) safeName := url.PathEscape(u.Name) - upgrade := filepath.Join(cfg.Root(), upgradesDir, safeName) + upgrade := filepath.Join(upgradesDir, safeName) // remove link if it exists - if _, err := os.Stat(link); err == nil { - if err := os.Remove(link); err != nil { + if _, err := os.Stat(currentLink); err == nil { + if err := os.Remove(currentLink); err != nil { return fmt.Errorf("failed to remove existing link: %w", err) } } // point to the new directory - if err := os.Symlink(upgrade, link); err != nil { + if err := os.Symlink(upgrade, currentLink); err != nil { return fmt.Errorf("creating current symlink: %w", err) } cfg.currentUpgrade = u - f, err := os.Create(filepath.Join(upgrade, upgradetypes.UpgradeInfoFilename)) + f, err := os.Create(filepath.Join(cfg.Root(), upgrade, upgradetypes.UpgradeInfoFilename)) if err != nil { return err } diff --git a/tools/cosmovisor/args_test.go b/tools/cosmovisor/args_test.go index f121a3989d9f..12c28b8e7952 100644 --- a/tools/cosmovisor/args_test.go +++ b/tools/cosmovisor/args_test.go @@ -454,6 +454,7 @@ var newConfig = func( skipBackup bool, dataBackupPath string, interval, preupgradeMaxRetries int, + grpcAddress string, disableLogs, colorLogs bool, timeFormatLogs string, customPreUpgrade string, @@ -470,6 +471,7 @@ var newConfig = func( PollInterval: time.Millisecond * time.Duration(interval), UnsafeSkipBackup: skipBackup, DataBackupPath: dataBackupPath, + GRPCAddress: grpcAddress, PreUpgradeMaxRetries: preupgradeMaxRetries, DisableLogs: disableLogs, ColorLogs: colorLogs, @@ -518,7 +520,7 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "all good", envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "true", "10s"}, - expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen, "preupgrade.sh", true, 10000000000), + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", true, 10000000000), expectedErrCount: 0, }, { @@ -538,25 +540,25 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "download bin not set", envVals: cosmovisorEnv{absPath, "testname", "", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen, "", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "", false, 0), expectedErrCount: 0, }, { name: "download bin true", envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { name: "download bin false", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { name: "download ensure checksum true", envVals: cosmovisorEnv{absPath, "testname", "true", "false", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", true, false, false, 600, true, absPath, 303, 1, false, true, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", true, false, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { @@ -568,19 +570,19 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "restart upgrade not set", envVals: cosmovisorEnv{absPath, "testname", "true", "true", "", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", true, true, true, 600, true, absPath, 303, 1, false, true, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", true, true, true, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { name: "restart upgrade true", envVals: cosmovisorEnv{absPath, "testname", "true", "true", "true", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", true, true, true, 600, true, absPath, 303, 1, false, true, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", true, true, true, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { name: "restart upgrade true", envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { @@ -592,19 +594,19 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "skip unsafe backups not set", envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", true, true, false, 600, false, absPath, 303, 1, false, true, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, false, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { name: "skip unsafe backups true", envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { name: "skip unsafe backups false", envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "false", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", true, true, false, 600, false, absPath, 303, 1, false, true, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, false, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { @@ -622,7 +624,7 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "poll interval not set", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "", "1", "false", "false", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 300, 1, false, false, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 300, 1, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { @@ -634,7 +636,7 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "poll interval 1s", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "1s", "1", "false", "false", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 1000, 1, false, false, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 1000, 1, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { @@ -658,7 +660,7 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "restart delay not set", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "", "false", "", "303ms", "1", "false", "false", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 0, false, absPath, 303, 1, false, false, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 0, false, absPath, 303, 1, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { @@ -670,7 +672,7 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "restart delay 1s", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "1s", "false", "", "303ms", "1", "false", "false", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 1000, false, absPath, 303, 1, false, false, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 1000, false, absPath, 303, 1, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { @@ -688,19 +690,19 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "prepupgrade max retries 0", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "0", "false", "false", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, false, false, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { name: "prepupgrade max retries not set", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "false", "false", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, false, false, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { name: "prepupgrade max retries 5", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "5", "false", "false", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 5, false, false, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 5, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { @@ -712,7 +714,7 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "disable logs good", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "false", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, true, false, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", true, false, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { @@ -724,19 +726,19 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "disable logs color good", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "false", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, true, false, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", true, false, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { name: "disable logs timestamp", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "false", "", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, true, false, "", "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", true, false, "", "preupgrade.sh", false, 0), expectedErrCount: 0, }, { name: "enable rf3339 logs timestamp", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "true", "rfc3339", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, true, true, time.RFC3339, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", true, true, time.RFC3339, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { @@ -748,7 +750,7 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "disable recase good", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "true", "rfc3339", "preupgrade.sh", "true", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, true, true, time.RFC3339, "preupgrade.sh", true, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", true, true, time.RFC3339, "preupgrade.sh", true, 0), expectedErrCount: 0, }, { @@ -759,7 +761,7 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "shutdown grace good", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "true", "rfc3339", "preupgrade.sh", "true", "15s"}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, true, true, time.RFC3339, "preupgrade.sh", true, 15000000000), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", true, true, time.RFC3339, "preupgrade.sh", true, 15000000000), expectedErrCount: 0, }, } @@ -794,7 +796,7 @@ func (s *argsTestSuite) setUpDir() string { func (s *argsTestSuite) setupConfig(home string) string { s.T().Helper() - cfg := newConfig(home, "test", true, true, true, 406, false, home, 8, 0, false, true, "kitchen", "", true, 10000000000) + cfg := newConfig(home, "test", true, true, true, 406, false, home, 8, 0, "localhost:9090", false, true, "kitchen", "", true, 10000000000) path := filepath.Join(home, rootName, "config.toml") f, err := os.Create(path) s.Require().NoError(err) @@ -824,7 +826,7 @@ func (s *argsTestSuite) TestConfigFromFile() { { name: "valid config", expectedCfg: func() *Config { - return newConfig(home, "test", true, true, true, 406, false, home, 8, 0, false, true, time.Kitchen, "", true, 10000000000) + return newConfig(home, "test", true, true, true, 406, false, home, 8, 0, "localhost:9090", false, true, time.Kitchen, "", true, 10000000000) }, filePath: cfgFilePath, expectedError: "", @@ -839,13 +841,13 @@ func (s *argsTestSuite) TestConfigFromFile() { os.Setenv(EnvName, "env-name") }, expectedCfg: func() *Config { - return newConfig(home, "env-name", true, true, true, 406, false, home, 8, 0, false, true, time.Kitchen, "", true, 10000000000) + return newConfig(home, "env-name", true, true, true, 406, false, home, 8, 0, "localhost:9090", false, true, time.Kitchen, "", true, 10000000000) }, }, { name: "empty config file path will load config from ENV variables", expectedCfg: func() *Config { - return newConfig(home, "test", true, true, true, 406, false, home, 8, 0, false, true, time.Kitchen, "", true, 10000000000) + return newConfig(home, "test", true, true, true, 406, false, home, 8, 0, "localhost:9090", false, true, time.Kitchen, "", true, 10000000000) }, filePath: "", expectedError: "", diff --git a/tools/cosmovisor/cmd/cosmovisor/init.go b/tools/cosmovisor/cmd/cosmovisor/init.go index 5f78e2845c02..90a35ca286fa 100644 --- a/tools/cosmovisor/cmd/cosmovisor/init.go +++ b/tools/cosmovisor/cmd/cosmovisor/init.go @@ -14,7 +14,7 @@ import ( "cosmossdk.io/x/upgrade/plan" ) -func NewIntCmd() *cobra.Command { +func NewInitCmd() *cobra.Command { initCmd := &cobra.Command{ Use: "init ", Short: "Initialize a cosmovisor daemon home directory.", @@ -93,6 +93,12 @@ func InitializeCosmovisor(logger log.Logger, args []string) error { return err } + // set current working directory to $DAEMON_NAME/cosmosvisor + // to allow current symlink to be relative + if err = os.Chdir(cfg.Root()); err != nil { + return fmt.Errorf("failed to change directory to %s: %w", cfg.Root(), err) + } + logger.Info("checking on the current symlink and creating it if needed") cur, curErr := cfg.CurrentBin() if curErr != nil { diff --git a/tools/cosmovisor/cmd/cosmovisor/init_test.go b/tools/cosmovisor/cmd/cosmovisor/init_test.go index 97a082db729b..35a73348cc39 100644 --- a/tools/cosmovisor/cmd/cosmovisor/init_test.go +++ b/tools/cosmovisor/cmd/cosmovisor/init_test.go @@ -138,8 +138,8 @@ func (s *InitTestSuite) readStdInpFromFile(data []byte) { } var ( - _ io.Reader = BufferedPipe{} - _ io.Writer = BufferedPipe{} + _ io.Reader = &BufferedPipe{} + _ io.Writer = &BufferedPipe{} ) // BufferedPipe contains a connected read/write pair of files (a pipe), @@ -167,8 +167,8 @@ type BufferedPipe struct { // NewBufferedPipe creates a new BufferedPipe with the given name. // Files must be closed once you are done with them (e.g. with .Close()). // Once ready, buffering must be started using .Start(). See also StartNewBufferedPipe. -func NewBufferedPipe(name string, replicateTo ...io.Writer) (BufferedPipe, error) { - p := BufferedPipe{Name: name} +func NewBufferedPipe(name string, replicateTo ...io.Writer) (*BufferedPipe, error) { + p := &BufferedPipe{Name: name} p.Reader, p.Writer, p.Error = os.Pipe() if p.Error != nil { return p, p.Error @@ -184,7 +184,7 @@ func NewBufferedPipe(name string, replicateTo ...io.Writer) (BufferedPipe, error // // p, _ := NewBufferedPipe(name, replicateTo...) // p.Start() -func StartNewBufferedPipe(name string, replicateTo ...io.Writer) (BufferedPipe, error) { +func StartNewBufferedPipe(name string, replicateTo ...io.Writer) (*BufferedPipe, error) { p, err := NewBufferedPipe(name, replicateTo...) if err != nil { return p, err @@ -214,6 +214,7 @@ func (p *BufferedPipe) Start() { if _, p.Error = io.Copy(&b, p.BufferReader); p.Error != nil { b.WriteString("buffer error: " + p.Error.Error()) } + p.buffer <- b.Bytes() }() p.started = true @@ -238,6 +239,7 @@ func (p *BufferedPipe) Collect() []byte { panic("buffered pipe " + p.Name + " has not been started: cannot collect") } _ = p.Writer.Close() + if p.buffer == nil { return []byte{} } @@ -247,12 +249,12 @@ func (p *BufferedPipe) Collect() []byte { } // Read implements the io.Reader interface on this BufferedPipe. -func (p BufferedPipe) Read(bz []byte) (n int, err error) { +func (p *BufferedPipe) Read(bz []byte) (n int, err error) { return p.Reader.Read(bz) } // Write implements the io.Writer interface on this BufferedPipe. -func (p BufferedPipe) Write(bz []byte) (n int, err error) { +func (p *BufferedPipe) Write(bz []byte) (n int, err error) { return p.Writer.Write(bz) } @@ -274,7 +276,7 @@ func (s *InitTestSuite) NewCapturingLogger() (*BufferedPipe, log.Logger) { bufferedStdOut, err := StartNewBufferedPipe("stdout", os.Stdout) s.Require().NoError(err, "creating stdout buffered pipe") logger := log.NewLogger(bufferedStdOut, log.ColorOption(false), log.TimeFormatOption(time.RFC3339Nano)).With(log.ModuleKey, cosmovisorDirName) - return &bufferedStdOut, logger + return bufferedStdOut, logger } // CreateHelloWorld creates a shell script that outputs HELLO WORLD. @@ -443,15 +445,13 @@ func (s *InitTestSuite) TestInitializeCosmovisorInvalidExisting() { rootDir := filepath.Join(env.Home, cosmovisorDirName) require.NoError(t, os.MkdirAll(rootDir, 0o755)) curLn := filepath.Join(rootDir, "current") - genDir := filepath.Join(rootDir, "genesis") require.NoError(t, copyFile(hwExe, curLn)) - expErr := fmt.Sprintf("symlink %s %s: file exists", genDir, curLn) s.setEnv(t, env) buffer, logger := s.NewCapturingLogger() logger.Info(fmt.Sprintf("Calling InitializeCosmovisor: %s", t.Name())) err := InitializeCosmovisor(logger, []string{hwExe}) - require.EqualError(t, err, expErr, "calling InitializeCosmovisor") + require.EqualError(t, err, "symlink genesis current: file exists", "calling InitializeCosmovisor") bufferBz := buffer.Collect() bufferStr := string(bufferBz) assert.Contains(t, bufferStr, "checking on the current symlink and creating it if needed") @@ -484,37 +484,43 @@ func (s *InitTestSuite) TestInitializeCosmovisorValid() { hwExe := s.CreateHelloWorld(0o755) s.T().Run("starting with blank slate", func(t *testing.T) { - testDir := s.T().TempDir() - env := &cosmovisorInitEnv{ - Home: filepath.Join(testDir, "home"), + env := s.prepareConfig(s.T(), cosmovisorInitEnv{ Name: "blank", - } + }) + curLn := filepath.Join(env.Home, cosmovisorDirName, "current") - genBinDir := filepath.Join(env.Home, cosmovisorDirName, "genesis", "bin") - genBinExe := filepath.Join(genBinDir, env.Name) + + s.setEnv(s.T(), env) + buffer, logger := s.NewCapturingLogger() + logger.Info(fmt.Sprintf("Calling InitializeCosmovisor: %s", t.Name())) + err := InitializeCosmovisor(logger, []string{hwNonExe}) + require.NoError(t, err, "calling InitializeCosmovisor") + + genDir := filepath.Join(env.Home, cosmovisorDirName, "genesis", "bin") + genBinExe := filepath.Join(genDir, env.Name) + + genBinDirEval, err := filepath.EvalSymlinks(genDir) + require.NoError(t, err) + + genBinEvalExe := filepath.Join(genBinDirEval, env.Name) + expInLog := []string{ "checking on the genesis/bin directory", - fmt.Sprintf("creating directory (and any parents): %q", genBinDir), + fmt.Sprintf("creating directory (and any parents): %q", genDir), "checking on the genesis/bin executable", fmt.Sprintf("copying executable into place: %q", genBinExe), fmt.Sprintf("making sure %q is executable", genBinExe), "checking on the current symlink and creating it if needed", - fmt.Sprintf("the current symlink points to: %q", genBinExe), + fmt.Sprintf("the current symlink points to: %q", genBinEvalExe), fmt.Sprintf("cosmovisor config.toml created at: %s", filepath.Join(env.Home, cosmovisorDirName, cfgFileWithExt)), } - s.setEnv(s.T(), env) - buffer, logger := s.NewCapturingLogger() - logger.Info(fmt.Sprintf("Calling InitializeCosmovisor: %s", t.Name())) - err := InitializeCosmovisor(logger, []string{hwNonExe}) - require.NoError(t, err, "calling InitializeCosmovisor") - - _, err = os.Stat(genBinDir) - assert.NoErrorf(t, err, "statting the genesis bin dir: %q", genBinDir) + _, err = os.Stat(genBinDirEval) + assert.NoErrorf(t, err, "statting the genesis bin dir: %q", genBinDirEval) _, err = os.Stat(curLn) assert.NoError(t, err, "statting the current link: %q", curLn) - exeInfo, exeErr := os.Stat(genBinExe) - if assert.NoError(t, exeErr, "statting the executable: %q", genBinExe) { + exeInfo, exeErr := os.Stat(genBinEvalExe) + if assert.NoError(t, exeErr, "statting the executable: %q", genBinEvalExe) { assert.True(t, exeInfo.Mode().IsRegular(), "executable is regular file") // Check if the world-executable bit is set. exePermMask := exeInfo.Mode().Perm() & 0o001 @@ -534,10 +540,18 @@ func (s *InitTestSuite) TestInitializeCosmovisorValid() { Name: "nocur", } rootDir := filepath.Join(env.Home, cosmovisorDirName) + genBinDir := filepath.Join(rootDir, "genesis", "bin") genBinDirExe := filepath.Join(genBinDir, env.Name) + require.NoError(t, os.MkdirAll(genBinDir, 0o755), "making genesis bin dir") require.NoError(t, copyFile(hwExe, genBinDirExe), "copying executable to genesis") + + genBinDirEval, err := filepath.EvalSymlinks(genBinDir) + require.NoError(t, err) + + genBinEvalExe := filepath.Join(genBinDirEval, env.Name) + upgradesDir := filepath.Join(rootDir, "upgrades") for i := 1; i <= 5; i++ { upgradeBinDir := filepath.Join(upgradesDir, fmt.Sprintf("upgrade-%02d", i), "bin") @@ -552,14 +566,14 @@ func (s *InitTestSuite) TestInitializeCosmovisorValid() { "checking on the genesis/bin executable", fmt.Sprintf("the %q file already exists", genBinDirExe), fmt.Sprintf("making sure %q is executable", genBinDirExe), - fmt.Sprintf("the current symlink points to: %q", genBinDirExe), + fmt.Sprintf("the current symlink points to: %q", genBinEvalExe), fmt.Sprintf("cosmovisor config.toml created at: %s", filepath.Join(env.Home, cosmovisorDirName, cfgFileWithExt)), } s.setEnv(t, env) buffer, logger := s.NewCapturingLogger() logger.Info(fmt.Sprintf("Calling InitializeCosmovisor: %s", t.Name())) - err := InitializeCosmovisor(logger, []string{hwExe}) + err = InitializeCosmovisor(logger, []string{hwExe}) require.NoError(t, err, "calling InitializeCosmovisor") bufferBz := buffer.Collect() bufferStr := string(bufferBz) @@ -579,21 +593,27 @@ func (s *InitTestSuite) TestInitializeCosmovisorValid() { genBinExe := filepath.Join(genBinDir, env.Name) require.NoError(t, os.MkdirAll(genBinDir, 0o755), "making genesis bin dir") + s.setEnv(t, env) + buffer, logger := s.NewCapturingLogger() + logger.Info(fmt.Sprintf("Calling InitializeCosmovisor: %s", t.Name())) + err := InitializeCosmovisor(logger, []string{hwExe}) + require.NoError(t, err, "calling InitializeCosmovisor") + + genBinDirEval, err := filepath.EvalSymlinks(genBinDir) + require.NoError(t, err) + + genBinEvalExe := filepath.Join(genBinDirEval, env.Name) + expInLog := []string{ "checking on the genesis/bin directory", fmt.Sprintf("the %q directory already exists", genBinDir), "checking on the genesis/bin executable", fmt.Sprintf("copying executable into place: %q", genBinExe), fmt.Sprintf("making sure %q is executable", genBinExe), - fmt.Sprintf("the current symlink points to: %q", genBinExe), + fmt.Sprintf("the current symlink points to: %q", genBinEvalExe), fmt.Sprintf("cosmovisor config.toml created at: %s", filepath.Join(env.Home, cosmovisorDirName, cfgFileWithExt)), } - s.setEnv(t, env) - buffer, logger := s.NewCapturingLogger() - logger.Info(fmt.Sprintf("Calling InitializeCosmovisor: %s", t.Name())) - err := InitializeCosmovisor(logger, []string{hwExe}) - require.NoError(t, err, "calling InitializeCosmovisor") bufferBz := buffer.Collect() bufferStr := string(bufferBz) for _, exp := range expInLog { @@ -693,7 +713,9 @@ func (s *InitTestSuite) TestInitializeCosmovisorWithOverrideCfg() { // read the config file cfgFile, err := os.Open(tc.cfg.DefaultCfgPath()) require.NoError(t, err) - defer cfgFile.Close() + defer func() { + _ = cfgFile.Close() + }() err = toml.NewDecoder(cfgFile).Decode(cfg) require.NoError(t, err) @@ -708,3 +730,14 @@ func (s *InitTestSuite) TestInitializeCosmovisorWithOverrideCfg() { }) } } + +func (s *InitTestSuite) prepareConfig(t *testing.T, config cosmovisorInitEnv) *cosmovisorInitEnv { + t.Helper() + + config.Home = s.T().TempDir() + + err := os.Chdir(config.Home) + require.NoError(t, err) + + return &config +} diff --git a/tools/cosmovisor/cmd/cosmovisor/root.go b/tools/cosmovisor/cmd/cosmovisor/root.go index 5cd31f8aea5b..de5e06d83e3e 100644 --- a/tools/cosmovisor/cmd/cosmovisor/root.go +++ b/tools/cosmovisor/cmd/cosmovisor/root.go @@ -14,7 +14,7 @@ func NewRootCmd() *cobra.Command { } rootCmd.AddCommand( - NewIntCmd(), + NewInitCmd(), runCmd, configCmd, NewVersionCmd(), diff --git a/tools/cosmovisor/cmd/cosmovisor/run.go b/tools/cosmovisor/cmd/cosmovisor/run.go index f835b59e5280..4da2ac24d846 100644 --- a/tools/cosmovisor/cmd/cosmovisor/run.go +++ b/tools/cosmovisor/cmd/cosmovisor/run.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "os" "strings" "github.com/spf13/cobra" @@ -39,6 +40,12 @@ func run(cfgPath string, args []string, options ...RunOption) error { opt(&runCfg) } + // set current working directory to $DAEMON_NAME/cosmosvisor + // to allow current symlink to be relative + if err = os.Chdir(cfg.Root()); err != nil { + return err + } + logger := cfg.Logger(runCfg.StdOut) launcher, err := cosmovisor.NewLauncher(logger, cfg) if err != nil { diff --git a/tools/cosmovisor/process_test.go b/tools/cosmovisor/process_test.go index 988bcf0a8074..26ef82900fe6 100644 --- a/tools/cosmovisor/process_test.go +++ b/tools/cosmovisor/process_test.go @@ -1,5 +1,4 @@ -//go:build linux -// +build linux +//go:build linux || darwin package cosmovisor_test @@ -20,12 +19,26 @@ import ( upgradetypes "cosmossdk.io/x/upgrade/types" ) +var workDir string + +func init() { + workDir, _ = os.Getwd() +} + // TestLaunchProcess will try running the script a few times and watch upgrades work properly // and args are passed through func TestLaunchProcess(t *testing.T) { // binaries from testdata/validate directory - home := copyTestData(t, "validate") - cfg := &cosmovisor.Config{Home: home, Name: "dummyd", PollInterval: 15, UnsafeSkipBackup: true} + cfg := prepareConfig( + t, + fmt.Sprintf("%s/%s", workDir, "testdata/validate"), + cosmovisor.Config{ + Name: "dummyd", + PollInterval: 15, + UnsafeSkipBackup: true, + }, + ) + logger := log.NewTestLogger(t).With(log.ModuleKey, "cosmosvisor") // should run the genesis binary and produce expected output @@ -33,7 +46,11 @@ func TestLaunchProcess(t *testing.T) { stdout, stderr := newBuffer(), newBuffer() currentBin, err := cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.GenesisBin(), currentBin) + + rPath, err := filepath.EvalSymlinks(cfg.GenesisBin()) + require.NoError(t, err) + + require.Equal(t, rPath, currentBin) launcher, err := cosmovisor.NewLauncher(logger, cfg) require.NoError(t, err) @@ -51,7 +68,10 @@ func TestLaunchProcess(t *testing.T) { currentBin, err = cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.UpgradeBin("chain2"), currentBin) + rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain2")) + require.NoError(t, err) + + require.Equal(t, rPath, currentBin) args = []string{"second", "run", "--verbose"} stdout.Reset() stderr.Reset() @@ -63,14 +83,26 @@ func TestLaunchProcess(t *testing.T) { require.Equal(t, "Chain 2 is live!\nArgs: second run --verbose\nFinished successfully\n", stdout.String()) // ended without other upgrade - require.Equal(t, cfg.UpgradeBin("chain2"), currentBin) + rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain2")) + require.NoError(t, err) + + require.Equal(t, rPath, currentBin) } // TestPlanDisableRecase will test upgrades without lower case plan names func TestPlanDisableRecase(t *testing.T) { // binaries from testdata/validate directory - home := copyTestData(t, "norecase") - cfg := &cosmovisor.Config{Home: home, Name: "dummyd", PollInterval: 20, UnsafeSkipBackup: true, DisableRecase: true} + cfg := prepareConfig( + t, + fmt.Sprintf("%s/%s", workDir, "testdata/norecase"), + cosmovisor.Config{ + Name: "dummyd", + PollInterval: 20, + UnsafeSkipBackup: true, + DisableRecase: true, + }, + ) + logger := log.NewTestLogger(t).With(log.ModuleKey, "cosmosvisor") // should run the genesis binary and produce expected output @@ -78,7 +110,11 @@ func TestPlanDisableRecase(t *testing.T) { stdout, stderr := newBuffer(), newBuffer() currentBin, err := cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.GenesisBin(), currentBin) + + rPath, err := filepath.EvalSymlinks(cfg.GenesisBin()) + require.NoError(t, err) + + require.Equal(t, rPath, currentBin) launcher, err := cosmovisor.NewLauncher(logger, cfg) require.NoError(t, err) @@ -96,7 +132,9 @@ func TestPlanDisableRecase(t *testing.T) { currentBin, err = cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.UpgradeBin("Chain2"), currentBin) + rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("Chain2")) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) args = []string{"second", "run", "--verbose"} stdout.Reset() stderr.Reset() @@ -108,13 +146,24 @@ func TestPlanDisableRecase(t *testing.T) { require.Equal(t, "Chain 2 is live!\nArgs: second run --verbose\nFinished successfully\n", stdout.String()) // ended without other upgrade - require.Equal(t, cfg.UpgradeBin("Chain2"), currentBin) + rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("Chain2")) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) } func TestLaunchProcessWithRestartDelay(t *testing.T) { // binaries from testdata/validate directory - home := copyTestData(t, "validate") - cfg := &cosmovisor.Config{Home: home, Name: "dummyd", RestartDelay: 5 * time.Second, PollInterval: 20, UnsafeSkipBackup: true} + cfg := prepareConfig( + t, + fmt.Sprintf("%s/%s", workDir, "testdata/validate"), + cosmovisor.Config{ + Name: "dummyd", + RestartDelay: 5 * time.Second, + PollInterval: 20, + UnsafeSkipBackup: true, + }, + ) + logger := log.NewTestLogger(t).With(log.ModuleKey, "cosmosvisor") // should run the genesis binary and produce expected output @@ -122,7 +171,10 @@ func TestLaunchProcessWithRestartDelay(t *testing.T) { stdout, stderr := newBuffer(), newBuffer() currentBin, err := cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.GenesisBin(), currentBin) + + rPath, err := filepath.EvalSymlinks(cfg.GenesisBin()) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) launcher, err := cosmovisor.NewLauncher(logger, cfg) require.NoError(t, err) @@ -144,8 +196,17 @@ func TestLaunchProcessWithRestartDelay(t *testing.T) { // TestPlanShutdownGrace will test upgrades without lower case plan names func TestPlanShutdownGrace(t *testing.T) { // binaries from testdata/validate directory - home := copyTestData(t, "dontdie") - cfg := &cosmovisor.Config{Home: home, Name: "dummyd", PollInterval: 15, UnsafeSkipBackup: true, ShutdownGrace: 2 * time.Second} + cfg := prepareConfig( + t, + fmt.Sprintf("%s/%s", workDir, "testdata/dontdie"), + cosmovisor.Config{ + Name: "dummyd", + PollInterval: 15, + UnsafeSkipBackup: true, + ShutdownGrace: 2 * time.Second, + }, + ) + logger := log.NewTestLogger(t).With(log.ModuleKey, "cosmosvisor") // should run the genesis binary and produce expected output @@ -153,7 +214,9 @@ func TestPlanShutdownGrace(t *testing.T) { stdout, stderr := newBuffer(), newBuffer() currentBin, err := cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.GenesisBin(), currentBin) + rPath, err := filepath.EvalSymlinks(cfg.GenesisBin()) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) launcher, err := cosmovisor.NewLauncher(logger, cfg) require.NoError(t, err) @@ -171,7 +234,9 @@ func TestPlanShutdownGrace(t *testing.T) { currentBin, err = cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.UpgradeBin("chain2"), currentBin) + rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain2")) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) args = []string{"second", "run", "--verbose"} stdout.Reset() stderr.Reset() @@ -183,7 +248,9 @@ func TestPlanShutdownGrace(t *testing.T) { require.Equal(t, "Chain 2 is live!\nArgs: second run --verbose\nFinished successfully\n", stdout.String()) // ended without other upgrade - require.Equal(t, cfg.UpgradeBin("chain2"), currentBin) + rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain2")) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) } // TestLaunchProcess will try running the script a few times and watch upgrades work properly @@ -193,15 +260,26 @@ func TestLaunchProcessWithDownloads(t *testing.T) { // genesis -> chain2-zip_bin // chain2-zip_bin -> ref_to_chain3-zip_dir.json = (json for the next download instructions) -> chain3-zip_dir // chain3-zip_dir - doesn't upgrade - home := copyTestData(t, "download") - cfg := &cosmovisor.Config{Home: home, Name: "autod", AllowDownloadBinaries: true, PollInterval: 100, UnsafeSkipBackup: true} + cfg := prepareConfig( + t, + fmt.Sprintf("%s/%s", workDir, "testdata/download"), + cosmovisor.Config{ + Name: "autod", + AllowDownloadBinaries: true, + PollInterval: 100, + UnsafeSkipBackup: true, + }, + ) + logger := log.NewTestLogger(t).With(log.ModuleKey, "cosmovisor") upgradeFilename := cfg.UpgradeInfoFilePath() // should run the genesis binary and produce expected output currentBin, err := cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.GenesisBin(), currentBin) + rPath, err := filepath.EvalSymlinks(cfg.GenesisBin()) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) launcher, err := cosmovisor.NewLauncher(logger, cfg) require.NoError(t, err) @@ -216,7 +294,10 @@ func TestLaunchProcessWithDownloads(t *testing.T) { require.Equal(t, "Genesis autod. Args: some args "+upgradeFilename+"\n"+`ERROR: UPGRADE "chain2" NEEDED at height: 49: zip_binary`+"\n", stdout.String()) currentBin, err = cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.UpgradeBin("chain2"), currentBin) + + rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain2")) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) // start chain2 stdout.Reset() @@ -231,7 +312,9 @@ func TestLaunchProcessWithDownloads(t *testing.T) { require.True(t, doUpgrade) currentBin, err = cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.UpgradeBin("chain3"), currentBin) + rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain3")) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) // run the last chain args = []string{"end", "--halt", upgradeFilename} @@ -246,7 +329,9 @@ func TestLaunchProcessWithDownloads(t *testing.T) { // and this doesn't upgrade currentBin, err = cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.UpgradeBin("chain3"), currentBin) + rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain3")) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) } // TestLaunchProcessWithDownloadsAndMissingPreupgrade will try running the script a few times and watch upgrades work properly @@ -256,22 +341,28 @@ func TestLaunchProcessWithDownloadsAndMissingPreupgrade(t *testing.T) { // genesis -> chain2-zip_bin // chain2-zip_bin -> ref_to_chain3-zip_dir.json = (json for the next download instructions) -> chain3-zip_dir // chain3-zip_dir - doesn't upgrade - home := copyTestData(t, "download") - cfg := &cosmovisor.Config{ - Home: home, - Name: "autod", - AllowDownloadBinaries: true, - PollInterval: 100, - UnsafeSkipBackup: true, - CustomPreUpgrade: "missing.sh", - } + cfg := prepareConfig( + t, + fmt.Sprintf("%s/%s", workDir, "testdata/download"), + cosmovisor.Config{ + Name: "autod", + AllowDownloadBinaries: true, + PollInterval: 100, + UnsafeSkipBackup: true, + CustomPreUpgrade: "missing.sh", + }, + ) + logger := log.NewTestLogger(t).With(log.ModuleKey, "cosmovisor") upgradeFilename := cfg.UpgradeInfoFilePath() // should run the genesis binary and produce expected output currentBin, err := cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.GenesisBin(), currentBin) + + rPath, err := filepath.EvalSymlinks(cfg.GenesisBin()) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) launcher, err := cosmovisor.NewLauncher(logger, cfg) require.NoError(t, err) @@ -292,15 +383,18 @@ func TestLaunchProcessWithDownloadsAndPreupgrade(t *testing.T) { // genesis -> chain2-zip_bin // chain2-zip_bin -> ref_to_chain3-zip_dir.json = (json for the next download instructions) -> chain3-zip_dir // chain3-zip_dir - doesn't upgrade - home := copyTestData(t, "download") - cfg := &cosmovisor.Config{ - Home: home, - Name: "autod", - AllowDownloadBinaries: true, - PollInterval: 100, - UnsafeSkipBackup: true, - CustomPreUpgrade: "preupgrade.sh", - } + cfg := prepareConfig( + t, + fmt.Sprintf("%s/%s", workDir, "testdata/download"), + cosmovisor.Config{ + Name: "autod", + AllowDownloadBinaries: true, + PollInterval: 100, + UnsafeSkipBackup: true, + CustomPreUpgrade: "preupgrade.sh", + }, + ) + buf := newBuffer() // inspect output using buf.String() logger := log.NewLogger(buf).With(log.ModuleKey, "cosmovisor") upgradeFilename := cfg.UpgradeInfoFilePath() @@ -308,7 +402,9 @@ func TestLaunchProcessWithDownloadsAndPreupgrade(t *testing.T) { // should run the genesis binary and produce expected output currentBin, err := cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.GenesisBin(), currentBin) + rPath, err := filepath.EvalSymlinks(cfg.GenesisBin()) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) launcher, err := cosmovisor.NewLauncher(logger, cfg) require.NoError(t, err) @@ -323,10 +419,13 @@ func TestLaunchProcessWithDownloadsAndPreupgrade(t *testing.T) { require.Equal(t, "Genesis autod. Args: some args "+upgradeFilename+"\n"+`ERROR: UPGRADE "chain2" NEEDED at height: 49: zip_binary`+"\n", stdout.String()) currentBin, err = cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.UpgradeBin("chain2"), currentBin) + + rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain2")) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) // should have preupgrade.sh results - require.FileExists(t, filepath.Join(home, "upgrade_name_chain2_height_49")) + require.FileExists(t, filepath.Join(cfg.Home, "upgrade_name_chain2_height_49")) // start chain2 stdout.Reset() @@ -341,10 +440,12 @@ func TestLaunchProcessWithDownloadsAndPreupgrade(t *testing.T) { require.True(t, doUpgrade) currentBin, err = cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.UpgradeBin("chain3"), currentBin) + rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain3")) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) // should have preupgrade.sh results - require.FileExists(t, filepath.Join(home, "upgrade_name_chain3_height_936")) + require.FileExists(t, filepath.Join(cfg.Home, "upgrade_name_chain3_height_936")) // run the last chain args = []string{"end", "--halt", upgradeFilename} @@ -359,7 +460,9 @@ func TestLaunchProcessWithDownloadsAndPreupgrade(t *testing.T) { // and this doesn't upgrade currentBin, err = cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.UpgradeBin("chain3"), currentBin) + rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain3")) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) } // TestSkipUpgrade tests heights that are identified to be skipped and return if upgrade height matches the skip heights diff --git a/tools/cosmovisor/upgrade_test.go b/tools/cosmovisor/upgrade_test.go index 5c8d867e2ca6..be79a0170f2e 100644 --- a/tools/cosmovisor/upgrade_test.go +++ b/tools/cosmovisor/upgrade_test.go @@ -1,5 +1,4 @@ -//go:build linux -// +build linux +//go:build darwin || linux package cosmovisor_test @@ -7,6 +6,7 @@ import ( "fmt" "os" "path/filepath" + "slices" "strings" "testing" @@ -28,13 +28,21 @@ func TestUpgradeTestSuite(t *testing.T) { } func (s *upgradeTestSuite) TestCurrentBin() { - home := copyTestData(s.T(), "validate") - cfg := cosmovisor.Config{Home: home, Name: "dummyd"} + cfg := prepareConfig( + s.T(), + fmt.Sprintf("%s/%s", workDir, "testdata/validate"), + cosmovisor.Config{ + Name: "dummyd", + }, + ) currentBin, err := cfg.CurrentBin() s.Require().NoError(err) - s.Require().Equal(cfg.GenesisBin(), currentBin) + rPath, err := filepath.EvalSymlinks(cfg.GenesisBin()) + s.Require().NoError(err) + + s.Require().Equal(rPath, currentBin) // ensure we cannot set this to an invalid value for _, name := range []string{"missing", "nobin"} { @@ -43,7 +51,10 @@ func (s *upgradeTestSuite) TestCurrentBin() { currentBin, err := cfg.CurrentBin() s.Require().NoError(err) - s.Require().Equal(cfg.GenesisBin(), currentBin, name) + rPath, err := filepath.EvalSymlinks(cfg.GenesisBin()) + s.Require().NoError(err) + + s.Require().Equal(rPath, currentBin, name) } // try a few times to make sure this can be reproduced @@ -55,29 +66,46 @@ func (s *upgradeTestSuite) TestCurrentBin() { currentBin, err := cfg.CurrentBin() s.Require().NoError(err) - s.Require().Equal(cfg.UpgradeBin(name), currentBin) + rPath, err := filepath.EvalSymlinks(cfg.UpgradeBin(name)) + s.Require().NoError(err) + + s.Require().Equal(rPath, currentBin) } } func (s *upgradeTestSuite) TestCurrentAlwaysSymlinkToDirectory() { - home := copyTestData(s.T(), "validate") - cfg := cosmovisor.Config{Home: home, Name: "dummyd"} + cfg := prepareConfig( + s.T(), + fmt.Sprintf("%s/%s", workDir, "testdata/validate"), + cosmovisor.Config{ + Name: "dummyd", + }, + ) currentBin, err := cfg.CurrentBin() s.Require().NoError(err) - s.Require().Equal(cfg.GenesisBin(), currentBin) + + rPath, err := filepath.EvalSymlinks(cfg.GenesisBin()) + s.Require().NoError(err) + + s.Require().Equal(rPath, currentBin) s.assertCurrentLink(cfg, "genesis") err = cfg.SetCurrentUpgrade(upgradetypes.Plan{Name: "chain2"}) s.Require().NoError(err) currentBin, err = cfg.CurrentBin() s.Require().NoError(err) - s.Require().Equal(cfg.UpgradeBin("chain2"), currentBin) + + eval, err := filepath.EvalSymlinks(cfg.UpgradeBin("chain2")) + s.Require().NoError(err) + + s.Require().Equal(eval, currentBin) s.assertCurrentLink(cfg, filepath.Join("upgrades", "chain2")) } -func (s *upgradeTestSuite) assertCurrentLink(cfg cosmovisor.Config, target string) { +func (s *upgradeTestSuite) assertCurrentLink(cfg *cosmovisor.Config, target string) { link := filepath.Join(cfg.Root(), "current") + // ensure this is a symlink info, err := os.Lstat(link) s.Require().NoError(err) @@ -85,20 +113,29 @@ func (s *upgradeTestSuite) assertCurrentLink(cfg cosmovisor.Config, target strin dest, err := os.Readlink(link) s.Require().NoError(err) - expected := filepath.Join(cfg.Root(), target) - s.Require().Equal(expected, dest) + s.Require().Equal(target, dest) } // TODO: test with download (and test all download functions) func (s *upgradeTestSuite) TestUpgradeBinaryNoDownloadUrl() { - home := copyTestData(s.T(), "validate") - cfg := &cosmovisor.Config{Home: home, Name: "dummyd", AllowDownloadBinaries: true} + cfg := prepareConfig( + s.T(), + fmt.Sprintf("%s/%s", workDir, "testdata/validate"), + cosmovisor.Config{ + Name: "dummyd", + AllowDownloadBinaries: true, + }, + ) + logger := log.NewLogger(os.Stdout).With(log.ModuleKey, "cosmovisor") currentBin, err := cfg.CurrentBin() s.Require().NoError(err) - s.Require().Equal(cfg.GenesisBin(), currentBin) + rPath, err := filepath.EvalSymlinks(cfg.GenesisBin()) + s.Require().NoError(err) + + s.Require().Equal(rPath, currentBin) // do upgrade ignores bad files for _, name := range []string{"missing", "nobin"} { @@ -107,7 +144,11 @@ func (s *upgradeTestSuite) TestUpgradeBinaryNoDownloadUrl() { s.Require().Error(err, name) currentBin, err := cfg.CurrentBin() s.Require().NoError(err) - s.Require().Equal(cfg.GenesisBin(), currentBin, name) + + rPath, err := filepath.EvalSymlinks(cfg.GenesisBin()) + s.Require().NoError(err) + + s.Require().Equal(rPath, currentBin, name) } // make sure it updates a few times @@ -121,7 +162,10 @@ func (s *upgradeTestSuite) TestUpgradeBinaryNoDownloadUrl() { currentBin, err := cfg.CurrentBin() s.Require().NoError(err) - s.Require().Equal(upgradeBin, currentBin) + rPath, err := filepath.EvalSymlinks(upgradeBin) + s.Require().NoError(err) + + s.Require().Equal(rPath, currentBin) } } @@ -135,26 +179,25 @@ func (s *upgradeTestSuite) TestUpgradeBinary() { }{ "get raw binary with checksum": { // sha256sum ./testdata/repo/raw_binary/autod - url: "./testdata/repo/raw_binary/autod?checksum=sha256:e6bc7851600a2a9917f7bf88eb7bdee1ec162c671101485690b4deb089077b0d", + url: workDir + "/testdata/repo/raw_binary/autod?checksum=sha256:e6bc7851600a2a9917f7bf88eb7bdee1ec162c671101485690b4deb089077b0d", canDownload: true, validBinary: true, }, "get raw binary with invalid checksum": { - url: "./testdata/repo/raw_binary/autod?checksum=sha256:73e2bd6cbb99261733caf137015d5cc58e3f96248d8b01da68be8564989dd906", + url: workDir + "/testdata/repo/raw_binary/autod?checksum=sha256:73e2bd6cbb99261733caf137015d5cc58e3f96248d8b01da68be8564989dd906", canDownload: false, }, "get zipped directory with valid checksum": { - // sha256sum ./testdata/repo/chain3-zip_dir/autod.zip - url: "./testdata/repo/chain3-zip_dir/autod.zip?checksum=sha256:8951f52a0aea8617de0ae459a20daf704c29d259c425e60d520e363df0f166b4", + url: workDir + "/testdata/repo/chain3-zip_dir/autod.zip?checksum=sha256:8951f52a0aea8617de0ae459a20daf704c29d259c425e60d520e363df0f166b4", canDownload: true, validBinary: true, }, "get zipped directory with invalid checksum": { - url: "./testdata/repo/chain3-zip_dir/autod.zip?checksum=sha256:73e2bd6cbb99261733caf137015d5cc58e3f96248d8b01da68be8564989dd906", + url: workDir + "/testdata/repo/chain3-zip_dir/autod.zip?checksum=sha256:73e2bd6cbb99261733caf137015d5cc58e3f96248d8b01da68be8564989dd906", canDownload: false, }, "invalid url": { - url: "./testdata/repo/bad_dir/autod?checksum=sha256:73e2bd6cbb99261733caf137015d5cc58e3f96248d8b01da68be8564989dd906", + url: workDir + "/testdata/repo/bad_dir/autod?checksum=sha256:73e2bd6cbb99261733caf137015d5cc58e3f96248d8b01da68be8564989dd906", canDownload: false, }, "valid remote": { @@ -167,14 +210,14 @@ func (s *upgradeTestSuite) TestUpgradeBinary() { for label, tc := range cases { s.Run(label, func() { var err error - // make temp dir - home := copyTestData(s.T(), "download") - - cfg := &cosmovisor.Config{ - Home: home, - Name: "autod", - AllowDownloadBinaries: true, - } + cfg := prepareConfig( + s.T(), + fmt.Sprintf("%s/%s", workDir, "testdata/download"), + cosmovisor.Config{ + Name: "autod", + AllowDownloadBinaries: true, + }, + ) url := tc.url if strings.HasPrefix(url, "./") { @@ -198,17 +241,37 @@ func (s *upgradeTestSuite) TestUpgradeBinary() { } func (s *upgradeTestSuite) TestOsArch() { - // all download tests will fail if we are not on linux... - s.Require().Equal("linux/amd64", cosmovisor.OSArch()) + // all download tests will fail if we are not on linux or darwin... + hosts := []string{ + "linux/arm64", + "linux/amd64", + "darwin/amd64", + "darwin/arm64", + } + + s.Require().True(slices.Contains(hosts, cosmovisor.OSArch())) } // copyTestData will make a tempdir and then // "cp -r" a subdirectory under testdata there // returns the directory (which can now be used as Config.Home) and modified safely -func copyTestData(t *testing.T, subdir string) string { +func copyTestData(t *testing.T, testData string) string { t.Helper() tmpdir := t.TempDir() - require.NoError(t, copy.Copy(filepath.Join("testdata", subdir), tmpdir)) + require.NoError(t, copy.Copy(testData, tmpdir)) return tmpdir } + +func prepareConfig(t *testing.T, testData string, config cosmovisor.Config) *cosmovisor.Config { + t.Helper() + + tmpdir := copyTestData(t, testData) + + config.Home = tmpdir + + err := os.Chdir(config.Root()) + require.NoError(t, err) + + return &config +} From 8e34382463937e19321316c9777def28855d5952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Francisco=20L=C3=B3pez?= Date: Mon, 7 Oct 2024 15:38:31 +0200 Subject: [PATCH 4/6] docs: Update mint docs (#22108) --- x/mint/README.md | 181 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 123 insertions(+), 58 deletions(-) diff --git a/x/mint/README.md b/x/mint/README.md index 5a885208eca1..db344931392c 100644 --- a/x/mint/README.md +++ b/x/mint/README.md @@ -8,20 +8,24 @@ sidebar_position: 1 * [Concepts](#concepts) * [The Minting Mechanism](#the-minting-mechanism) + * [Inflation](#inflation) * [Provisions](#provisions) * [Relation to Inflation](#relation-to-inflation) - * [Usage per Block](#usage-per-block) + * [Usage per Block](#usage-per-block-default-function) * [Example](#example) + * [Bonding](#bonding) * [State](#state) * [Minter](#minter) * [Params](#params) -* [Epoch minting](#epoch-minting) +* [Minting Methods](#minting-methods) + * [Epoch-based Minting](#epoch-based-minting) + * [Block-based Minting](#block-based-minting) * [MintFn](#mintfn) -* [Block based minting](#block-based-minting) * [Default configuration](#default-configuration) - * [NextInflationRate](#nextinflationrate) - * [NextAnnualProvisions](#nextannualprovisions) - * [BlockProvision](#blockprovision) + * [Calculations](#calculations) + * [NextInflationRate](#inflation-rate-calculation) + * [NextAnnualProvisions](#nextannualprovisions) + * [BlockProvision](#blockprovision) * [Parameters](#parameters) * [Events](#events) * [BeginBlocker](#beginblocker) @@ -34,26 +38,33 @@ sidebar_position: 1 ### The Minting Mechanism -The minting mechanism was designed to: +The minting mechanism in the x/mint module has been redesigned to offer more flexibility. The `InflationCalculationFn` has been deprecated in favor of `MintFn`, that can be customized by the application developer. The `MintFn` function is passed to the `NewAppModule` function and is used to mint tokens on the configured epoch beginning. This change allows users to define their own minting logic and removes any assumptions on how tokens are minted. -* allow for a flexible inflation rate determined by market demand targeting a particular bonded-stake ratio -* effect a balance between market liquidity and staked supply +Key features of the new minting mechanism: -In order to best determine the appropriate market rate for inflation rewards, a -moving change rate is used. The moving change rate mechanism ensures that if -the % bonded is either over or under the goal %-bonded, the inflation rate will -adjust to further incentivize or disincentivize being bonded, respectively. Setting the goal -%-bonded at less than 100% encourages the network to maintain some non-staked tokens -which should help provide some liquidity. +1. **Customizable Minting Function**: The `MintFn` can be defined by the application to implement any desired minting logic. +2. **Default Implementation**: If no custom function is provided, a default minting function is used. +3. **Epoch-based or Block-based Minting**: The mechanism supports both epoch-based and block-based minting, depending on how the `MintFn` is implemented. +4. **Flexible Inflation**: The inflation rate can be adjusted based on various parameters, not just the bonded ratio. -It can be broken down in the following way: +The default minting function, if no custom one is provided, is implemented in the `DefaultMintFn`. +This function is called during the `BeginBlocker` and is responsible for minting new tokens, implementation details can be found [here](#default-configuration). -* If the actual percentage of bonded tokens is below the goal %-bonded the inflation rate will - increase until a maximum value is reached -* If the goal % bonded (67% in Cosmos-Hub) is maintained, then the inflation - rate will stay constant -* If the actual percentage of bonded tokens is above the goal %-bonded the inflation rate will - decrease until a minimum value is reached +### Inflation + +Inflation is a key concept in the x/mint module, responsible for the creation of new tokens over time. The inflation rate determines how quickly the total supply of tokens increases. + +Key aspects of inflation in the x/mint module: + +1. **Dynamic Inflation**: The inflation rate can change over time based on various factors, primarily the bonded ratio. +2. **Bounded Inflation**: The inflation rate is typically constrained between a minimum and maximum value to prevent extreme fluctuations. +3. **Inflation Calculation**: The specific method of calculating inflation can be customized using the `MintFn`, allowing for flexible monetary policies. + +In the default implementation, inflation is calculated as follows: + +```plaintext +Inflation = CurrentInflation + (1 - BondedRatio / GoalBonded) * (InflationRateChange / BlocksPerYear) +``` ### Provisions @@ -63,7 +74,7 @@ Provisions are the number of tokens generated and distributed in each block. The The inflation rate determines the percentage of the total supply of tokens that will be added as provisions over a year. These annual provisions are divided by the number of blocks in a year to obtain the provisions per block. -#### Usage per Block +#### Usage per Block (default function) Each block uses a fraction of the annual provisions, calculated as: @@ -74,7 +85,7 @@ Provisions per block = Annual provisions / Number of blocks per year These provisions are distributed to validators and delegators as rewards for their participation in the network. -#### Example +##### Example For example, if the total supply of tokens is 1,000,000 and the inflation rate is 10%, the annual provisions would be: @@ -83,21 +94,17 @@ Annual provisions = 1,000,000 * 0.10 = 100,000 tokens If there are 3,153,600 blocks per year (one block every 10 seconds), the provisions per block would be: Provisions per block = 100,000 / 3,153,600 ≈ 0.0317 tokens per block. -These provisions are then distributed to validators and delegators as rewards. +These provisions are then passed to the fee collector. -```mermaid -flowchart TD - A[Start] --> B[Get Total Supply] - B --> C[Get Inflation Rate] - C --> D[Calculate Annual Provisions] - D --> E[Calculate Provisions per Block] - E --> F[Distribute Provisions to Validators and Delegators] - - subgraph Calculation - D --> |Annual Provisions = Total Supply * Inflation Rate| D - E --> |Provisions per Block = Annual Provisions / Number of Blocks per Year| E - end -``` +### Bonding + +Bonding refers to the process of staking tokens in the network, which plays a crucial role in the Proof of Stake consensus mechanism and affects the minting process. + +Key aspects of bonding in relation to the x/mint module: + +1. **Bonded Ratio**: This is the proportion of the total token supply that is currently staked (bonded) in the network. +2. **Goal Bonded Ratio**: A target percentage of tokens that should ideally be bonded, defined in the module parameters. +3. **Inflation Adjustment**: The bonded ratio is used to adjust the inflation rate, encouraging or discouraging bonding as needed to maintain network security and token liquidity. ## State @@ -109,7 +116,7 @@ related to minting (in the `data` field) * Minter: `0x00 -> ProtocolBuffer(minter)` ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/release/v0.52.x/x/mint/proto/cosmos/mint/v1beta1/mint.proto#L11-L29 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/mint/proto/cosmos/mint/v1beta1/mint.proto#L11-L29 ``` ### Params @@ -122,23 +129,19 @@ A value of `0` indicates an unlimited supply. * Params: `mint/params -> legacy_amino(params)` ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/release/v0.52.x/x/mint/proto/cosmos/mint/v1beta1/mint.proto#L31-L73 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/mint/proto/cosmos/mint/v1beta1/mint.proto#L31-L73 ``` -## Epoch minting +## Minting Methods -In the latest release of x/mint, the minting logic has been refactored to allow for more flexibility in the minting process. The `InflationCalculationFn` has been deprecated in favor of `MintFn`. The `MintFn` function is passed to the `NewAppModule` function and is used to mint tokens on the configured epoch beginning. This change allows users to define their own minting logic and removes any assumptions on how tokens are minted. +### Epoch-based Minting -```mermaid -flowchart LR - A[BeforeEpochStart] --> B[MintFn] +Epoch-based minting allows for tokens to be minted at specific intervals or epochs, rather than on every block. +To implement epoch-based minting, the `MintFn` should be designed to mint tokens only when a specific epoch ID is received. The epoch ID and number are passed as parameters to the `MintFn`. - subgraph B["MintFn (user defined)"] - direction LR - C[Get x/staking info] --> D[Calculate Inflation] - D --> E[Mint Tokens] - end -``` +### Block-based Minting + +In addition to minting based on epoch, minting based on block is also possible. This is achieved through calling the `MintFn` in `BeginBlock` with an epochID and epochNumber of `"block"` and `-1`, respectively. ### MintFn @@ -154,16 +157,49 @@ How this function mints tokens is defined by the app developers, meaning they ca Note that BeginBlock will keep calling the MintFn for every block, so it is important to ensure that MintFn returns early if the epoch ID does not match the expected one. ::: +### Default configuration -## Block based minting +If no `MintFn` is passed to the `NewAppModule` function, the minting logic defaults to block-based minting, corresponding to `mintKeeper.DefaultMintFn(types.DefaultInflationCalculationFn)`. -In addition to minting based on epoch, minting based on block is also possible. This is achieved through calling the `MintFn` in `BeginBlock` with an epochID and epochNumber of `"block"` and `-1`, respectively. +The next diagram shows how the `DefaultMintFn` works: -### Default configuration +```mermaid +flowchart TD + A[BeforeEpochStart] --> B[MintFn] + B --> C{epochId == 'block'?} + C -->|No| D[Return without minting] + C -->|Yes| E[Get StakingTokenSupply] + E --> F[Get BondedRatio] + F --> G[Get Parameters] + G --> H[Calculate Inflation] + H --> I[Calculate Annual Provisions] + I --> J[Calculate Block Provision] + J --> K{MaxSupply > 0?} + K -->|No| M[Mint coins] + K -->|Yes| L{TotalSupply + MintedCoins > MaxSupply?} + L -->|No| M + L -->|Yes| N[Adjust minting amount] + N --> O{Difference > 0?} + O -->|No| P[Do not mint] + O -->|Yes| M + M --> Q[Send minted coins to FeeCollector] + Q --> R[Emit events] + R --> S[End] + + subgraph Calculations + H --> |Uses InflationCalculationFn| H + I --> |AnnualProvisions = TotalSupply * Inflation| I + J --> |BlockProvision = AnnualProvisions / BlocksPerYear| J + end -If no `MintFn` is passed to the `NewAppModule` function, the minting logic defaults to block-based minting, corresponding to `mintKeeper.DefaultMintFn(types.DefaultInflationCalculationFn)`. + subgraph MaxSupply Adjustment + N --> |MintedCoins = MaxSupply - TotalSupply| N + end +``` -### Inflation rate calculation +### Calculations + +#### Inflation rate calculation Inflation rate is calculated using an "inflation calculation function" that's passed to the `NewAppModule` function. If no function is passed, then the SDK's @@ -200,7 +236,7 @@ NextInflationRate(params Params, bondedRatio math.LegacyDec) (inflation math.Leg } ``` -### NextAnnualProvisions +#### NextAnnualProvisions Calculate the annual provisions based on current total supply and inflation rate. This parameter is calculated once per block. @@ -208,9 +244,10 @@ rate. This parameter is calculated once per block. ```go NextAnnualProvisions(params Params, totalSupply math.LegacyDec) (provisions math.LegacyDec) { return Inflation * totalSupply +} ``` -### BlockProvision +#### BlockProvision Calculate the provisions generated for each block based on current annual provisions. The provisions are then minted by the `mint` module's `ModuleMinterAccount` and then transferred to the `auth`'s `FeeCollector` `ModuleAccount`. @@ -315,6 +352,12 @@ simd query mint params [flags] Example: +```shell +simd query mint params +``` + +Example Output: + ```yml blocks_per_year: "4360000" goal_bonded: "0.670000000000000000" @@ -325,6 +368,28 @@ mint_denom: stake max_supply: "0" ``` +#### Transactions + +The `tx` commands allow users to interact with the `mint` module. + +```shell +simd tx mint --help +``` + +##### update-params-proposal + +The `update-params-proposal` command allows users to submit a proposal to update the mint module parameters (Note: the entire params must be provided). + +```shell +simd tx mint update-params-proposal [flags] +``` + +Example: + +```shell +simd tx mint update-params-proposal '{ "mint_denom": "stake" }' +``` + ### gRPC A user can query the `mint` module using gRPC endpoints. From 46b01ba176484cf76529f12197be6757d7bb4aa5 Mon Sep 17 00:00:00 2001 From: XiaoBei <1505929057@qq.com> Date: Mon, 7 Oct 2024 23:11:54 +0800 Subject: [PATCH 5/6] docs: modify the corresponding link in the code document (#22151) --- x/feegrant/README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/x/feegrant/README.md b/x/feegrant/README.md index f60720a51ff4..63e563d2efb8 100644 --- a/x/feegrant/README.md +++ b/x/feegrant/README.md @@ -35,13 +35,13 @@ This module allows accounts to grant fee allowances and to use fees from their a `Grant` is stored in the KVStore to record a grant with full context. Every grant will contain `granter`, `grantee` and what kind of `allowance` is granted. `granter` is an account address who is giving permission to `grantee` (the beneficiary account address) to pay for some or all of `grantee`'s transaction fees. `allowance` defines what kind of fee allowance (`BasicAllowance` or `PeriodicAllowance`, see below) is granted to `grantee`. `allowance` accepts an interface which implements `FeeAllowanceI`, encoded as `Any` type. There can be only one existing fee grant allowed for a `grantee` and `granter`, self grants are not allowed. ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/release/v0.52.x/x/feegrant/proto/cosmos/feegrant/v1beta1/feegrant.proto#L86-L96 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/feegrant/proto/cosmos/feegrant/v1beta1/feegrant.proto#L86-L96 ``` `FeeAllowanceI` looks like: ```go reference -https://github.com/cosmos/cosmos-sdk/blob/release/v0.52.x/x/feegrant/fees.go#L10-L34 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/feegrant/fees.go#L10-L34 ``` ### Fee Allowance types @@ -57,7 +57,7 @@ There are two types of fee allowances present at the moment: `BasicAllowance` is permission for `grantee` to use fee from a `granter`'s account. If any of the `spend_limit` or `expiration` reaches its limit, the grant will be removed from the state. ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/release/v0.52.x/x/feegrant/proto/cosmos/feegrant/v1beta1/feegrant.proto#L15-L33 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/feegrant/proto/cosmos/feegrant/v1beta1/feegrant.proto#L15-L33 ``` * `spend_limit` is the limit of coins that are allowed to be used from the `granter` account. If it is empty, it assumes there's no spend limit, `grantee` can use any number of available coins from `granter` account address before the expiration. @@ -71,7 +71,7 @@ https://github.com/cosmos/cosmos-sdk/blob/release/v0.52.x/x/feegrant/proto/cosmo `PeriodicAllowance` is a repeating fee allowance for the mentioned period, we can mention when the grant can expire as well as when a period can reset. We can also define the maximum number of coins that can be used in a mentioned period of time. ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/release/v0.52.x/x/feegrant/proto/cosmos/feegrant/v1beta1/feegrant.proto#L35-L71 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/feegrant/proto/cosmos/feegrant/v1beta1/feegrant.proto#L35-L71 ``` * `basic` is the instance of `BasicAllowance` which is optional for periodic fee allowance. If empty, the grant will have no `expiration` and no `spend_limit`. @@ -89,7 +89,7 @@ https://github.com/cosmos/cosmos-sdk/blob/release/v0.52.x/x/feegrant/proto/cosmo `AllowedMsgAllowance` is a fee allowance, it can be any of `BasicFeeAllowance`, `PeriodicAllowance` but restricted only to the allowed messages mentioned by the granter. ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/release/v0.52.x/x/feegrant/proto/cosmos/feegrant/v1beta1/feegrant.proto#L73-L84 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/feegrant/proto/cosmos/feegrant/v1beta1/feegrant.proto#L73-L84 ``` * `allowance` is either `BasicAllowance` or `PeriodicAllowance`. @@ -101,19 +101,19 @@ https://github.com/cosmos/cosmos-sdk/blob/release/v0.52.x/x/feegrant/proto/cosmo `feegrant` module introduces a `FeeGranter` flag for CLI for the sake of executing transactions with fee granter. When this flag is set, `clientCtx` will append the granter account address for transactions generated through CLI. ```go reference -https://github.com/cosmos/cosmos-sdk/blob/release/v0.52.x/client/cmd.go#L256-L267 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/client/cmd.go#L269-L280 ``` ```go reference -https://github.com/cosmos/cosmos-sdk/blob/release/v0.52.x/client/tx/tx.go#L129-L131 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/client/tx/tx.go#L129-L131 ``` ```go reference -https://github.com/cosmos/cosmos-sdk/blob/release/v0.52.x/x/auth/tx/builder.go#L208 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/auth/tx/builder.go#L208 ``` ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/release/v0.52.x/proto/cosmos/tx/v1beta1/tx.proto#L216-L243 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/proto/cosmos/tx/v1beta1/tx.proto#L216-L243 ``` Example cmd: @@ -147,7 +147,7 @@ Fee allowance grants are stored in the state as follows: * Grant: `0x00 | grantee_addr_len (1 byte) | grantee_addr_bytes | granter_addr_len (1 byte) | granter_addr_bytes -> ProtocolBuffer(Grant)` ```go reference -https://github.com/cosmos/cosmos-sdk/blob/release/v0.52.x/x/feegrant/feegrant.pb.go#L222-L230 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/feegrant/feegrant.pb.go#L222-L230 ``` ### FeeAllowanceQueue @@ -165,7 +165,7 @@ Fee allowance queue keys are stored in the state as follows: A fee allowance grant will be created with the `MsgGrantAllowance` message. ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/release/v0.52.x/x/feegrant/proto/cosmos/feegrant/v1beta1/tx.proto#L30-L44 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/feegrant/proto/cosmos/feegrant/v1beta1/tx.proto#L30-L44 ``` ### Msg/RevokeAllowance @@ -173,7 +173,7 @@ https://github.com/cosmos/cosmos-sdk/blob/release/v0.52.x/x/feegrant/proto/cosmo An allowed grant fee allowance can be removed with the `MsgRevokeAllowance` message. ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/release/v0.52.x/x/feegrant/proto/cosmos/feegrant/v1beta1/tx.proto#L49-L62 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/feegrant/proto/cosmos/feegrant/v1beta1/tx.proto#L49-L62 ``` ## Events From 9076487d035e43d39fe54e8498da1ce31b9c845c Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 7 Oct 2024 14:52:43 -0500 Subject: [PATCH 6/6] docs(core): create docs for environment and core API (#22156) --- docs/learn/advanced/05-encoding.md | 35 ++++--- docs/learn/advanced/17-core.md | 141 +++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+), 19 deletions(-) create mode 100644 docs/learn/advanced/17-core.md diff --git a/docs/learn/advanced/05-encoding.md b/docs/learn/advanced/05-encoding.md index 4a3af677d61d..07fca0e6bb01 100644 --- a/docs/learn/advanced/05-encoding.md +++ b/docs/learn/advanced/05-encoding.md @@ -16,14 +16,15 @@ While encoding in the Cosmos SDK used to be mainly handled by `go-amino` codec, ## Encoding -The Cosmos SDK utilizes two binary wire encoding protocols, [Amino](https://github.com/tendermint/go-amino/) which is an object encoding specification and [Protocol Buffers](https://developers.google.com/protocol-buffers), a subset of Proto3 with an extension for -interface support. See the [Proto3 spec](https://developers.google.com/protocol-buffers/docs/proto3) -for more information on Proto3, which Amino is largely compatible with (but not with Proto2). +The Cosmos SDK supports two wire encoding protocols. Binary encoding is fulfilled by [Protocol +Buffers](https://developers.google.com/protocol-buffers), specifically the +[gogoprotobuf](https://github.com/cosmos/gogoproto/) implementation, which is a subset of +[Proto3](https://developers.google.com/protocol-buffers/docs/proto3) with an extension for +interface support. Text encoding is fulfilled by [Amino](https://github.com/tendermint/go-amino). -Due to Amino having significant performance drawbacks, being reflection-based, and -not having any meaningful cross-language/client support, Protocol Buffers, specifically -[gogoprotobuf](https://github.com/cosmos/gogoproto/), is being used in place of Amino. -Note, this process of using Protocol Buffers over Amino is still an ongoing process. +Due to Amino having significant performance drawbacks, being reflection-based, and not having +any meaningful cross-language/client support, Amino is only used to generate JSON (Amino +JSON) in order to support the Amino JSON sign mode, and for JSON RPC endpoints. Binary wire encoding of types in the Cosmos SDK can be broken down into two main categories, client encoding and store encoding. Client encoding mainly revolves @@ -31,23 +32,19 @@ around transaction processing and signing, whereas store encoding revolves aroun types used in state-machine transitions and what is ultimately stored in the Merkle tree. -For store encoding, protobuf definitions can exist for any type and will typically -have an Amino-based "intermediary" type. Specifically, the protobuf-based type -definition is used for serialization and persistence, whereas the Amino-based type -is used for business logic in the state-machine where they may convert back-n-forth. -Note, the Amino-based types may slowly be phased-out in the future, so developers -should take note to use the protobuf message definitions where possible. +For storage encoding, module developers are encouraged to use Protobuf encoding for their types +but may choose any encoding schema they like. The +[collections](../../build/packages/02-collections.md) package automatically handles encoding and +decoding of state for you. In the `codec` package, there exists two core interfaces, `BinaryCodec` and `JSONCodec`, where the former encapsulates the current Amino interface except it operates on types implementing the latter instead of generic `interface{}` types. -The `ProtoCodec`, where both binary and JSON serialization is handled -via Protobuf. This means that modules may use Protobuf encoding, but the types must -implement `ProtoMarshaler`. If modules wish to avoid implementing this interface -for their types, this is autogenerated via [buf](https://buf.build/) - -Modules are recommended to use [collections](../../build/packages/02-collections.md) for handling encoding and decoding of state. Usage of collections handles marshal and unmarshal for you. By default protobuf is used but other encodings can be used if preferred. +The `ProtoCodec`, where both binary and JSON serialization is handled via Protobuf. This means +that modules may use Protobuf encoding, but the types must implement `ProtoMarshaler`. If +modules wish to avoid implementing this interface for their types, this is autogenerated via +[buf](https://buf.build/) ### Gogoproto diff --git a/docs/learn/advanced/17-core.md b/docs/learn/advanced/17-core.md new file mode 100644 index 000000000000..85f5b110cec5 --- /dev/null +++ b/docs/learn/advanced/17-core.md @@ -0,0 +1,141 @@ +--- +sidebar_position: 1 +--- + +# Core + +Core is package which specifies the interfaces for core components of the Cosmos SDK. Other +packages in the SDK implement these interfaces to provide the core functionality. This design +provides modularity and flexibility to the SDK, allowing developers to swap out implementations +of core components as needed. As such it is often referred to as the Core API. + +## Environment + +The `Environment` struct is a core component of the Cosmos SDK. It provides access to the core +services of the SDK, such as the KVStore, EventManager, and Logger. The `Environment` struct is +passed to modules and other components of the SDK to provide access to these services. + +```go reference +https://github.com/cosmos/cosmos-sdk/blob/core/v1.0.0-alpha.4/core/appmodule/v2/environment.go#L16-L29 +``` + +Historically the SDK has used an [sdk.Context](02-context.md) to pass around services and data. +`Environment` is a newer construct that is intended to replace an `sdk.Context` in many cases. +`sdk.Context` will be deprecated in the future on the same timeline as [Baseapp](00-baseapp.md). + +## Branch Service + +The [BranchService](https://pkg.go.dev/cosmossdk.io/core/branch#Service.Execute) provides an +interface to execute arbitrary code in a branched store. This is useful for executing code +that needs to make changes to the store, but may need to be rolled back if an error occurs. +Below is a contrived example based on the `x/epoch` module's BeginBlocker logic. + +```go reference +func (k Keeper) BeginBlocker(ctx context.Context) error { + err := k.EpochInfo.Walk( + // ... + ctx, + nil, + func(key string, epochInfo types.EpochInfo) (stop bool, err error) { + // ... + if err := k.BranchService.Execute(ctx, func(ctx context.Context) error { + return k.AfterEpochEnd(ctx, epochInfo.Identifier, epochInfo.CurrentEpoch) + }); err != nil { + return true, err + } + }) +} +``` + +Note that calls to `BranchService.Execute` are atomic and cannot share state with each other +except when the transaction is successful. If successful, the changes made to the store will be +committed. If an error occurs, the changes will be rolled back. + +## Event Service + +The Event Service returns a handle to an [EventManager](https://pkg.go.dev/cosmossdk.io/core@v1.0.0-alpha.4/event#Manager) +which can be used to emit events. For information on how to emit events and their meaning +in the SDK see the [Events](08-events.md) document. + +Note that core's `EventManager` API is a subset of the EventManager API described above; the +latter will be deprecated and removed in the future. Roughly speaking legacy `EmitTypeEvent` +maps to `Emit` and legacy `EmitEvent` maps to `EmitKV`. + +```go reference +https://github.com/cosmos/cosmos-sdk/blob/core/v1.0.0-alpha.4/core/event/service.go#L18-L29 +``` + +## Gas Service + +The gas service encapsulates both gas configuration and a gas meter. Gas consumption is largely +handled at the framework level for transaction processing and state access but modules can +choose to use the gas service directly if needed. + +```go reference +https://github.com/cosmos/cosmos-sdk/blob/core/v1.0.0-alpha.4/core/gas/service.go#L26-L54 +``` + +## Header Service + +The header service provides access to the current block header. This is useful for modules that +need to access the block header fields like `Time` and `Height` during transaction processing. + +```go reference +https://github.com/cosmos/cosmos-sdk/blob/a3729c1ad6ba2fb46f879ec7ea67c3afc02e9859/core/header/service.go#L11-L23 +``` + +### Custom Header Service + +Core's service oriented architecture (SOA) allows for chain developers to define a custom +implementation of the `HeaderService` interface. This would involve creating a new struct that +satisfies `HeaderService` but composes additional logic on top. An example of where this would +happen (when using depinject is shown below). Note this example is taken from `runtime/v2` but +could easily be adapted to `runtime/v1` (the default runtime 0.52). This same pattern can be +replicated for any core service. + +```go reference +https://github.com/cosmos/cosmos-sdk/blob/489aaae40234f1015a7bbcfa9384a89dc8de8153/runtime/v2/module.go#L262-L288 +``` + +These bindings are applied to the `depinject` container in simapp/v2 as shown below. + +```go reference +https://github.com/cosmos/cosmos-sdk/blob/489aaae40234f1015a7bbcfa9384a89dc8de8153/simapp/v2/app_di.go#L72-L74 +``` + +## Query and Message Router Service + +Both the query and message router services are implementation of the same interface, `router.Service`. + +```go reference +https://github.com/cosmos/cosmos-sdk/blob/core/v1.0.0-alpha.4/core/router/service.go#L11-L16 +``` + +Both are exposed to modules so that arbitrary messages and queries can be routed to the +appropriate handler. This powerful abstraction allows module developers to fully decouple +modules from each other by using only the proto message for dispatching. This is particularly +useful for modules like `x/accounts` which require a dynamic dispatch mechanism in order to +function. + +## TransactionService + +```go reference +https://github.com/cosmos/cosmos-sdk/blob/core/v1.0.0-alpha.4/core/transaction/service.go#L21-L25 +``` + +The transaction service provides access to the execution mode a state machine transaction is +running in, which may be one of `Check`, `Recheck`, `Simulate` or `Finalize`. The SDK primarily +uses these flags in ante handlers to skip certain checks while in `Check` or `Simulate` modes, +but module developers may find uses for them as well. + +## KVStore Service + +```go reference +https://github.com/cosmos/cosmos-sdk/blob/core/v1.0.0-alpha.4/core/store/service.go#L5-L11 +``` + +The KVStore service abstracts access to, and creation of, key-value stores. Most use cases will +be backed by a merkle-tree store, but developers can provide their own implementations if +needed. In the case of the `KVStoreService` implementation provided in `Environment`, module +developers should understand that calling `OpenKVStore` will return a store already scoped to +the module's prefix. The wiring for this scoping is specified in `runtime`.