diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 376dfb95755..48c7e82a967 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -144,7 +144,7 @@ jobs: name: "${{ github.sha }}-e2e-coverage" path: ./tests/e2e-profile.out - test-system: + test-system: # v2 system tests are in v2-test.yml runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -183,16 +183,6 @@ jobs: name: "testnet-setup" path: ./systemtests/testnet/ retention-days: 3 - - name: system tests v2 - if: env.GIT_DIFF - run: | - COSMOS_BUILD_OPTIONS=v2 make test-system - - uses: actions/upload-artifact@v3 - if: failure() - with: - name: "testnet-setup" - path: ./systemtests/testnet/ - retention-days: 3 repo-analysis: runs-on: ubuntu-latest diff --git a/.github/workflows/v2-test.yml b/.github/workflows/v2-test.yml index 6896f803a2c..e26e12c0f40 100644 --- a/.github/workflows/v2-test.yml +++ b/.github/workflows/v2-test.yml @@ -109,3 +109,43 @@ jobs: if: env.GIT_DIFF run: | cd server/v2/cometbft && go test -mod=readonly -race -timeout 30m -tags='ledger test_ledger_mock' + + test-system-v2: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-tags: true + - uses: actions/setup-go@v5 + with: + go-version: "1.23" + check-latest: true + cache: true + cache-dependency-path: | + simapp/v2/go.sum + systemtest/go.sum + - uses: technote-space/get-diff-action@v6.1.2 + id: git_diff + with: + PATTERNS: | + **/*.go + go.mod + go.sum + **/go.mod + **/go.sum + **/Makefile + Makefile + - name: Install musl lib for simd (docker) binary + if: env.GIT_DIFF + run: | + sudo apt-get install -y musl + - name: system tests v2 + if: env.GIT_DIFF + run: | + COSMOS_BUILD_OPTIONS=v2 make test-system + - uses: actions/upload-artifact@v3 + if: failure() + with: + name: "testnet-setup" + path: ./systemtests/testnet/ + retention-days: 3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 9aac848e00a..db1e8bed256 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ Every module contains its own CHANGELOG.md. Please refer to the module you are i ### Improvements +* (crypto/ledger) [#22116](https://github.com/cosmos/cosmos-sdk/pull/22116) Improve error message when deriving paths using index >100 * (sims) [#21613](https://github.com/cosmos/cosmos-sdk/pull/21613) Add sims2 framework and factory methods for simpler message factories in modules * (modules) [#21963](https://github.com/cosmos/cosmos-sdk/pull/21963) Duplicatable metrics are no more collected in modules. They were unecessary overhead. @@ -60,6 +61,7 @@ Every module contains its own CHANGELOG.md. Please refer to the module you are i * (sims) [#21952](https://github.com/cosmos/cosmos-sdk/pull/21952) Use liveness matrix for validator sign status in sims * (sims) [#21906](https://github.com/cosmos/cosmos-sdk/pull/21906) Skip sims test when running dry on validators * (cli) [#21919](https://github.com/cosmos/cosmos-sdk/pull/21919) Query address-by-acc-num by account_id instead of id. +* (baseapp) [#21003](https://github.com/cosmos/cosmos-sdk/pull/21003) Align block header when query with latest height. ### API Breaking Changes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 22653e51c33..10b2e3396fa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -270,9 +270,9 @@ When extracting a package to its own go modules, some extra steps are required, ## Protobuf -We use [Protocol Buffers](https://developers.google.com/protocol-buffers) along with [gogoproto](https://github.com/cosmos/gogoproto) to generate code for use in Cosmos SDK. +We use [Protocol Buffers](https://protobuf.dev) along with [gogoproto](https://github.com/cosmos/gogoproto) to generate code for use in Cosmos SDK. -For deterministic behavior around Protobuf tooling, everything is containerized using Docker. Make sure to have Docker installed on your machine, or head to [Docker's website](https://docs.docker.com/get-docker/) to install it. +For deterministic behavior around Protobuf tooling, everything is containerized using Docker. Make sure to have Docker installed on your machine, or head to [Docker's website](https://docs.docker.com/get-started/get-docker/) to install it. For formatting code in `.proto` files, you can run `make proto-format` command. diff --git a/api/go.mod b/api/go.mod index c6e040103a8..0e67e47d69c 100644 --- a/api/go.mod +++ b/api/go.mod @@ -8,7 +8,7 @@ require ( github.com/cosmos/gogoproto v1.7.0 google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 google.golang.org/grpc v1.67.1 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 ) require ( diff --git a/api/go.sum b/api/go.sum index d1fd43b8106..54ee95f2370 100644 --- a/api/go.sum +++ b/api/go.sum @@ -22,5 +22,5 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= diff --git a/baseapp/abci.go b/baseapp/abci.go index 27118f0f3b1..15646e438ab 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "sort" + "strconv" "strings" "time" @@ -142,7 +143,7 @@ func (app *BaseApp) Info(_ *abci.InfoRequest) (*abci.InfoResponse, error) { lastCommitID := app.cms.LastCommitID() appVersion := InitialAppVersion if lastCommitID.Version > 0 { - ctx, err := app.CreateQueryContext(lastCommitID.Version, false) + ctx, err := app.CreateQueryContextWithCheckHeader(lastCommitID.Version, false, false) if err != nil { return nil, fmt.Errorf("failed creating query context: %w", err) } @@ -831,7 +832,7 @@ func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.Finaliz // NOTE: Not all raw transactions may adhere to the sdk.Tx interface, e.g. // vote extensions, so skip those. txResults := make([]*abci.ExecTxResult, 0, len(req.Txs)) - for _, rawTx := range req.Txs { + for txIndex, rawTx := range req.Txs { response := app.deliverTx(rawTx) @@ -843,6 +844,12 @@ func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.Finaliz // continue } + // append the tx index to the response.Events + for i, event := range response.Events { + response.Events[i].Attributes = append(event.Attributes, + abci.EventAttribute{Key: "tx_index", Value: strconv.Itoa(txIndex)}) + } + txResults = append(txResults, response) } @@ -1215,6 +1222,12 @@ func checkNegativeHeight(height int64) error { // CreateQueryContext creates a new sdk.Context for a query, taking as args // the block height and whether the query needs a proof or not. func (app *BaseApp) CreateQueryContext(height int64, prove bool) (sdk.Context, error) { + return app.CreateQueryContextWithCheckHeader(height, prove, true) +} + +// CreateQueryContextWithCheckHeader creates a new sdk.Context for a query, taking as args +// the block height, whether the query needs a proof or not, and whether to check the header or not. +func (app *BaseApp) CreateQueryContextWithCheckHeader(height int64, prove, checkHeader bool) (sdk.Context, error) { if err := checkNegativeHeight(height); err != nil { return sdk.Context{}, err } @@ -1238,12 +1251,7 @@ func (app *BaseApp) CreateQueryContext(height int64, prove bool) (sdk.Context, e ) } - // when a client did not provide a query height, manually inject the latest - if height == 0 { - height = lastBlockHeight - } - - if height <= 1 && prove { + if height > 0 && height <= 1 && prove { return sdk.Context{}, errorsmod.Wrap( sdkerrors.ErrInvalidRequest, @@ -1251,6 +1259,38 @@ func (app *BaseApp) CreateQueryContext(height int64, prove bool) (sdk.Context, e ) } + var header *cmtproto.Header + isLatest := height == 0 + for _, state := range []*state{ + app.checkState, + app.finalizeBlockState, + } { + if state != nil { + // branch the commit multi-store for safety + h := state.Context().BlockHeader() + if isLatest { + lastBlockHeight = qms.LatestVersion() + } + if !checkHeader || !isLatest || isLatest && h.Height == lastBlockHeight { + header = &h + break + } + } + } + + if header == nil { + return sdk.Context{}, + errorsmod.Wrapf( + sdkerrors.ErrInvalidHeight, + "header height in all state context is not latest height (%d)", lastBlockHeight, + ) + } + + // when a client did not provide a query height, manually inject the latest + if isLatest { + height = lastBlockHeight + } + cacheMS, err := qms.CacheMultiStoreWithVersion(height) if err != nil { return sdk.Context{}, @@ -1268,10 +1308,10 @@ func (app *BaseApp) CreateQueryContext(height int64, prove bool) (sdk.Context, e ChainID: app.chainID, Height: height, }). - WithBlockHeader(app.checkState.Context().BlockHeader()). + WithBlockHeader(*header). WithBlockHeight(height) - if height != lastBlockHeight { + if !isLatest { rms, ok := app.cms.(*rootmulti.Store) if ok { cInfo, err := rms.GetCommitInfo(height) @@ -1280,7 +1320,6 @@ func (app *BaseApp) CreateQueryContext(height int64, prove bool) (sdk.Context, e } } } - return ctx, nil } diff --git a/baseapp/abci_test.go b/baseapp/abci_test.go index fb61080916d..111a8693835 100644 --- a/baseapp/abci_test.go +++ b/baseapp/abci_test.go @@ -676,7 +676,7 @@ func TestABCI_FinalizeBlock_DeliverTx(t *testing.T) { events := res.TxResults[i].GetEvents() require.Len(t, events, 3, "should contain ante handler, message type and counter events respectively") - require.Equal(t, sdk.MarkEventsToIndex(counterEvent("ante_handler", counter).ToABCIEvents(), map[string]struct{}{})[0], events[0], "ante handler event") + require.Equal(t, sdk.MarkEventsToIndex(counterEvent("ante_handler", counter).ToABCIEvents(), map[string]struct{}{})[0].Attributes[0], events[0].Attributes[0], "ante handler event") require.Equal(t, sdk.MarkEventsToIndex(counterEvent(sdk.EventTypeMessage, counter).ToABCIEvents(), map[string]struct{}{})[0].Attributes[0], events[2].Attributes[0], "msg handler update counter event") } diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index b5d100d263a..2ef933c205c 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -718,6 +718,15 @@ func (app *BaseApp) preBlock(req *abci.FinalizeBlockRequest) ([]abci.Event, erro ctx = ctx.WithBlockGasMeter(gasMeter) app.finalizeBlockState.SetContext(ctx) events = ctx.EventManager().ABCIEvents() + + // append PreBlock attributes to all events + for i, event := range events { + events[i].Attributes = append( + event.Attributes, + abci.EventAttribute{Key: "mode", Value: "PreBlock"}, + abci.EventAttribute{Key: "event_index", Value: strconv.Itoa(i)}, + ) + } } return events, nil } @@ -739,6 +748,7 @@ func (app *BaseApp) beginBlock(_ *abci.FinalizeBlockRequest) (sdk.BeginBlock, er resp.Events[i].Attributes = append( event.Attributes, abci.EventAttribute{Key: "mode", Value: "BeginBlock"}, + abci.EventAttribute{Key: "event_index", Value: strconv.Itoa(i)}, ) } @@ -801,6 +811,7 @@ func (app *BaseApp) endBlock(_ context.Context) (sdk.EndBlock, error) { eb.Events[i].Attributes = append( event.Attributes, abci.EventAttribute{Key: "mode", Value: "EndBlock"}, + abci.EventAttribute{Key: "event_index", Value: strconv.Itoa(i)}, ) } @@ -1151,6 +1162,12 @@ func createEvents(cdc codec.Codec, events sdk.Events, msg sdk.Msg, reflectMsg pr } } + // append the event_index attribute to all events + msgEvent = msgEvent.AppendAttributes(sdk.NewAttribute("event_index", "0")) + for i, event := range events { + events[i] = event.AppendAttributes(sdk.NewAttribute("event_index", strconv.Itoa(i+1))) + } + return sdk.Events{msgEvent}.AppendEvents(events), nil } diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 60e474b851e..9ca9669b622 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -696,26 +696,26 @@ func TestBaseAppPostHandler(t *testing.T) { require.NotContains(t, suite.logBuffer.String(), "panic recovered in runTx") } +type mockABCIListener struct { + ListenCommitFn func(context.Context, abci.CommitResponse, []*storetypes.StoreKVPair) error +} + +func (m mockABCIListener) ListenFinalizeBlock(_ context.Context, _ abci.FinalizeBlockRequest, _ abci.FinalizeBlockResponse) error { + return nil +} + +func (m *mockABCIListener) ListenCommit(ctx context.Context, commit abci.CommitResponse, pairs []*storetypes.StoreKVPair) error { + return m.ListenCommitFn(ctx, commit, pairs) +} + // Test and ensure that invalid block heights always cause errors. // See issues: // - https://github.com/cosmos/cosmos-sdk/issues/11220 // - https://github.com/cosmos/cosmos-sdk/issues/7662 func TestABCI_CreateQueryContext(t *testing.T) { t.Parallel() + app := getQueryBaseapp(t) - db := coretesting.NewMemDB() - name := t.Name() - app := baseapp.NewBaseApp(name, log.NewTestLogger(t), db, nil) - - _, err := app.FinalizeBlock(&abci.FinalizeBlockRequest{Height: 1}) - require.NoError(t, err) - _, err = app.Commit() - require.NoError(t, err) - - _, err = app.FinalizeBlock(&abci.FinalizeBlockRequest{Height: 2}) - require.NoError(t, err) - _, err = app.Commit() - require.NoError(t, err) testCases := []struct { name string height int64 @@ -724,7 +724,7 @@ func TestABCI_CreateQueryContext(t *testing.T) { expErr bool }{ {"valid height", 2, 2, true, false}, - {"valid height with different initial height", 2, 1, true, false}, + {"valid height with different initial height", 2, 1, true, true}, {"future height", 10, 10, true, true}, {"negative height, prove=true", -1, -1, true, true}, {"negative height, prove=false", -1, -1, false, true}, @@ -738,7 +738,11 @@ func TestABCI_CreateQueryContext(t *testing.T) { }) require.NoError(t, err) } - ctx, err := app.CreateQueryContext(tc.height, tc.prove) + height := tc.height + if tc.height > tc.headerHeight { + height = 0 + } + ctx, err := app.CreateQueryContext(height, tc.prove) if tc.expErr { require.Error(t, err) } else { @@ -749,6 +753,81 @@ func TestABCI_CreateQueryContext(t *testing.T) { } } +func TestABCI_CreateQueryContextWithCheckHeader(t *testing.T) { + t.Parallel() + app := getQueryBaseapp(t) + var height int64 = 2 + var headerHeight int64 = 1 + + testCases := []struct { + checkHeader bool + expErr bool + }{ + {true, true}, + {false, false}, + } + + for _, tc := range testCases { + t.Run("valid height with different initial height", func(t *testing.T) { + _, err := app.InitChain(&abci.InitChainRequest{ + InitialHeight: headerHeight, + }) + require.NoError(t, err) + ctx, err := app.CreateQueryContextWithCheckHeader(0, true, tc.checkHeader) + if tc.expErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, height, ctx.BlockHeight()) + } + }) + } +} + +func TestABCI_CreateQueryContext_Before_Set_CheckState(t *testing.T) { + t.Parallel() + + db := coretesting.NewMemDB() + name := t.Name() + var height int64 = 2 + var headerHeight int64 = 1 + + t.Run("valid height with different initial height", func(t *testing.T) { + app := baseapp.NewBaseApp(name, log.NewTestLogger(t), db, nil) + + _, err := app.FinalizeBlock(&abci.FinalizeBlockRequest{Height: 1}) + require.NoError(t, err) + _, err = app.Commit() + require.NoError(t, err) + + _, err = app.FinalizeBlock(&abci.FinalizeBlockRequest{Height: 2}) + require.NoError(t, err) + + var queryCtx *sdk.Context + var queryCtxErr error + app.SetStreamingManager(storetypes.StreamingManager{ + ABCIListeners: []storetypes.ABCIListener{ + &mockABCIListener{ + ListenCommitFn: func(context.Context, abci.CommitResponse, []*storetypes.StoreKVPair) error { + qCtx, qErr := app.CreateQueryContext(0, true) + queryCtx = &qCtx + queryCtxErr = qErr + return nil + }, + }, + }, + }) + _, err = app.Commit() + require.NoError(t, err) + require.NoError(t, queryCtxErr) + require.Equal(t, height, queryCtx.BlockHeight()) + _, err = app.InitChain(&abci.InitChainRequest{ + InitialHeight: headerHeight, + }) + require.NoError(t, err) + }) +} + func TestSetMinGasPrices(t *testing.T) { minGasPrices := sdk.DecCoins{sdk.NewInt64DecCoin("stake", 5000)} suite := NewBaseAppSuite(t, baseapp.SetMinGasPrices(minGasPrices.String())) diff --git a/baseapp/streaming.go b/baseapp/streaming.go index b6f40cb87ff..3e30f8888d5 100644 --- a/baseapp/streaming.go +++ b/baseapp/streaming.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "sort" + "strconv" "strings" abci "github.com/cometbft/cometbft/api/cometbft/abci/v1" @@ -143,21 +144,111 @@ func exposeStoreKeysSorted(keysStr []string, keys map[string]*storetypes.KVStore return exposeStoreKeys } +func eventToAppDataEvent(event abci.Event) (appdata.Event, error) { + appdataEvent := appdata.Event{ + Type: event.Type, + Attributes: func() ([]appdata.EventAttribute, error) { + attrs := make([]appdata.EventAttribute, len(event.Attributes)) + for j, attr := range event.Attributes { + attrs[j] = appdata.EventAttribute{ + Key: attr.Key, + Value: attr.Value, + } + } + return attrs, nil + }, + } + + for _, attr := range event.Attributes { + if attr.Key == "mode" { + switch attr.Value { + case "PreBlock": + appdataEvent.BlockStage = appdata.PreBlockStage + case "BeginBlock": + appdataEvent.BlockStage = appdata.BeginBlockStage + case "EndBlock": + appdataEvent.BlockStage = appdata.EndBlockStage + default: + appdataEvent.BlockStage = appdata.UnknownBlockStage + } + } else if attr.Key == "tx_index" { + txIndex, err := strconv.Atoi(attr.Value) + if err != nil { + return appdata.Event{}, err + } + appdataEvent.TxIndex = int32(txIndex + 1) + appdataEvent.BlockStage = appdata.TxProcessingStage + } else if attr.Key == "msg_index" { + msgIndex, err := strconv.Atoi(attr.Value) + if err != nil { + return appdata.Event{}, err + } + appdataEvent.MsgIndex = int32(msgIndex + 1) + } else if attr.Key == "event_index" { + eventIndex, err := strconv.Atoi(attr.Value) + if err != nil { + return appdata.Event{}, err + } + appdataEvent.EventIndex = int32(eventIndex + 1) + } + } + + return appdataEvent, nil +} + type listenerWrapper struct { listener appdata.Listener } +// NewListenerWrapper creates a new listenerWrapper. +// It is only used for testing purposes. +func NewListenerWrapper(listener appdata.Listener) listenerWrapper { + return listenerWrapper{listener: listener} +} + func (p listenerWrapper) ListenFinalizeBlock(_ context.Context, req abci.FinalizeBlockRequest, res abci.FinalizeBlockResponse) error { if p.listener.StartBlock != nil { - err := p.listener.StartBlock(appdata.StartBlockData{ - Height: uint64(req.Height), - }) - if err != nil { + if err := p.listener.StartBlock(appdata.StartBlockData{ + Height: uint64(req.Height), + HeaderBytes: nil, // TODO: https://github.com/cosmos/cosmos-sdk/issues/22009 + HeaderJSON: nil, // TODO: https://github.com/cosmos/cosmos-sdk/issues/22009 + }); err != nil { + return err + } + } + if p.listener.OnTx != nil { + for i, tx := range req.Txs { + if err := p.listener.OnTx(appdata.TxData{ + TxIndex: int32(i), + Bytes: func() ([]byte, error) { return tx, nil }, + JSON: nil, // TODO: https://github.com/cosmos/cosmos-sdk/issues/22009 + }); err != nil { + return err + } + } + } + if p.listener.OnEvent != nil { + events := make([]appdata.Event, len(res.Events)) + var err error + for i, event := range res.Events { + events[i], err = eventToAppDataEvent(event) + if err != nil { + return err + } + } + for _, txResult := range res.TxResults { + for _, event := range txResult.Events { + appdataEvent, err := eventToAppDataEvent(event) + if err != nil { + return err + } + events = append(events, appdataEvent) + } + } + if err := p.listener.OnEvent(appdata.EventData{Events: events}); err != nil { return err } } - - //// TODO txs, events return nil } diff --git a/baseapp/streaming_test.go b/baseapp/streaming_test.go index b0779c6b91c..cb3c065be31 100644 --- a/baseapp/streaming_test.go +++ b/baseapp/streaming_test.go @@ -9,10 +9,12 @@ import ( tmproto "github.com/cometbft/cometbft/api/cometbft/types/v1" "github.com/stretchr/testify/require" + "cosmossdk.io/schema/appdata" storetypes "cosmossdk.io/store/types" "github.com/cosmos/cosmos-sdk/baseapp" baseapptestutil "github.com/cosmos/cosmos-sdk/baseapp/testutil" + sdk "github.com/cosmos/cosmos-sdk/types" ) var _ storetypes.ABCIListener = (*MockABCIListener)(nil) @@ -146,3 +148,186 @@ func Test_Ctx_with_StreamingManager(t *testing.T) { require.NoError(t, err) } } + +type mockAppDataListener struct { + appdata.Listener + + startBlockData []appdata.StartBlockData + txData []appdata.TxData + eventData []appdata.EventData + kvPairData []appdata.KVPairData + commitData []appdata.CommitData +} + +func newMockAppDataListener() *mockAppDataListener { + listener := &mockAppDataListener{} + + // Initialize the Listener with custom behavior to store data + listener.Listener = appdata.Listener{ + StartBlock: func(data appdata.StartBlockData) error { + listener.startBlockData = append(listener.startBlockData, data) // Store StartBlockData + return nil + }, + OnTx: func(data appdata.TxData) error { + listener.txData = append(listener.txData, data) // Store TxData + return nil + }, + OnEvent: func(data appdata.EventData) error { + listener.eventData = append(listener.eventData, data) // Store EventData + return nil + }, + OnKVPair: func(data appdata.KVPairData) error { + listener.kvPairData = append(listener.kvPairData, data) // Store KVPairData + return nil + }, + Commit: func(data appdata.CommitData) (func() error, error) { + listener.commitData = append(listener.commitData, data) // Store CommitData + return nil, nil + }, + } + + return listener +} + +func TestAppDataListener(t *testing.T) { + anteKey := []byte("ante-key") + anteOpt := func(bapp *baseapp.BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey)) } + distOpt := func(bapp *baseapp.BaseApp) { bapp.MountStores(distKey1) } + mockListener := newMockAppDataListener() + streamingManager := storetypes.StreamingManager{ABCIListeners: []storetypes.ABCIListener{baseapp.NewListenerWrapper(mockListener.Listener)}} + streamingManagerOpt := func(bapp *baseapp.BaseApp) { bapp.SetStreamingManager(streamingManager) } + addListenerOpt := func(bapp *baseapp.BaseApp) { bapp.CommitMultiStore().AddListeners([]storetypes.StoreKey{distKey1}) } + + // for event tests + baseappOpts := func(app *baseapp.BaseApp) { + app.SetPreBlocker(func(ctx sdk.Context, req *abci.FinalizeBlockRequest) error { + ctx.EventManager().EmitEvent(sdk.NewEvent("pre-block")) + return nil + }) + app.SetBeginBlocker(func(_ sdk.Context) (sdk.BeginBlock, error) { + return sdk.BeginBlock{ + Events: []abci.Event{ + {Type: "begin-block"}, + }, + }, nil + }) + app.SetEndBlocker(func(_ sdk.Context) (sdk.EndBlock, error) { + return sdk.EndBlock{ + Events: []abci.Event{ + {Type: "end-block"}, + }, + }, nil + }) + } + + suite := NewBaseAppSuite(t, anteOpt, distOpt, streamingManagerOpt, addListenerOpt, baseappOpts) + + _, err := suite.baseApp.InitChain( + &abci.InitChainRequest{ + ConsensusParams: &tmproto.ConsensusParams{}, + }, + ) + require.NoError(t, err) + deliverKey := []byte("deliver-key") + baseapptestutil.RegisterCounterServer(suite.baseApp.MsgServiceRouter(), CounterServerImpl{t, capKey1, deliverKey}) + + txCount := 5 + txs := make([][]byte, txCount) + + for i := 0; i < txCount; i++ { + tx := newTxCounter(t, suite.txConfig, suite.ac, int64(i), int64(i)) + + txBytes, err := suite.txConfig.TxEncoder()(tx) + require.NoError(t, err) + + sKey := []byte(fmt.Sprintf("distKey%d", i)) + sVal := []byte(fmt.Sprintf("distVal%d", i)) + store := getFinalizeBlockStateCtx(suite.baseApp).KVStore(distKey1) + store.Set(sKey, sVal) + + txs[i] = txBytes + } + + _, err = suite.baseApp.FinalizeBlock(&abci.FinalizeBlockRequest{Height: 1, Txs: txs}) + require.NoError(t, err) + _, err = suite.baseApp.Commit() + require.NoError(t, err) + + // StartBlockData + require.Len(t, mockListener.startBlockData, 1) + require.Equal(t, uint64(1), mockListener.startBlockData[0].Height) + // TxData + txData := mockListener.txData + require.Len(t, txData, len(txs)) + for i := 0; i < txCount; i++ { + require.Equal(t, int32(i), txData[i].TxIndex) + txBytes, err := txData[i].Bytes() + require.NoError(t, err) + require.Equal(t, txs[i], txBytes) + } + // KVPairData + require.Len(t, mockListener.kvPairData, 1) + updates := mockListener.kvPairData[0].Updates + for i := 0; i < txCount; i++ { + require.Equal(t, []byte(distKey1.Name()), updates[i].Actor) + require.Len(t, updates[i].StateChanges, 1) + sKey := []byte(fmt.Sprintf("distKey%d", i)) + sVal := []byte(fmt.Sprintf("distVal%d", i)) + require.Equal(t, sKey, updates[i].StateChanges[0].Key) + require.Equal(t, sVal, updates[i].StateChanges[0].Value) + } + // CommitData + require.Len(t, mockListener.commitData, 1) + // EventData + require.Len(t, mockListener.eventData, 1) + events := mockListener.eventData[0].Events + require.Len(t, events, 3+txCount*3) + + for i := 0; i < 3; i++ { + require.Equal(t, int32(0), events[i].TxIndex) + require.Equal(t, int32(0), events[i].MsgIndex) + require.Equal(t, int32(1), events[i].EventIndex) + attrs, err := events[i].Attributes() + require.NoError(t, err) + require.Len(t, attrs, 2) + switch i { + case 0: + require.Equal(t, appdata.PreBlockStage, events[i].BlockStage) + require.Equal(t, "pre-block", events[i].Type) + case 1: + require.Equal(t, appdata.BeginBlockStage, events[i].BlockStage) + require.Equal(t, "begin-block", events[i].Type) + case 2: + require.Equal(t, appdata.EndBlockStage, events[i].BlockStage) + require.Equal(t, "end-block", events[i].Type) + } + } + + for i := 3; i < 3+txCount*3; i++ { + require.Equal(t, appdata.TxProcessingStage, events[i].BlockStage) + require.Equal(t, int32(i/3), events[i].TxIndex) + switch i % 3 { + case 0: + require.Equal(t, "ante_handler", events[i].Type) + require.Equal(t, int32(0), events[i].MsgIndex) + require.Equal(t, int32(0), events[i].EventIndex) + attrs, err := events[i].Attributes() + require.NoError(t, err) + require.Len(t, attrs, 2) + case 1: + require.Equal(t, "message", events[i].Type) + require.Equal(t, int32(1), events[i].MsgIndex) + require.Equal(t, int32(1), events[i].EventIndex) + attrs, err := events[i].Attributes() + require.NoError(t, err) + require.Len(t, attrs, 5) + case 2: + require.Equal(t, "message", events[i].Type) + require.Equal(t, int32(1), events[i].MsgIndex) + require.Equal(t, int32(2), events[i].EventIndex) + attrs, err := events[i].Attributes() + require.NoError(t, err) + require.Len(t, attrs, 4) + } + } +} diff --git a/client/keys/migrate.go b/client/keys/migrate.go index 0a9fc78e214..b2e192dffb9 100644 --- a/client/keys/migrate.go +++ b/client/keys/migrate.go @@ -18,8 +18,6 @@ Otherwise, we try to deserialize it using Amino into LegacyInfo. If this attempt LegacyInfo to Protobuf serialization format and overwrite the keyring entry. If any error occurred, it will be outputted in CLI and migration will be continued until all keys in the keyring DB are exhausted. See https://github.com/cosmos/cosmos-sdk/pull/9695 for more details. - -It is recommended to run in 'dry-run' mode first to verify all key migration material. `, Args: cobra.NoArgs, RunE: runMigrateCmd, diff --git a/client/v2/CHANGELOG.md b/client/v2/CHANGELOG.md index dfd4b8cfb21..8bd7877b448 100644 --- a/client/v2/CHANGELOG.md +++ b/client/v2/CHANGELOG.md @@ -42,6 +42,8 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [#18626](https://github.com/cosmos/cosmos-sdk/pull/18626) Support for off-chain signing and verification of a file. * [#18461](https://github.com/cosmos/cosmos-sdk/pull/18461) Support governance proposals. +* [#20623](https://github.com/cosmos/cosmos-sdk/pull/20623) Introduce client/v2 tx factory. +* [#20623](https://github.com/cosmos/cosmos-sdk/pull/20623) Extend client/v2 keyring interface with `KeyType` and `KeyInfo`. ### Improvements @@ -51,6 +53,10 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [#17709](https://github.com/cosmos/cosmos-sdk/pull/17709) Address codecs have been removed from `autocli.AppOptions` and `flag.Builder`. Instead client/v2 uses the address codecs present in the context (introduced in [#17503](https://github.com/cosmos/cosmos-sdk/pull/17503)). +### Bug Fixes + +* [#21853](https://github.com/cosmos/cosmos-sdk/pull/21853) Fix `*big.Int` unmarshalling in txs. + ## [v2.0.0-beta.5] - 2024-09-18 ### Improvements diff --git a/client/v2/README.md b/client/v2/README.md index 268798233fc..b4c2666850e 100644 --- a/client/v2/README.md +++ b/client/v2/README.md @@ -2,7 +2,9 @@ sidebar_position: 1 --- -# AutoCLI +# Client/v2 + +## AutoCLI :::note Synopsis This document details how to build CLI and REST interfaces for a module. Examples from various Cosmos SDK modules are included. @@ -14,9 +16,9 @@ This document details how to build CLI and REST interfaces for a module. Example ::: -The `autocli` (also known as `client/v2`) package is a [Go library](https://pkg.go.dev/cosmossdk.io/client/v2/autocli) for generating CLI (command line interface) interfaces for Cosmos SDK-based applications. It provides a simple way to add CLI commands to your application by generating them automatically based on your gRPC service definitions. Autocli generates CLI commands and flags directly from your protobuf messages, including options, input parameters, and output parameters. This means that you can easily add a CLI interface to your application without having to manually create and manage commands. +The `autocli` (also known as `client/v2/autocli`) package is a [Go library](https://pkg.go.dev/cosmossdk.io/client/v2/autocli) for generating CLI (command line interface) interfaces for Cosmos SDK-based applications. It provides a simple way to add CLI commands to your application by generating them automatically based on your gRPC service definitions. Autocli generates CLI commands and flags directly from your protobuf messages, including options, input parameters, and output parameters. This means that you can easily add a CLI interface to your application without having to manually create and manage commands. -## Overview +### Overview `autocli` generates CLI commands and flags for each method defined in your gRPC service. By default, it generates commands for each gRPC services. The commands are named based on the name of the service method. @@ -32,7 +34,7 @@ For instance, `autocli` would generate a command named `my-method` for the `MyMe It is possible to customize the generation of transactions and queries by defining options for each service. -## Application Wiring +### Application Wiring Here are the steps to use AutoCLI: @@ -73,7 +75,7 @@ if err := rootCmd.Execute(); err != nil { } ``` -### Keyring +#### Keyring `autocli` uses a keyring for key name resolving names and signing transactions. @@ -100,7 +102,7 @@ keyring.NewAutoCLIKeyring(kb) ::: -## Signing +### Signing `autocli` supports signing transactions with the keyring. The [`cosmos.msg.v1.signer` protobuf annotation](https://docs.cosmos.network/main/build/building-modules/protobuf-annotations) defines the signer field of the message. @@ -110,7 +112,7 @@ This field is automatically filled when using the `--from` flag or defining the AutoCLI currently supports only one signer per transaction. ::: -## Module wiring & Customization +### Module wiring & Customization The `AutoCLIOptions()` method on your module allows to specify custom commands, sub-commands or flags for each service, as it was a `cobra.Command` instance, within the `RpcCommandOptions` struct. Defining such options will customize the behavior of the `autocli` command generation, which by default generates a command for each method in your gRPC service. @@ -131,31 +133,7 @@ AutoCLI can create a gov proposal of any tx by simply setting the `GovProposal` Users can however use the `--no-proposal` flag to disable the proposal creation (which is useful if the authority isn't the gov module on a chain). ::: -### Conventions for the `Use` field in Cobra - -According to the [Cobra documentation](https://pkg.go.dev/github.com/spf13/cobra#Command) the following conventions should be followed for the `Use` field in Cobra commands: - -1. **Required arguments**: - * Should not be enclosed in brackets. They can be enclosed in angle brackets `< >` for clarity. - * Example: `command ` - -2. **Optional arguments**: - * Should be enclosed in square brackets `[ ]`. - * Example: `command [optional_argument]` - -3. **Alternative (mutually exclusive) arguments**: - * Should be enclosed in curly braces `{ }`. - * Example: `command {-a | -b}` for required alternatives. - * Example: `command [-a | -b]` for optional alternatives. - -4. **Multiple arguments**: - * Indicated with `...` after the argument. - * Example: `command argument...` - -5. **Combination of options**: - * Example: `command [-F file | -D dir]... [-f format] profile` - -### Specifying Subcommands +#### Specifying Subcommands By default, `autocli` generates a command for each method in your gRPC service. However, you can specify subcommands to group related commands together. To specify subcommands, use the `autocliv1.ServiceCommandDescriptor` struct. @@ -165,7 +143,7 @@ This example shows how to use the `autocliv1.ServiceCommandDescriptor` struct to https://github.com/cosmos/cosmos-sdk/blob/v0.50.0-beta.0/x/gov/autocli.go#L94-L97 ``` -### Positional Arguments +#### Positional Arguments By default `autocli` generates a flag for each field in your protobuf message. However, you can choose to use positional arguments instead of flags for certain fields. @@ -183,7 +161,7 @@ Then the command can be used as follows, instead of having to specify the `--add query auth account cosmos1abcd...xyz ``` -### Customising Flag Names +#### Customising Flag Names By default, `autocli` generates flag names based on the names of the fields in your protobuf message. However, you can customise the flag names by providing a `FlagOptions`. This parameter allows you to specify custom names for flags based on the names of the message fields. @@ -200,7 +178,7 @@ autocliv1.RpcCommandOptions{ `FlagsOptions` is defined like sub commands in the `AutoCLIOptions()` method on your module. -### Combining AutoCLI with Other Commands Within A Module +#### Combining AutoCLI with Other Commands Within A Module AutoCLI can be used alongside other commands within a module. For example, the `gov` module uses AutoCLI to generate commands for the `query` subcommand, but also defines custom commands for the `proposer` subcommands. @@ -212,7 +190,7 @@ https://github.com/cosmos/cosmos-sdk/blob/fa4d87ef7e6d87aaccc94c337ffd2fe90fcb7a If not set to true, `AutoCLI` will not generate commands for the module if there are already commands registered for the module (when `GetTxCmd()` or `GetQueryCmd()` are defined). -### Skip a command +#### Skip a command AutoCLI automatically skips unsupported commands when [`cosmos_proto.method_added_in` protobuf annotation](https://docs.cosmos.network/main/build/building-modules/protobuf-annotations) is present. @@ -225,7 +203,7 @@ Additionally, a command can be manually skipped using the `autocliv1.RpcCommandO } ``` -### Use AutoCLI for non module commands +#### Use AutoCLI for non module commands It is possible to use `AutoCLI` for non module commands. The trick is still to implement the `appmodule.Module` interface and append it to the `appOptions.ModuleOptions` map. @@ -235,7 +213,31 @@ For example, here is how the SDK does it for `cometbft` gRPC commands: https://github.com/cosmos/cosmos-sdk/blob/main/client/grpc/cmtservice/autocli.go#L52-L71 ``` -## Summary +#### Conventions for the `Use` field in Cobra + +According to the [Cobra documentation](https://pkg.go.dev/github.com/spf13/cobra#Command) the following conventions should be followed for the `Use` field in Cobra commands: + +1. **Required arguments**: + * Should not be enclosed in brackets. They can be enclosed in angle brackets `< >` for clarity. + * Example: `command ` + +2. **Optional arguments**: + * Should be enclosed in square brackets `[ ]`. + * Example: `command [optional_argument]` + +3. **Alternative (mutually exclusive) arguments**: + * Should be enclosed in curly braces `{ }`. + * Example: `command {-a | -b}` for required alternatives. + * Example: `command [-a | -b]` for optional alternatives. + +4. **Multiple arguments**: + * Indicated with `...` after the argument. + * Example: `command argument...` + +5. **Combination of options**: + * Example: `command [-F file | -D dir]... [-f format] profile` + +### Summary `autocli` lets you generate CLI to your Cosmos SDK-based applications without any cobra boilerplate. It allows you to easily generate CLI commands and flags from your protobuf messages, and provides many options for customising the behavior of your CLI application. @@ -245,7 +247,7 @@ For more information on `hubl`, including how to configure a new chain and query # Off-Chain -Off-chain functionalities allow you to sign and verify files with two commands: +Off-chain is a `client/v2` package providing functionalities for allowing to sign and verify files with two commands: * `sign-file` for signing a file. * `verify-file` for verifying a previously signed file. @@ -275,6 +277,7 @@ The `encoding` flag lets you choose how the contents of the file should be encod "signer": "cosmos1x33fy6rusfprkntvjsfregss7rvsvyy4lkwrqu", "data": "Hello World!\n" } + ``` * `simd off-chain sign-file alice myFile.json --encoding base64` @@ -286,6 +289,7 @@ The `encoding` flag lets you choose how the contents of the file should be encod "signer": "cosmos1x33fy6rusfprkntvjsfregss7rvsvyy4lkwrqu", "data": "SGVsbG8gV29ybGQhCg==" } + ``` * `simd off-chain sign-file alice myFile.json --encoding hex` diff --git a/client/v2/autocli/flag/address.go b/client/v2/autocli/flag/address.go index 58108d09499..454c30a317d 100644 --- a/client/v2/autocli/flag/address.go +++ b/client/v2/autocli/flag/address.go @@ -151,7 +151,7 @@ func getKeyringFromCtx(ctx *context.Context) keyring.Keyring { dctx := *ctx if dctx != nil { if clientCtx := dctx.Value(client.ClientContextKey); clientCtx != nil { - k, err := sdkkeyring.NewAutoCLIKeyring(clientCtx.(*client.Context).Keyring) + k, err := sdkkeyring.NewAutoCLIKeyring(clientCtx.(*client.Context).Keyring, clientCtx.(*client.Context).AddressCodec) if err != nil { panic(fmt.Errorf("failed to create keyring: %w", err)) } diff --git a/client/v2/autocli/flag/builder.go b/client/v2/autocli/flag/builder.go index 6ff325c53bd..bdd42634dd3 100644 --- a/client/v2/autocli/flag/builder.go +++ b/client/v2/autocli/flag/builder.go @@ -26,6 +26,7 @@ const ( ValidatorAddressStringScalarType = "cosmos.ValidatorAddressString" ConsensusAddressStringScalarType = "cosmos.ConsensusAddressString" PubkeyScalarType = "cosmos.Pubkey" + DecScalarType = "cosmos.Dec" ) // Builder manages options for building pflag flags for protobuf messages. @@ -67,6 +68,7 @@ func (b *Builder) init() { b.scalarFlagTypes[ValidatorAddressStringScalarType] = validatorAddressStringType{} b.scalarFlagTypes[ConsensusAddressStringScalarType] = consensusAddressStringType{} b.scalarFlagTypes[PubkeyScalarType] = pubkeyType{} + b.scalarFlagTypes[DecScalarType] = decType{} } } diff --git a/client/v2/autocli/flag/legacy_dec.go b/client/v2/autocli/flag/legacy_dec.go new file mode 100644 index 00000000000..073afa94f1f --- /dev/null +++ b/client/v2/autocli/flag/legacy_dec.go @@ -0,0 +1,48 @@ +package flag + +import ( + "context" + + "google.golang.org/protobuf/reflect/protoreflect" + + "cosmossdk.io/math" +) + +type decType struct{} + +func (a decType) NewValue(_ *context.Context, _ *Builder) Value { + return &decValue{} +} + +func (a decType) DefaultValue() string { + return "0" +} + +type decValue struct { + value string +} + +func (a decValue) Get(protoreflect.Value) (protoreflect.Value, error) { + return protoreflect.ValueOf(a.value), nil +} + +func (a decValue) String() string { + return a.value +} + +func (a *decValue) Set(s string) error { + dec, err := math.LegacyNewDecFromStr(s) + if err != nil { + return err + } + + // we need to convert from float representation to non-float representation using default precision + // 0.5 -> 500000000000000000 + a.value = dec.BigInt().String() + + return nil +} + +func (a decValue) Type() string { + return "cosmos.Dec" +} diff --git a/client/v2/autocli/keyring/interface.go b/client/v2/autocli/keyring/interface.go index fa448bd2059..7f2fee1b3e3 100644 --- a/client/v2/autocli/keyring/interface.go +++ b/client/v2/autocli/keyring/interface.go @@ -20,4 +20,10 @@ type Keyring interface { // Sign signs the given bytes with the key with the given name. Sign(name string, msg []byte, signMode signingv1beta1.SignMode) ([]byte, error) + + // KeyType returns the type of the key. + KeyType(name string) (uint, error) + + // KeyInfo given a key name or address returns key name, key address and key type. + KeyInfo(nameOrAddr string) (string, string, uint, error) } diff --git a/client/v2/autocli/keyring/keyring.go b/client/v2/autocli/keyring/keyring.go index 70c2d27d08e..f5dce25efce 100644 --- a/client/v2/autocli/keyring/keyring.go +++ b/client/v2/autocli/keyring/keyring.go @@ -48,3 +48,13 @@ func (k *KeyringImpl) LookupAddressByKeyName(name string) ([]byte, error) { func (k *KeyringImpl) Sign(name string, msg []byte, signMode signingv1beta1.SignMode) ([]byte, error) { return k.k.Sign(name, msg, signMode) } + +// KeyType returns the type of the key. +func (k *KeyringImpl) KeyType(name string) (uint, error) { + return k.k.KeyType(name) +} + +// KeyInfo given a key name or address returns key name, key address and key type. +func (k *KeyringImpl) KeyInfo(nameOrAddr string) (string, string, uint, error) { + return k.k.KeyInfo(nameOrAddr) +} diff --git a/client/v2/autocli/keyring/no_keyring.go b/client/v2/autocli/keyring/no_keyring.go index e14267cee5e..7f0be9c7593 100644 --- a/client/v2/autocli/keyring/no_keyring.go +++ b/client/v2/autocli/keyring/no_keyring.go @@ -29,3 +29,11 @@ func (k NoKeyring) GetPubKey(name string) (cryptotypes.PubKey, error) { func (k NoKeyring) Sign(name string, msg []byte, signMode signingv1beta1.SignMode) ([]byte, error) { return nil, errNoKeyring } + +func (k NoKeyring) KeyType(name string) (uint, error) { + return 0, errNoKeyring +} + +func (k NoKeyring) KeyInfo(name string) (string, string, uint, error) { + return "", "", 0, errNoKeyring +} diff --git a/client/v2/go.mod b/client/v2/go.mod index 8a9d7bd03c1..6b1bbc12f80 100644 --- a/client/v2/go.mod +++ b/client/v2/go.mod @@ -14,7 +14,7 @@ require ( github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 google.golang.org/grpc v1.67.1 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 gotest.tools/v3 v3.5.1 sigs.k8s.io/yaml v1.4.0 ) @@ -53,7 +53,7 @@ require ( github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/cosmos-db v1.0.3-0.20240911104526-ddc3f09bfc22 // indirect github.com/cosmos/crypto v0.1.2 // indirect - github.com/cosmos/go-bip39 v1.0.0 // indirect + github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/gogogateway v1.2.0 // indirect github.com/cosmos/gogoproto v1.7.0 github.com/cosmos/iavl v1.3.0 // indirect @@ -153,14 +153,14 @@ require ( go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect diff --git a/client/v2/go.sum b/client/v2/go.sum index 1a32ea6bc6f..0fed5256394 100644 --- a/client/v2/go.sum +++ b/client/v2/go.sum @@ -523,8 +523,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -607,19 +607,19 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -679,8 +679,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/client/v2/internal/account/retriever.go b/client/v2/internal/account/retriever.go new file mode 100644 index 00000000000..2cef69f92cf --- /dev/null +++ b/client/v2/internal/account/retriever.go @@ -0,0 +1,116 @@ +package account + +import ( + "context" + "fmt" + "strconv" + + gogogrpc "github.com/cosmos/gogoproto/grpc" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + + "cosmossdk.io/core/address" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +// GRPCBlockHeightHeader represents the gRPC header for block height. +const GRPCBlockHeightHeader = "x-cosmos-block-height" + +var _ AccountRetriever = accountRetriever{} + +// Account provides a read-only abstraction over the auth module's AccountI. +type Account interface { + GetAddress() sdk.AccAddress + GetPubKey() cryptotypes.PubKey // can return nil. + GetAccountNumber() uint64 + GetSequence() uint64 +} + +// AccountRetriever defines methods required to retrieve account details necessary for transaction signing. +type AccountRetriever interface { + GetAccount(context.Context, []byte) (Account, error) + GetAccountWithHeight(context.Context, []byte) (Account, int64, error) + EnsureExists(context.Context, []byte) error + GetAccountNumberSequence(context.Context, []byte) (accNum, accSeq uint64, err error) +} + +type accountRetriever struct { + ac address.Codec + conn gogogrpc.ClientConn + registry codectypes.InterfaceRegistry +} + +// NewAccountRetriever creates a new instance of accountRetriever. +func NewAccountRetriever(ac address.Codec, conn gogogrpc.ClientConn, registry codectypes.InterfaceRegistry) *accountRetriever { + return &accountRetriever{ + ac: ac, + conn: conn, + registry: registry, + } +} + +// GetAccount retrieves an account using its address. +func (a accountRetriever) GetAccount(ctx context.Context, addr []byte) (Account, error) { + acc, _, err := a.GetAccountWithHeight(ctx, addr) + return acc, err +} + +// GetAccountWithHeight retrieves an account and its associated block height using the account's address. +func (a accountRetriever) GetAccountWithHeight(ctx context.Context, addr []byte) (Account, int64, error) { + var header metadata.MD + qc := authtypes.NewQueryClient(a.conn) + + addrStr, err := a.ac.BytesToString(addr) + if err != nil { + return nil, 0, err + } + + res, err := qc.Account(ctx, &authtypes.QueryAccountRequest{Address: addrStr}, grpc.Header(&header)) + if err != nil { + return nil, 0, err + } + + blockHeight := header.Get(GRPCBlockHeightHeader) + if len(blockHeight) != 1 { + return nil, 0, fmt.Errorf("unexpected '%s' header length; got %d, expected 1", GRPCBlockHeightHeader, len(blockHeight)) + } + + nBlockHeight, err := strconv.Atoi(blockHeight[0]) + if err != nil { + return nil, 0, fmt.Errorf("failed to parse block height: %w", err) + } + + var acc Account + if err := a.registry.UnpackAny(res.Account, &acc); err != nil { + return nil, 0, err + } + + return acc, int64(nBlockHeight), nil +} + +// EnsureExists checks if an account exists using its address. +func (a accountRetriever) EnsureExists(ctx context.Context, addr []byte) error { + if _, err := a.GetAccount(ctx, addr); err != nil { + return err + } + return nil +} + +// GetAccountNumberSequence retrieves the account number and sequence for an account using its address. +func (a accountRetriever) GetAccountNumberSequence(ctx context.Context, addr []byte) (accNum, accSeq uint64, err error) { + acc, err := a.GetAccount(ctx, addr) + if err != nil { + if status.Code(err) == codes.NotFound { + return 0, 0, nil + } + return 0, 0, err + } + + return acc.GetAccountNumber(), acc.GetSequence(), nil +} diff --git a/client/v2/internal/coins/util.go b/client/v2/internal/coins/util.go new file mode 100644 index 00000000000..1495386713f --- /dev/null +++ b/client/v2/internal/coins/util.go @@ -0,0 +1,66 @@ +package coins + +import ( + "errors" + + base "cosmossdk.io/api/cosmos/base/v1beta1" + "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var ( + _ withAmount = &base.Coin{} + _ withAmount = &base.DecCoin{} +) + +type withAmount interface { + GetAmount() string +} + +// IsZero check if given coins are zero. +func IsZero[T withAmount](coins []T) (bool, error) { + for _, coin := range coins { + amount, ok := math.NewIntFromString(coin.GetAmount()) + if !ok { + return false, errors.New("invalid coin amount") + } + if !amount.IsZero() { + return false, nil + } + } + return true, nil +} + +func ParseDecCoins(coins string) ([]*base.DecCoin, error) { + parsedGasPrices, err := sdk.ParseDecCoins(coins) // TODO: do it here to avoid sdk dependency + if err != nil { + return nil, err + } + + finalGasPrices := make([]*base.DecCoin, len(parsedGasPrices)) + for i, coin := range parsedGasPrices { + finalGasPrices[i] = &base.DecCoin{ + Denom: coin.Denom, + Amount: coin.Amount.String(), + } + } + return finalGasPrices, nil +} + +func ParseCoinsNormalized(coins string) ([]*base.Coin, error) { + parsedFees, err := sdk.ParseCoinsNormalized(coins) // TODO: do it here to avoid sdk dependency + if err != nil { + return nil, err + } + + finalFees := make([]*base.Coin, len(parsedFees)) + for i, coin := range parsedFees { + finalFees[i] = &base.Coin{ + Denom: coin.Denom, + Amount: coin.Amount.String(), + } + } + + return finalFees, nil +} diff --git a/client/v2/internal/coins/util_test.go b/client/v2/internal/coins/util_test.go new file mode 100644 index 00000000000..1ee7f584292 --- /dev/null +++ b/client/v2/internal/coins/util_test.go @@ -0,0 +1,83 @@ +package coins + +import ( + "testing" + + "github.com/stretchr/testify/require" + + base "cosmossdk.io/api/cosmos/base/v1beta1" +) + +func TestCoinIsZero(t *testing.T) { + type testCase[T withAmount] struct { + name string + coins []T + isZero bool + } + tests := []testCase[*base.Coin]{ + { + name: "not zero coin", + coins: []*base.Coin{ + { + Denom: "stake", + Amount: "100", + }, + }, + isZero: false, + }, + { + name: "zero coin", + coins: []*base.Coin{ + { + Denom: "stake", + Amount: "0", + }, + }, + isZero: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := IsZero(tt.coins) + require.NoError(t, err) + require.Equal(t, got, tt.isZero) + }) + } +} + +func TestDecCoinIsZero(t *testing.T) { + type testCase[T withAmount] struct { + name string + coins []T + isZero bool + } + tests := []testCase[*base.DecCoin]{ + { + name: "not zero coin", + coins: []*base.DecCoin{ + { + Denom: "stake", + Amount: "100", + }, + }, + isZero: false, + }, + { + name: "zero coin", + coins: []*base.DecCoin{ + { + Denom: "stake", + Amount: "0", + }, + }, + isZero: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := IsZero(tt.coins) + require.NoError(t, err) + require.Equal(t, got, tt.isZero) + }) + } +} diff --git a/client/v2/offchain/sign.go b/client/v2/offchain/sign.go index fd01227ed13..b36a50c00ca 100644 --- a/client/v2/offchain/sign.go +++ b/client/v2/offchain/sign.go @@ -63,7 +63,7 @@ func Sign(ctx client.Context, rawBytes []byte, fromName, indent, encoding, outpu // sign signs a digest with provided key and SignMode. func sign(ctx client.Context, fromName, digest string) (*apitx.Tx, error) { - keybase, err := keyring.NewAutoCLIKeyring(ctx.Keyring) + keybase, err := keyring.NewAutoCLIKeyring(ctx.Keyring, ctx.AddressCodec) if err != nil { return nil, err } diff --git a/client/v2/tx/README.md b/client/v2/tx/README.md new file mode 100644 index 00000000000..ffe2fb8fda0 --- /dev/null +++ b/client/v2/tx/README.md @@ -0,0 +1,465 @@ +The tx package provides a robust set of tools for building, signing, and managing transactions in a Cosmos SDK-based blockchain application. + +## Overview + +This package includes several key components: + +1. Transaction Factory +2. Transaction Config +3. Transaction Encoder/Decoder +4. Signature Handling + +## Architecture + +```mermaid +graph TD + A[Client] --> B[Factory] + B --> D[TxConfig] + D --> E[TxEncodingConfig] + D --> F[TxSigningConfig] + B --> G[Tx] + G --> H[Encoder] + G --> I[Decoder] + F --> J[SignModeHandler] + F --> K[SigningContext] + B --> L[AuxTxBuilder] +``` + +## Key Components + +### TxConfig + +`TxConfig` provides configuration for transaction handling, including: + +- Encoding and decoding +- Sign mode handling +- Signature JSON marshaling/unmarshaling + +```mermaid +classDiagram + class TxConfig { + <> + TxEncodingConfig + TxSigningConfig + } + + class TxEncodingConfig { + <> + TxEncoder() txEncoder + TxDecoder() txDecoder + TxJSONEncoder() txEncoder + TxJSONDecoder() txDecoder + Decoder() Decoder + } + + class TxSigningConfig { + <> + SignModeHandler() *signing.HandlerMap + SigningContext() *signing.Context + MarshalSignatureJSON([]Signature) ([]byte, error) + UnmarshalSignatureJSON([]byte) ([]Signature, error) + } + + class txConfig { + TxEncodingConfig + TxSigningConfig + } + + class defaultEncodingConfig { + cdc codec.BinaryCodec + decoder Decoder + TxEncoder() txEncoder + TxDecoder() txDecoder + TxJSONEncoder() txEncoder + TxJSONDecoder() txDecoder + } + + class defaultTxSigningConfig { + signingCtx *signing.Context + handlerMap *signing.HandlerMap + cdc codec.BinaryCodec + SignModeHandler() *signing.HandlerMap + SigningContext() *signing.Context + MarshalSignatureJSON([]Signature) ([]byte, error) + UnmarshalSignatureJSON([]byte) ([]Signature, error) + } + + TxConfig <|-- txConfig + TxEncodingConfig <|.. defaultEncodingConfig + TxSigningConfig <|.. defaultTxSigningConfig + txConfig *-- defaultEncodingConfig + txConfig *-- defaultTxSigningConfig +``` + +### Factory + +The `Factory` is the main entry point for creating and managing transactions. It handles: + +- Account preparation +- Gas calculation +- Unsigned transaction building +- Transaction signing +- Transaction simulation +- Transaction broadcasting + +```mermaid +classDiagram + class Factory { + keybase keyring.Keyring + cdc codec.BinaryCodec + accountRetriever account.AccountRetriever + ac address.Codec + conn gogogrpc.ClientConn + txConfig TxConfig + txParams TxParameters + tx txState + + NewFactory(keybase, cdc, accRetriever, txConfig, ac, conn, parameters) Factory + Prepare() error + BuildUnsignedTx(msgs ...transaction.Msg) error + BuildsSignedTx(ctx context.Context, msgs ...transaction.Msg) (Tx, error) + calculateGas(msgs ...transaction.Msg) error + Simulate(msgs ...transaction.Msg) (*apitx.SimulateResponse, uint64, error) + UnsignedTxString(msgs ...transaction.Msg) (string, error) + BuildSimTx(msgs ...transaction.Msg) ([]byte, error) + sign(ctx context.Context, overwriteSig bool) (Tx, error) + WithGas(gas uint64) + WithSequence(sequence uint64) + WithAccountNumber(accnum uint64) + getTx() (Tx, error) + getFee() (*apitx.Fee, error) + getSigningTxData() (signing.TxData, error) + setSignatures(...Signature) error + } + + class TxParameters { + <> + chainID string + AccountConfig + GasConfig + FeeConfig + SignModeConfig + TimeoutConfig + MemoConfig + } + + class TxConfig { + <> + } + + class Tx { + <> + } + + class txState { + <> + msgs []transaction.Msg + memo string + fees []*base.Coin + gasLimit uint64 + feeGranter []byte + feePayer []byte + timeoutHeight uint64 + unordered bool + timeoutTimestamp uint64 + signatures []Signature + signerInfos []*apitx.SignerInfo + } + + Factory *-- TxParameters + Factory *-- TxConfig + Factory *-- txState + Factory ..> Tx : creates +``` + +### Encoder/Decoder + +The package includes functions for encoding and decoding transactions in both binary and JSON formats. + +```mermaid +classDiagram + class Decoder { + <> + Decode(txBytes []byte) (*txdecode.DecodedTx, error) + } + + class txDecoder { + <> + decode(txBytes []byte) (Tx, error) + } + + class txEncoder { + <> + encode(tx Tx) ([]byte, error) + } + + class EncoderUtils { + <> + decodeTx(cdc codec.BinaryCodec, decoder Decoder) txDecoder + encodeTx(tx Tx) ([]byte, error) + decodeJsonTx(cdc codec.BinaryCodec, decoder Decoder) txDecoder + encodeJsonTx(tx Tx) ([]byte, error) + protoTxBytes(tx *txv1beta1.Tx) ([]byte, error) + } + + class MarshalOptions { + <> + Deterministic bool + } + + class JSONMarshalOptions { + <> + Indent string + UseProtoNames bool + UseEnumNumbers bool + } + + Decoder <.. EncoderUtils : uses + txDecoder <.. EncoderUtils : creates + txEncoder <.. EncoderUtils : implements + EncoderUtils ..> MarshalOptions : uses + EncoderUtils ..> JSONMarshalOptions : uses +``` + +### Sequence Diagrams + +#### Generate Aux Signer Data +```mermaid +sequenceDiagram + participant User + participant GenerateOrBroadcastTxCLI + participant generateAuxSignerData + participant makeAuxSignerData + participant AuxTxBuilder + participant ctx.PrintProto + + User->>GenerateOrBroadcastTxCLI: Call with isAux flag + GenerateOrBroadcastTxCLI->>generateAuxSignerData: Call + + generateAuxSignerData->>makeAuxSignerData: Call + makeAuxSignerData->>AuxTxBuilder: NewAuxTxBuilder() + + makeAuxSignerData->>AuxTxBuilder: SetAddress(f.txParams.fromAddress) + + alt f.txParams.offline + makeAuxSignerData->>AuxTxBuilder: SetAccountNumber(f.AccountNumber()) + makeAuxSignerData->>AuxTxBuilder: SetSequence(f.Sequence()) + else + makeAuxSignerData->>f.accountRetriever: GetAccountNumberSequence() + makeAuxSignerData->>AuxTxBuilder: SetAccountNumber(accNum) + makeAuxSignerData->>AuxTxBuilder: SetSequence(seq) + end + + makeAuxSignerData->>AuxTxBuilder: SetMsgs(msgs...) + makeAuxSignerData->>AuxTxBuilder: SetSignMode(f.SignMode()) + + makeAuxSignerData->>f.keybase: GetPubKey(f.txParams.fromName) + makeAuxSignerData->>AuxTxBuilder: SetPubKey(pubKey) + + makeAuxSignerData->>AuxTxBuilder: SetChainID(f.txParams.chainID) + makeAuxSignerData->>AuxTxBuilder: GetSignBytes() + + makeAuxSignerData->>f.keybase: Sign(f.txParams.fromName, signBz, f.SignMode()) + makeAuxSignerData->>AuxTxBuilder: SetSignature(sig) + + makeAuxSignerData->>AuxTxBuilder: GetAuxSignerData() + AuxTxBuilder-->>makeAuxSignerData: Return AuxSignerData + makeAuxSignerData-->>generateAuxSignerData: Return AuxSignerData + + generateAuxSignerData->>ctx.PrintProto: Print AuxSignerData + ctx.PrintProto-->>GenerateOrBroadcastTxCLI: Return result + GenerateOrBroadcastTxCLI-->>User: Return result +``` + +#### Generate Only +```mermaid +sequenceDiagram + participant User + participant GenerateOrBroadcastTxCLI + participant generateOnly + participant Factory + participant ctx.PrintString + + User->>GenerateOrBroadcastTxCLI: Call with generateOnly flag + GenerateOrBroadcastTxCLI->>generateOnly: Call + + generateOnly->>Factory: Prepare() + alt Error in Prepare + Factory-->>generateOnly: Return error + generateOnly-->>GenerateOrBroadcastTxCLI: Return error + GenerateOrBroadcastTxCLI-->>User: Return error + end + + generateOnly->>Factory: UnsignedTxString(msgs...) + Factory->>Factory: BuildUnsignedTx(msgs...) + Factory->>Factory: setMsgs(msgs...) + Factory->>Factory: setMemo(f.txParams.memo) + Factory->>Factory: setFees(f.txParams.gasPrices) + Factory->>Factory: setGasLimit(f.txParams.gas) + Factory->>Factory: setFeeGranter(f.txParams.feeGranter) + Factory->>Factory: setFeePayer(f.txParams.feePayer) + Factory->>Factory: setTimeoutHeight(f.txParams.timeoutHeight) + + Factory->>Factory: getTx() + Factory->>Factory: txConfig.TxJSONEncoder() + Factory->>Factory: encoder(tx) + + Factory-->>generateOnly: Return unsigned tx string + generateOnly->>ctx.PrintString: Print unsigned tx string + ctx.PrintString-->>generateOnly: Return result + generateOnly-->>GenerateOrBroadcastTxCLI: Return result + GenerateOrBroadcastTxCLI-->>User: Return result +``` + +#### DryRun +```mermaid +sequenceDiagram + participant User + participant GenerateOrBroadcastTxCLI + participant dryRun + participant Factory + participant os.Stderr + + User->>GenerateOrBroadcastTxCLI: Call with dryRun flag + GenerateOrBroadcastTxCLI->>dryRun: Call + + dryRun->>Factory: Prepare() + alt Error in Prepare + Factory-->>dryRun: Return error + dryRun-->>GenerateOrBroadcastTxCLI: Return error + GenerateOrBroadcastTxCLI-->>User: Return error + end + + dryRun->>Factory: Simulate(msgs...) + Factory->>Factory: BuildSimTx(msgs...) + Factory->>Factory: BuildUnsignedTx(msgs...) + Factory->>Factory: getSimPK() + Factory->>Factory: getSimSignatureData(pk) + Factory->>Factory: setSignatures(sig) + Factory->>Factory: getTx() + Factory->>Factory: txConfig.TxEncoder()(tx) + + Factory->>ServiceClient: Simulate(context.Background(), &apitx.SimulateRequest{}) + ServiceClient->>Factory: Return result + + Factory-->>dryRun: Return (simulation, gas, error) + alt Error in Simulate + dryRun-->>GenerateOrBroadcastTxCLI: Return error + GenerateOrBroadcastTxCLI-->>User: Return error + end + + dryRun->>os.Stderr: Fprintf(GasEstimateResponse{GasEstimate: gas}) + os.Stderr-->>dryRun: Return result + dryRun-->>GenerateOrBroadcastTxCLI: Return result + GenerateOrBroadcastTxCLI-->>User: Return result +``` + +#### Generate and Broadcast Tx +```mermaid +sequenceDiagram + participant User + participant GenerateOrBroadcastTxCLI + participant BroadcastTx + participant Factory + participant clientCtx + + User->>GenerateOrBroadcastTxCLI: Call + GenerateOrBroadcastTxCLI->>BroadcastTx: Call + + BroadcastTx->>Factory: Prepare() + alt Error in Prepare + Factory-->>BroadcastTx: Return error + BroadcastTx-->>GenerateOrBroadcastTxCLI: Return error + GenerateOrBroadcastTxCLI-->>User: Return error + end + + alt SimulateAndExecute is true + BroadcastTx->>Factory: calculateGas(msgs...) + Factory->>Factory: Simulate(msgs...) + Factory->>Factory: WithGas(adjusted) + end + + BroadcastTx->>Factory: BuildUnsignedTx(msgs...) + Factory->>Factory: setMsgs(msgs...) + Factory->>Factory: setMemo(f.txParams.memo) + Factory->>Factory: setFees(f.txParams.gasPrices) + Factory->>Factory: setGasLimit(f.txParams.gas) + Factory->>Factory: setFeeGranter(f.txParams.feeGranter) + Factory->>Factory: setFeePayer(f.txParams.feePayer) + Factory->>Factory: setTimeoutHeight(f.txParams.timeoutHeight) + + alt !clientCtx.SkipConfirm + BroadcastTx->>Factory: getTx() + BroadcastTx->>Factory: txConfig.TxJSONEncoder() + BroadcastTx->>clientCtx: PrintRaw(txBytes) + BroadcastTx->>clientCtx: Input.GetConfirmation() + alt Not confirmed + BroadcastTx-->>GenerateOrBroadcastTxCLI: Return error + GenerateOrBroadcastTxCLI-->>User: Return error + end + end + + BroadcastTx->>Factory: BuildsSignedTx(ctx, msgs...) + Factory->>Factory: sign(ctx, true) + Factory->>Factory: keybase.GetPubKey(fromName) + Factory->>Factory: getSignBytesAdapter() + Factory->>Factory: keybase.Sign(fromName, bytesToSign, signMode) + Factory->>Factory: setSignatures(sig) + Factory->>Factory: getTx() + + BroadcastTx->>Factory: txConfig.TxEncoder() + BroadcastTx->>clientCtx: BroadcastTx(txBytes) + + alt Error in BroadcastTx + clientCtx-->>BroadcastTx: Return error + BroadcastTx-->>GenerateOrBroadcastTxCLI: Return error + GenerateOrBroadcastTxCLI-->>User: Return error + end + + BroadcastTx->>clientCtx: OutputTx(res) + clientCtx-->>BroadcastTx: Return result + BroadcastTx-->>GenerateOrBroadcastTxCLI: Return result + GenerateOrBroadcastTxCLI-->>User: Return result +``` + +## Usage + +To use the `tx` package, typically you would: + +1. Create a `Factory` +2. Simulate the transaction (optional) +3. Build a signed transaction +4. Encode the transaction +5. Broadcast the transaction + +Here's a simplified example: + +```go +// Create a Factory +factory, err := NewFactory(keybase, cdc, accountRetriever, txConfig, addressCodec, conn, txParameters) +if err != nil { + return err +} + +// Simulate the transaction (optional) +simRes, gas, err := factory.Simulate(msgs...) +if err != nil { + return err +} +factory.WithGas(gas) + +// Build a signed transaction +signedTx, err := factory.BuildsSignedTx(context.Background(), msgs...) +if err != nil { + return err +} + +// Encode the transaction +txBytes, err := factory.txConfig.TxEncoder()(signedTx) +if err != nil { + return err +} + +// Broadcast the transaction +// (This step depends on your specific client implementation) +``` \ No newline at end of file diff --git a/client/v2/tx/common_test.go b/client/v2/tx/common_test.go new file mode 100644 index 00000000000..3b474e9fef7 --- /dev/null +++ b/client/v2/tx/common_test.go @@ -0,0 +1,116 @@ +package tx + +import ( + "context" + + "google.golang.org/grpc" + + abciv1beta1 "cosmossdk.io/api/cosmos/base/abci/v1beta1" + apitx "cosmossdk.io/api/cosmos/tx/v1beta1" + "cosmossdk.io/client/v2/autocli/keyring" + "cosmossdk.io/client/v2/internal/account" + txdecode "cosmossdk.io/x/tx/decode" + "cosmossdk.io/x/tx/signing" + + "github.com/cosmos/cosmos-sdk/codec" + addrcodec "github.com/cosmos/cosmos-sdk/codec/address" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + codec2 "github.com/cosmos/cosmos-sdk/crypto/codec" + "github.com/cosmos/cosmos-sdk/crypto/hd" + cryptoKeyring "github.com/cosmos/cosmos-sdk/crypto/keyring" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/types" +) + +var ( + cdc = codec.NewProtoCodec(codectypes.NewInterfaceRegistry()) + ac = addrcodec.NewBech32Codec("cosmos") + valCodec = addrcodec.NewBech32Codec("cosmosval") + signingOptions = signing.Options{ + AddressCodec: ac, + ValidatorAddressCodec: valCodec, + } + signingContext, _ = signing.NewContext(signingOptions) + decodeOptions = txdecode.Options{SigningContext: signingContext, ProtoCodec: cdc} + decoder, _ = txdecode.NewDecoder(decodeOptions) + + k = cryptoKeyring.NewInMemory(cdc) + keybase, _ = cryptoKeyring.NewAutoCLIKeyring(k, ac) + txConf, _ = NewTxConfig(ConfigOptions{ + AddressCodec: ac, + Cdc: cdc, + ValidatorAddressCodec: valCodec, + }) +) + +func setKeyring() keyring.Keyring { + registry := codectypes.NewInterfaceRegistry() + codec2.RegisterInterfaces(registry) + cdc := codec.NewProtoCodec(registry) + k := cryptoKeyring.NewInMemory(cdc) + _, err := k.NewAccount("alice", "equip will roof matter pink blind book anxiety banner elbow sun young", "", "m/44'/118'/0'/0/0", hd.Secp256k1) + if err != nil { + panic(err) + } + keybase, err := cryptoKeyring.NewAutoCLIKeyring(k, ac) + if err != nil { + panic(err) + } + return keybase +} + +type mockAccount struct { + addr []byte +} + +func (m mockAccount) GetAddress() types.AccAddress { + return m.addr +} + +func (m mockAccount) GetPubKey() cryptotypes.PubKey { + return nil +} + +func (m mockAccount) GetAccountNumber() uint64 { + return 1 +} + +func (m mockAccount) GetSequence() uint64 { + return 0 +} + +type mockAccountRetriever struct{} + +func (m mockAccountRetriever) GetAccount(_ context.Context, address []byte) (account.Account, error) { + return mockAccount{addr: address}, nil +} + +func (m mockAccountRetriever) GetAccountWithHeight(_ context.Context, address []byte) (account.Account, int64, error) { + return mockAccount{addr: address}, 0, nil +} + +func (m mockAccountRetriever) EnsureExists(_ context.Context, _ []byte) error { + return nil +} + +func (m mockAccountRetriever) GetAccountNumberSequence(_ context.Context, _ []byte) (accNum, accSeq uint64, err error) { + return accNum, accSeq, nil +} + +type mockClientConn struct{} + +func (m mockClientConn) Invoke(_ context.Context, _ string, _, reply interface{}, _ ...grpc.CallOption) error { + simResponse := apitx.SimulateResponse{ + GasInfo: &abciv1beta1.GasInfo{ + GasWanted: 10000, + GasUsed: 7500, + }, + Result: nil, + } + *reply.(*apitx.SimulateResponse) = simResponse // nolint:govet // ignore linting error + return nil +} + +func (m mockClientConn) NewStream(_ context.Context, _ *grpc.StreamDesc, _ string, _ ...grpc.CallOption) (grpc.ClientStream, error) { + return nil, nil +} diff --git a/client/v2/tx/config.go b/client/v2/tx/config.go new file mode 100644 index 00000000000..a500f7c9b00 --- /dev/null +++ b/client/v2/tx/config.go @@ -0,0 +1,338 @@ +package tx + +import ( + "errors" + + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/known/anypb" + + apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + "cosmossdk.io/core/address" + txdecode "cosmossdk.io/x/tx/decode" + "cosmossdk.io/x/tx/signing" + "cosmossdk.io/x/tx/signing/aminojson" + "cosmossdk.io/x/tx/signing/direct" + "cosmossdk.io/x/tx/signing/directaux" + "cosmossdk.io/x/tx/signing/textual" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" +) + +var ( + _ TxConfig = txConfig{} + _ TxEncodingConfig = defaultEncodingConfig{} + _ TxSigningConfig = defaultTxSigningConfig{} + + defaultEnabledSignModes = []apitxsigning.SignMode{ + apitxsigning.SignMode_SIGN_MODE_DIRECT, + apitxsigning.SignMode_SIGN_MODE_DIRECT_AUX, + apitxsigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, + } +) + +// TxConfig is an interface that a client can use to generate a concrete transaction type +// defined by the application. +type TxConfig interface { + TxEncodingConfig + TxSigningConfig +} + +// TxEncodingConfig defines the interface for transaction encoding and decoding. +// It provides methods for both binary and JSON encoding/decoding. +type TxEncodingConfig interface { + // TxEncoder returns an encoder for binary transaction encoding. + TxEncoder() txEncoder + // TxDecoder returns a decoder for binary transaction decoding. + TxDecoder() txDecoder + // TxJSONEncoder returns an encoder for JSON transaction encoding. + TxJSONEncoder() txEncoder + // TxJSONDecoder returns a decoder for JSON transaction decoding. + TxJSONDecoder() txDecoder + // Decoder returns the Decoder interface for decoding transaction bytes into a DecodedTx. + Decoder() Decoder +} + +// TxSigningConfig defines the interface for transaction signing configurations. +type TxSigningConfig interface { + // SignModeHandler returns a reference to the HandlerMap which manages the different signing modes. + SignModeHandler() *signing.HandlerMap + // SigningContext returns a reference to the Context which holds additional data required during signing. + SigningContext() *signing.Context + // MarshalSignatureJSON takes a slice of Signature objects and returns their JSON encoding. + MarshalSignatureJSON([]Signature) ([]byte, error) + // UnmarshalSignatureJSON takes a JSON byte slice and returns a slice of Signature objects. + UnmarshalSignatureJSON([]byte) ([]Signature, error) +} + +// ConfigOptions defines the configuration options for transaction processing. +type ConfigOptions struct { + AddressCodec address.Codec + Decoder Decoder + Cdc codec.BinaryCodec + + ValidatorAddressCodec address.Codec + FileResolver signing.ProtoFileResolver + TypeResolver signing.TypeResolver + CustomGetSigner map[protoreflect.FullName]signing.GetSignersFunc + MaxRecursionDepth int + + EnablesSignModes []apitxsigning.SignMode + CustomSignModes []signing.SignModeHandler + TextualCoinMetadataQueryFn textual.CoinMetadataQueryFn +} + +// validate checks the ConfigOptions for required fields and sets default values where necessary. +// It returns an error if any required field is missing. +func (c *ConfigOptions) validate() error { + if c.AddressCodec == nil { + return errors.New("address codec cannot be nil") + } + if c.Cdc == nil { + return errors.New("codec cannot be nil") + } + if c.ValidatorAddressCodec == nil { + return errors.New("validator address codec cannot be nil") + } + + // set default signModes if none are provided + if len(c.EnablesSignModes) == 0 { + c.EnablesSignModes = defaultEnabledSignModes + } + return nil +} + +// txConfig is a struct that embeds TxEncodingConfig and TxSigningConfig interfaces. +type txConfig struct { + TxEncodingConfig + TxSigningConfig +} + +// NewTxConfig creates a new TxConfig instance using the provided ConfigOptions. +// It validates the options, initializes the signing context, and sets up the decoder if not provided. +func NewTxConfig(options ConfigOptions) (TxConfig, error) { + err := options.validate() + if err != nil { + return nil, err + } + + signingCtx, err := newDefaultTxSigningConfig(options) + if err != nil { + return nil, err + } + + if options.Decoder == nil { + options.Decoder, err = txdecode.NewDecoder(txdecode.Options{ + SigningContext: signingCtx.SigningContext(), + ProtoCodec: options.Cdc, + }) + if err != nil { + return nil, err + } + } + + return &txConfig{ + TxEncodingConfig: defaultEncodingConfig{ + cdc: options.Cdc, + decoder: options.Decoder, + }, + TxSigningConfig: signingCtx, + }, nil +} + +// defaultEncodingConfig is an empty struct that implements the TxEncodingConfig interface. +type defaultEncodingConfig struct { + cdc codec.BinaryCodec + decoder Decoder +} + +// TxEncoder returns the default transaction encoder. +func (t defaultEncodingConfig) TxEncoder() txEncoder { + return encodeTx +} + +// TxDecoder returns the default transaction decoder. +func (t defaultEncodingConfig) TxDecoder() txDecoder { + return decodeTx(t.cdc, t.decoder) +} + +// TxJSONEncoder returns the default JSON transaction encoder. +func (t defaultEncodingConfig) TxJSONEncoder() txEncoder { + return encodeJsonTx +} + +// TxJSONDecoder returns the default JSON transaction decoder. +func (t defaultEncodingConfig) TxJSONDecoder() txDecoder { + return decodeJsonTx(t.cdc, t.decoder) +} + +// Decoder returns the Decoder instance associated with this encoding configuration. +func (t defaultEncodingConfig) Decoder() Decoder { + return t.decoder +} + +// defaultTxSigningConfig is a struct that holds the signing context and handler map. +type defaultTxSigningConfig struct { + signingCtx *signing.Context + handlerMap *signing.HandlerMap + cdc codec.BinaryCodec +} + +// newDefaultTxSigningConfig creates a new defaultTxSigningConfig instance using the provided ConfigOptions. +// It initializes the signing context and handler map. +func newDefaultTxSigningConfig(opts ConfigOptions) (*defaultTxSigningConfig, error) { + signingCtx, err := newSigningContext(opts) + if err != nil { + return nil, err + } + + handlerMap, err := newHandlerMap(opts, signingCtx) + if err != nil { + return nil, err + } + + return &defaultTxSigningConfig{ + signingCtx: signingCtx, + handlerMap: handlerMap, + cdc: opts.Cdc, + }, nil +} + +// SignModeHandler returns the handler map that manages the different signing modes. +func (t defaultTxSigningConfig) SignModeHandler() *signing.HandlerMap { + return t.handlerMap +} + +// SigningContext returns the signing context that holds additional data required during signing. +func (t defaultTxSigningConfig) SigningContext() *signing.Context { + return t.signingCtx +} + +// MarshalSignatureJSON takes a slice of Signature objects and returns their JSON encoding. +// This method is not yet implemented and will panic if called. +func (t defaultTxSigningConfig) MarshalSignatureJSON(signatures []Signature) ([]byte, error) { + descriptor := make([]*apitxsigning.SignatureDescriptor, len(signatures)) + + for i, sig := range signatures { + descData, err := signatureDataToProto(sig.Data) + if err != nil { + return nil, err + } + + anyPk, err := codectypes.NewAnyWithValue(sig.PubKey) + if err != nil { + return nil, err + } + + descriptor[i] = &apitxsigning.SignatureDescriptor{ + PublicKey: &anypb.Any{ + TypeUrl: codectypes.MsgTypeURL(sig.PubKey), + Value: anyPk.Value, + }, + Data: descData, + Sequence: sig.Sequence, + } + } + + return jsonMarshalOptions.Marshal(&apitxsigning.SignatureDescriptors{Signatures: descriptor}) +} + +// UnmarshalSignatureJSON takes a JSON byte slice and returns a slice of Signature objects. +// This method is not yet implemented and will panic if called. +func (t defaultTxSigningConfig) UnmarshalSignatureJSON(bz []byte) ([]Signature, error) { + var descriptor apitxsigning.SignatureDescriptors + + err := protojson.UnmarshalOptions{}.Unmarshal(bz, &descriptor) + if err != nil { + return nil, err + } + + sigs := make([]Signature, len(descriptor.Signatures)) + for i, desc := range descriptor.Signatures { + var pubkey cryptotypes.PubKey + + anyPk := &codectypes.Any{ + TypeUrl: desc.PublicKey.TypeUrl, + Value: desc.PublicKey.Value, + } + + err = t.cdc.UnpackAny(anyPk, &pubkey) + if err != nil { + return nil, err + } + + data, err := SignatureDataFromProto(desc.Data) + if err != nil { + return nil, err + } + + sigs[i] = Signature{ + PubKey: pubkey, + Data: data, + Sequence: desc.Sequence, + } + } + + return sigs, nil +} + +// newSigningContext creates a new signing context using the provided ConfigOptions. +// Returns a signing.Context instance or an error if initialization fails. +func newSigningContext(opts ConfigOptions) (*signing.Context, error) { + return signing.NewContext(signing.Options{ + FileResolver: opts.FileResolver, + TypeResolver: opts.TypeResolver, + AddressCodec: opts.AddressCodec, + ValidatorAddressCodec: opts.ValidatorAddressCodec, + CustomGetSigners: opts.CustomGetSigner, + MaxRecursionDepth: opts.MaxRecursionDepth, + }) +} + +// newHandlerMap constructs a new HandlerMap based on the provided ConfigOptions and signing context. +// It initializes handlers for each enabled and custom sign mode specified in the options. +func newHandlerMap(opts ConfigOptions, signingCtx *signing.Context) (*signing.HandlerMap, error) { + lenSignModes := len(opts.EnablesSignModes) + handlers := make([]signing.SignModeHandler, lenSignModes+len(opts.CustomSignModes)) + + for i, m := range opts.EnablesSignModes { + var err error + switch m { + case apitxsigning.SignMode_SIGN_MODE_DIRECT: + handlers[i] = &direct.SignModeHandler{} + case apitxsigning.SignMode_SIGN_MODE_TEXTUAL: + if opts.TextualCoinMetadataQueryFn == nil { + return nil, errors.New("cannot enable SIGN_MODE_TEXTUAL without a TextualCoinMetadataQueryFn") + } + handlers[i], err = textual.NewSignModeHandler(textual.SignModeOptions{ + CoinMetadataQuerier: opts.TextualCoinMetadataQueryFn, + FileResolver: signingCtx.FileResolver(), + TypeResolver: signingCtx.TypeResolver(), + }) + if err != nil { + return nil, err + } + case apitxsigning.SignMode_SIGN_MODE_DIRECT_AUX: + handlers[i], err = directaux.NewSignModeHandler(directaux.SignModeHandlerOptions{ + TypeResolver: signingCtx.TypeResolver(), + SignersContext: signingCtx, + }) + if err != nil { + return nil, err + } + case apitxsigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON: + handlers[i] = aminojson.NewSignModeHandler(aminojson.SignModeHandlerOptions{ + FileResolver: signingCtx.FileResolver(), + TypeResolver: opts.TypeResolver, + }) + } + } + for i, m := range opts.CustomSignModes { + handlers[i+lenSignModes] = m + } + + handler := signing.NewHandlerMap(handlers...) + return handler, nil +} diff --git a/client/v2/tx/config_test.go b/client/v2/tx/config_test.go new file mode 100644 index 00000000000..7d1f223d121 --- /dev/null +++ b/client/v2/tx/config_test.go @@ -0,0 +1,293 @@ +package tx + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + apicrypto "cosmossdk.io/api/cosmos/crypto/multisig/v1beta1" + _ "cosmossdk.io/api/cosmos/crypto/secp256k1" + apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + "cosmossdk.io/x/tx/signing" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/address" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + codec2 "github.com/cosmos/cosmos-sdk/crypto/codec" + kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" +) + +type mockModeHandler struct{} + +func (t mockModeHandler) Mode() apitxsigning.SignMode { + return apitxsigning.SignMode_SIGN_MODE_DIRECT +} + +func (t mockModeHandler) GetSignBytes(_ context.Context, _ signing.SignerData, _ signing.TxData) ([]byte, error) { + return []byte{}, nil +} + +func TestConfigOptions_validate(t *testing.T) { + tests := []struct { + name string + opts ConfigOptions + wantErr bool + }{ + { + name: "valid options", + opts: ConfigOptions{ + AddressCodec: address.NewBech32Codec("cosmos"), + Decoder: decoder, + Cdc: cdc, + ValidatorAddressCodec: address.NewBech32Codec("cosmosvaloper"), + }, + }, + { + name: "missing address codec", + opts: ConfigOptions{ + Decoder: decoder, + Cdc: cdc, + ValidatorAddressCodec: address.NewBech32Codec("cosmosvaloper"), + }, + wantErr: true, + }, + { + name: "missing decoder", + opts: ConfigOptions{ + AddressCodec: address.NewBech32Codec("cosmos"), + Cdc: cdc, + ValidatorAddressCodec: address.NewBech32Codec("cosmosvaloper"), + }, + }, + { + name: "missing codec", + opts: ConfigOptions{ + AddressCodec: address.NewBech32Codec("cosmos"), + Decoder: decoder, + ValidatorAddressCodec: address.NewBech32Codec("cosmosvaloper"), + }, + wantErr: true, + }, + { + name: "missing validator address codec", + opts: ConfigOptions{ + AddressCodec: address.NewBech32Codec("cosmos"), + Decoder: decoder, + Cdc: cdc, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.opts.validate(); (err != nil) != tt.wantErr { + t.Errorf("validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_newHandlerMap(t *testing.T) { + tests := []struct { + name string + opts ConfigOptions + }{ + { + name: "handler map with default sign modes", + opts: ConfigOptions{ + AddressCodec: address.NewBech32Codec("cosmos"), + Decoder: decoder, + Cdc: cdc, + ValidatorAddressCodec: address.NewBech32Codec("cosmosvaloper"), + }, + }, + { + name: "handler map with just one sign mode", + opts: ConfigOptions{ + AddressCodec: address.NewBech32Codec("cosmos"), + Decoder: decoder, + Cdc: cdc, + ValidatorAddressCodec: address.NewBech32Codec("cosmosvaloper"), + EnablesSignModes: []apitxsigning.SignMode{apitxsigning.SignMode_SIGN_MODE_DIRECT}, + }, + }, + { + name: "handler map with custom sign modes", + opts: ConfigOptions{ + AddressCodec: address.NewBech32Codec("cosmos"), + Decoder: decoder, + Cdc: cdc, + ValidatorAddressCodec: address.NewBech32Codec("cosmosvaloper"), + CustomSignModes: []signing.SignModeHandler{mockModeHandler{}}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.opts.validate() + require.NoError(t, err) + + signingCtx, err := newSigningContext(tt.opts) + require.NoError(t, err) + + handlerMap, err := newHandlerMap(tt.opts, signingCtx) + require.NoError(t, err) + require.NotNil(t, handlerMap) + require.Equal(t, len(handlerMap.SupportedModes()), len(tt.opts.EnablesSignModes)+len(tt.opts.CustomSignModes)) + }) + } +} + +func TestNewTxConfig(t *testing.T) { + tests := []struct { + name string + options ConfigOptions + wantErr bool + }{ + { + name: "valid options", + options: ConfigOptions{ + AddressCodec: ac, + Cdc: cdc, + ValidatorAddressCodec: valCodec, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewTxConfig(tt.options) + if (err != nil) != tt.wantErr { + t.Errorf("NewTxConfig() error = %v, wantErr %v", err, tt.wantErr) + return + } + require.NotNil(t, got) + }) + } +} + +func Test_defaultTxSigningConfig_MarshalSignatureJSON(t *testing.T) { + tests := []struct { + name string + options ConfigOptions + signatures func(t *testing.T) []Signature + }{ + { + name: "single signature", + options: ConfigOptions{ + AddressCodec: ac, + Cdc: cdc, + ValidatorAddressCodec: valCodec, + }, + signatures: func(t *testing.T) []Signature { + t.Helper() + + k := setKeyring() + pk, err := k.GetPubKey("alice") + require.NoError(t, err) + signature, err := k.Sign("alice", make([]byte, 10), apitxsigning.SignMode_SIGN_MODE_DIRECT) + require.NoError(t, err) + return []Signature{ + { + PubKey: pk, + Data: &SingleSignatureData{ + SignMode: apitxsigning.SignMode_SIGN_MODE_DIRECT, + Signature: signature, + }, + }, + } + }, + }, + { + name: "multisig signatures", + options: ConfigOptions{ + AddressCodec: ac, + Cdc: cdc, + ValidatorAddressCodec: valCodec, + }, + signatures: func(t *testing.T) []Signature { + t.Helper() + + n := 2 + pubKeys := make([]cryptotypes.PubKey, n) + sigs := make([]SignatureData, n) + for i := 0; i < n; i++ { + sk := secp256k1.GenPrivKey() + pubKeys[i] = sk.PubKey() + msg, err := sk.Sign(make([]byte, 10)) + require.NoError(t, err) + sigs[i] = &SingleSignatureData{ + SignMode: apitxsigning.SignMode_SIGN_MODE_DIRECT, + Signature: msg, + } + } + bitArray := cryptotypes.NewCompactBitArray(n) + mKey := kmultisig.NewLegacyAminoPubKey(n, pubKeys) + return []Signature{ + { + PubKey: mKey, + Data: &MultiSignatureData{ + BitArray: &apicrypto.CompactBitArray{ + ExtraBitsStored: bitArray.ExtraBitsStored, + Elems: bitArray.Elems, + }, + Signatures: sigs, + }, + }, + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + config, err := NewTxConfig(tt.options) + require.NoError(t, err) + + got, err := config.MarshalSignatureJSON(tt.signatures(t)) + require.NoError(t, err) + require.NotNil(t, got) + }) + } +} + +func Test_defaultTxSigningConfig_UnmarshalSignatureJSON(t *testing.T) { + registry := codectypes.NewInterfaceRegistry() + codec2.RegisterInterfaces(registry) + cdc := codec.NewProtoCodec(registry) + tests := []struct { + name string + options ConfigOptions + bz []byte + }{ + { + name: "single signature", + options: ConfigOptions{ + AddressCodec: ac, + Cdc: cdc, + ValidatorAddressCodec: valCodec, + }, + bz: []byte(`{"signatures":[{"public_key":{"@type":"/cosmos.crypto.secp256k1.PubKey", "key":"A0/vnNfExjWI07A/61KBudIyy6NNbz1xruWSEf+/4f6H"}, "data":{"single":{"mode":"SIGN_MODE_DIRECT", "signature":"usUTJwdc4PWPuox0Y0G/RuHoxyj+QpUcBGvXyNdDX1FOdoVj0tg4TGKT2NnM3QP6wCNbubjHuMOhTtqfW8SkYg=="}}}]}`), + }, + { + name: "multisig signatures", + options: ConfigOptions{ + AddressCodec: ac, + Cdc: cdc, + ValidatorAddressCodec: valCodec, + }, + bz: []byte(`{"signatures":[{"public_key":{"@type":"/cosmos.crypto.multisig.LegacyAminoPubKey","threshold":2,"public_keys":[{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A4Bs9huvS/COpZNhVhTnhgc8YR6VrSQ8hLQIHgnA+m3w"},{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AuNz2lFkLn3sKNjC5r4OWhgkWg5DZpGUiR9OdpzXspnp"}]},"data":{"multi":{"bitarray":{"extra_bits_stored":2,"elems":"AA=="},"signatures":[{"single":{"mode":"SIGN_MODE_DIRECT","signature":"vng4IlPzLH3fDFpikM5y1SfXFGny4BcLGwIFU0Ty4yoWjIxjTS4m6fgDB61sxEkV5DK/CD7gUwenGuEpzJ2IGw=="}},{"single":{"mode":"SIGN_MODE_DIRECT","signature":"2dsGmr13bq/mPxbk9AgqcFpuvk4beszWu6uxkx+EhTMdVGp4J8FtjZc8xs/Pp3oTWY4ScAORYQHxwqN4qwMXGg=="}}]}}}]}`), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + config, err := NewTxConfig(tt.options) + require.NoError(t, err) + + got, err := config.UnmarshalSignatureJSON(tt.bz) + require.NoError(t, err) + require.NotNil(t, got) + }) + } +} diff --git a/client/v2/tx/encoder.go b/client/v2/tx/encoder.go new file mode 100644 index 00000000000..2094efe6d3d --- /dev/null +++ b/client/v2/tx/encoder.go @@ -0,0 +1,119 @@ +package tx + +import ( + "fmt" + + "google.golang.org/protobuf/encoding/protojson" + protov2 "google.golang.org/protobuf/proto" + + txv1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1" + txdecode "cosmossdk.io/x/tx/decode" + + "github.com/cosmos/cosmos-sdk/codec" +) + +var ( + // marshalOption configures protobuf marshaling to be deterministic. + marshalOption = protov2.MarshalOptions{Deterministic: true} + + // jsonMarshalOptions configures JSON marshaling for protobuf messages. + jsonMarshalOptions = protojson.MarshalOptions{ + Indent: "", + UseProtoNames: true, + UseEnumNumbers: false, + } +) + +// Decoder defines the interface for decoding transaction bytes into a DecodedTx. +type Decoder interface { + Decode(txBytes []byte) (*txdecode.DecodedTx, error) +} + +// txDecoder is a function type that unmarshals transaction bytes into an API Tx type. +type txDecoder func(txBytes []byte) (Tx, error) + +// txEncoder is a function type that marshals a transaction into bytes. +type txEncoder func(tx Tx) ([]byte, error) + +// decodeTx decodes transaction bytes into an apitx.Tx structure. +func decodeTx(cdc codec.BinaryCodec, decoder Decoder) txDecoder { + return func(txBytes []byte) (Tx, error) { + tx := new(txv1beta1.Tx) + err := protov2.Unmarshal(txBytes, tx) + if err != nil { + return nil, err + } + + pTxBytes, err := protoTxBytes(tx) + if err != nil { + return nil, err + } + + decodedTx, err := decoder.Decode(pTxBytes) + if err != nil { + return nil, err + } + return newWrapperTx(cdc, decodedTx), nil + } +} + +// encodeTx encodes an apitx.Tx into bytes using protobuf marshaling options. +func encodeTx(tx Tx) ([]byte, error) { + wTx, ok := tx.(*wrappedTx) + if !ok { + return nil, fmt.Errorf("unexpected tx type: %T", tx) + } + return marshalOption.Marshal(wTx.Tx) +} + +// decodeJsonTx decodes transaction bytes into an apitx.Tx structure using JSON format. +func decodeJsonTx(cdc codec.BinaryCodec, decoder Decoder) txDecoder { + return func(txBytes []byte) (Tx, error) { + jsonTx := new(txv1beta1.Tx) + err := protojson.UnmarshalOptions{ + AllowPartial: false, + DiscardUnknown: false, + }.Unmarshal(txBytes, jsonTx) + if err != nil { + return nil, err + } + + pTxBytes, err := protoTxBytes(jsonTx) + if err != nil { + return nil, err + } + + decodedTx, err := decoder.Decode(pTxBytes) + if err != nil { + return nil, err + } + return newWrapperTx(cdc, decodedTx), nil + } +} + +// encodeJsonTx encodes an apitx.Tx into bytes using JSON marshaling options. +func encodeJsonTx(tx Tx) ([]byte, error) { + wTx, ok := tx.(*wrappedTx) + if !ok { + return nil, fmt.Errorf("unexpected tx type: %T", tx) + } + return jsonMarshalOptions.Marshal(wTx.Tx) +} + +func protoTxBytes(tx *txv1beta1.Tx) ([]byte, error) { + bodyBytes, err := marshalOption.Marshal(tx.Body) + if err != nil { + return nil, err + } + + authInfoBytes, err := marshalOption.Marshal(tx.AuthInfo) + if err != nil { + return nil, err + } + + return marshalOption.Marshal(&txv1beta1.TxRaw{ + BodyBytes: bodyBytes, + AuthInfoBytes: authInfoBytes, + Signatures: tx.Signatures, + }) +} diff --git a/client/v2/tx/encoder_test.go b/client/v2/tx/encoder_test.go new file mode 100644 index 00000000000..9dec5676231 --- /dev/null +++ b/client/v2/tx/encoder_test.go @@ -0,0 +1,107 @@ +package tx + +import ( + "testing" + + "github.com/stretchr/testify/require" + + base "cosmossdk.io/api/cosmos/base/v1beta1" + countertypes "cosmossdk.io/api/cosmos/counter/v1" + apisigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + "cosmossdk.io/core/transaction" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" +) + +func getWrappedTx(t *testing.T) *wrappedTx { + t.Helper() + + f, err := NewFactory(keybase, cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, TxParameters{}) + require.NoError(t, err) + + pk := secp256k1.GenPrivKey().PubKey() + addr, _ := ac.BytesToString(pk.Address()) + + f.tx.msgs = []transaction.Msg{&countertypes.MsgIncreaseCounter{ + Signer: addr, + Count: 0, + }} + require.NoError(t, err) + + err = f.setFeePayer(addr) + require.NoError(t, err) + + f.tx.fees = []*base.Coin{{ + Denom: "cosmos", + Amount: "1000", + }} + + err = f.setSignatures([]Signature{{ + PubKey: pk, + Data: &SingleSignatureData{ + SignMode: apisigning.SignMode_SIGN_MODE_DIRECT, + Signature: nil, + }, + Sequence: 0, + }}...) + require.NoError(t, err) + wTx, err := f.getTx() + require.NoError(t, err) + return wTx +} + +func Test_txEncoder_txDecoder(t *testing.T) { + wTx := getWrappedTx(t) + + encodedTx, err := encodeTx(wTx) + require.NoError(t, err) + require.NotNil(t, encodedTx) + + isDeterministic, err := encodeTx(wTx) + require.NoError(t, err) + require.NotNil(t, encodedTx) + require.Equal(t, encodedTx, isDeterministic) + + f := decodeTx(cdc, decoder) + decodedTx, err := f(encodedTx) + require.NoError(t, err) + require.NotNil(t, decodedTx) + + dTx, ok := decodedTx.(*wrappedTx) + require.True(t, ok) + require.Equal(t, wTx.TxRaw, dTx.TxRaw) + require.Equal(t, wTx.Tx.AuthInfo.String(), dTx.Tx.AuthInfo.String()) + require.Equal(t, wTx.Tx.Body.String(), dTx.Tx.Body.String()) + require.Equal(t, wTx.Tx.Signatures, dTx.Tx.Signatures) +} + +func Test_txJsonEncoder_txJsonDecoder(t *testing.T) { + tests := []struct { + name string + }{ + { + name: "json encode and decode tx", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + wTx := getWrappedTx(t) + + encodedTx, err := encodeJsonTx(wTx) + require.NoError(t, err) + require.NotNil(t, encodedTx) + + f := decodeJsonTx(cdc, decoder) + decodedTx, err := f(encodedTx) + require.NoError(t, err) + require.NotNil(t, decodedTx) + + dTx, ok := decodedTx.(*wrappedTx) + require.True(t, ok) + require.Equal(t, wTx.TxRaw, dTx.TxRaw) + require.Equal(t, wTx.Tx.AuthInfo.String(), dTx.Tx.AuthInfo.String()) + require.Equal(t, wTx.Tx.Body.String(), dTx.Tx.Body.String()) + require.Equal(t, wTx.Tx.Signatures, dTx.Tx.Signatures) + }) + } +} diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go new file mode 100644 index 00000000000..9dd0eae21a3 --- /dev/null +++ b/client/v2/tx/factory.go @@ -0,0 +1,761 @@ +package tx + +import ( + "context" + "errors" + "fmt" + "math/big" + "strings" + + "github.com/cosmos/go-bip39" + gogogrpc "github.com/cosmos/gogoproto/grpc" + "github.com/spf13/pflag" + "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/timestamppb" + + base "cosmossdk.io/api/cosmos/base/v1beta1" + apicrypto "cosmossdk.io/api/cosmos/crypto/multisig/v1beta1" + apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + apitx "cosmossdk.io/api/cosmos/tx/v1beta1" + "cosmossdk.io/client/v2/autocli/keyring" + "cosmossdk.io/client/v2/internal/account" + "cosmossdk.io/client/v2/internal/coins" + "cosmossdk.io/core/address" + "cosmossdk.io/core/transaction" + "cosmossdk.io/math" + "cosmossdk.io/x/tx/signing" + + flags2 "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" +) + +// Factory defines a client transaction factory that facilitates generating and +// signing an application-specific transaction. +type Factory struct { + keybase keyring.Keyring + cdc codec.BinaryCodec + accountRetriever account.AccountRetriever + ac address.Codec + conn gogogrpc.ClientConn + txConfig TxConfig + txParams TxParameters + + tx txState +} + +func NewFactoryFromFlagSet(flags *pflag.FlagSet, keybase keyring.Keyring, cdc codec.BinaryCodec, accRetriever account.AccountRetriever, + txConfig TxConfig, ac address.Codec, conn gogogrpc.ClientConn, +) (Factory, error) { + offline, _ := flags.GetBool(flags2.FlagOffline) + if err := validateFlagSet(flags, offline); err != nil { + return Factory{}, err + } + + params, err := txParamsFromFlagSet(flags, keybase, ac) + if err != nil { + return Factory{}, err + } + + params, err = prepareTxParams(params, accRetriever, offline) + if err != nil { + return Factory{}, err + } + + return NewFactory(keybase, cdc, accRetriever, txConfig, ac, conn, params) +} + +// NewFactory returns a new instance of Factory. +func NewFactory(keybase keyring.Keyring, cdc codec.BinaryCodec, accRetriever account.AccountRetriever, + txConfig TxConfig, ac address.Codec, conn gogogrpc.ClientConn, parameters TxParameters, +) (Factory, error) { + return Factory{ + keybase: keybase, + cdc: cdc, + accountRetriever: accRetriever, + ac: ac, + conn: conn, + txConfig: txConfig, + txParams: parameters, + + tx: txState{}, + }, nil +} + +// validateFlagSet checks the provided flags for consistency and requirements based on the operation mode. +func validateFlagSet(flags *pflag.FlagSet, offline bool) error { + if offline { + if !flags.Changed(flags2.FlagAccountNumber) || !flags.Changed(flags2.FlagSequence) { + return errors.New("account-number and sequence must be set in offline mode") + } + + gas, _ := flags.GetString(flags2.FlagGas) + gasSetting, _ := flags2.ParseGasSetting(gas) + if gasSetting.Simulate { + return errors.New("simulate and offline flags cannot be set at the same time") + } + } + + generateOnly, _ := flags.GetBool(flags2.FlagGenerateOnly) + chainID, _ := flags.GetString(flags2.FlagChainID) + if offline && generateOnly && chainID != "" { + return errors.New("chain ID cannot be used when offline and generate-only flags are set") + } + if chainID == "" { + return errors.New("chain ID required but not specified") + } + + dryRun, _ := flags.GetBool(flags2.FlagDryRun) + if offline && dryRun { + return errors.New("dry-run: cannot use offline mode") + } + + return nil +} + +// prepareTxParams ensures the account defined by ctx.GetFromAddress() exists and +// if the account number and/or the account sequence number are zero (not set), +// they will be queried for and set on the provided Factory. +func prepareTxParams(parameters TxParameters, accRetriever account.AccountRetriever, offline bool) (TxParameters, error) { + if offline { + return parameters, nil + } + + if len(parameters.address) == 0 { + return parameters, errors.New("missing 'from address' field") + } + + if parameters.accountNumber == 0 || parameters.sequence == 0 { + num, seq, err := accRetriever.GetAccountNumberSequence(context.Background(), parameters.address) + if err != nil { + return parameters, err + } + + if parameters.accountNumber == 0 { + parameters.accountNumber = num + } + + if parameters.sequence == 0 { + parameters.sequence = seq + } + } + + return parameters, nil +} + +// BuildUnsignedTx builds a transaction to be signed given a set of messages. +// Once created, the fee, memo, and messages are set. +func (f *Factory) BuildUnsignedTx(msgs ...transaction.Msg) error { + fees := f.txParams.fees + + isGasPriceZero, err := coins.IsZero(f.txParams.gasPrices) + if err != nil { + return err + } + if !isGasPriceZero { + areFeesZero, err := coins.IsZero(fees) + if err != nil { + return err + } + if !areFeesZero { + return errors.New("cannot provide both fees and gas prices") + } + + // f.gas is an uint64 and we should convert to LegacyDec + // without the risk of under/overflow via uint64->int64. + glDec := math.LegacyNewDecFromBigInt(new(big.Int).SetUint64(f.txParams.gas)) + + // Derive the fees based on the provided gas prices, where + // fee = ceil(gasPrice * gasLimit). + fees = make([]*base.Coin, len(f.txParams.gasPrices)) + + for i, gp := range f.txParams.gasPrices { + fee, err := math.LegacyNewDecFromStr(gp.Amount) + if err != nil { + return err + } + fee = fee.Mul(glDec) + fees[i] = &base.Coin{Denom: gp.Denom, Amount: fee.Ceil().RoundInt().String()} + } + } + + if err := validateMemo(f.txParams.memo); err != nil { + return err + } + + f.tx.msgs = msgs + f.tx.memo = f.txParams.memo + f.tx.fees = fees + f.tx.gasLimit = f.txParams.gas + f.tx.unordered = f.txParams.unordered + f.tx.timeoutTimestamp = f.txParams.timeoutTimestamp + + err = f.setFeeGranter(f.txParams.feeGranter) + if err != nil { + return err + } + err = f.setFeePayer(f.txParams.feePayer) + if err != nil { + return err + } + + return nil +} + +func (f *Factory) BuildsSignedTx(ctx context.Context, msgs ...transaction.Msg) (Tx, error) { + err := f.BuildUnsignedTx(msgs...) + if err != nil { + return nil, err + } + + return f.sign(ctx, true) +} + +// calculateGas calculates the gas required for the given messages. +func (f *Factory) calculateGas(msgs ...transaction.Msg) error { + _, adjusted, err := f.Simulate(msgs...) + if err != nil { + return err + } + + f.WithGas(adjusted) + + return nil +} + +// Simulate simulates the execution of a transaction and returns the +// simulation response obtained by the query and the adjusted gas amount. +func (f *Factory) Simulate(msgs ...transaction.Msg) (*apitx.SimulateResponse, uint64, error) { + txBytes, err := f.BuildSimTx(msgs...) + if err != nil { + return nil, 0, err + } + + txSvcClient := apitx.NewServiceClient(f.conn) + simRes, err := txSvcClient.Simulate(context.Background(), &apitx.SimulateRequest{ + TxBytes: txBytes, + }) + if err != nil { + return nil, 0, err + } + + return simRes, uint64(f.gasAdjustment() * float64(simRes.GasInfo.GasUsed)), nil +} + +// UnsignedTxString will generate an unsigned transaction and print it to the writer +// specified by ctx.Output. If simulation was requested, the gas will be +// simulated and also printed to the same writer before the transaction is +// printed. +func (f *Factory) UnsignedTxString(msgs ...transaction.Msg) (string, error) { + if f.simulateAndExecute() { + err := f.calculateGas(msgs...) + if err != nil { + return "", err + } + } + + err := f.BuildUnsignedTx(msgs...) + if err != nil { + return "", err + } + + encoder := f.txConfig.TxJSONEncoder() + if encoder == nil { + return "", errors.New("cannot print unsigned tx: tx json encoder is nil") + } + + tx, err := f.getTx() + if err != nil { + return "", err + } + + json, err := encoder(tx) + if err != nil { + return "", err + } + + return fmt.Sprintf("%s\n", json), nil +} + +// BuildSimTx creates an unsigned tx with an empty single signature and returns +// the encoded transaction or an error if the unsigned transaction cannot be +// built. +func (f *Factory) BuildSimTx(msgs ...transaction.Msg) ([]byte, error) { + err := f.BuildUnsignedTx(msgs...) + if err != nil { + return nil, err + } + + pk, err := f.getSimPK() + if err != nil { + return nil, err + } + + // Create an empty signature literal as the ante handler will populate with a + // sentinel pubkey. + sig := Signature{ + PubKey: pk, + Data: f.getSimSignatureData(pk), + Sequence: f.sequence(), + } + if err := f.setSignatures(sig); err != nil { + return nil, err + } + + encoder := f.txConfig.TxEncoder() + if encoder == nil { + return nil, fmt.Errorf("cannot simulate tx: tx encoder is nil") + } + + tx, err := f.getTx() + if err != nil { + return nil, err + } + return encoder(tx) +} + +// sign signs a given tx with a named key. The bytes signed over are canonical. +// The resulting signature will be added to the transaction builder overwriting the previous +// ones if overwrite=true (otherwise, the signature will be appended). +// Signing a transaction with multiple signers in the DIRECT mode is not supported and will +// return an error. +func (f *Factory) sign(ctx context.Context, overwriteSig bool) (Tx, error) { + if f.keybase == nil { + return nil, errors.New("keybase must be set prior to signing a transaction") + } + + var err error + if f.txParams.signMode == apitxsigning.SignMode_SIGN_MODE_UNSPECIFIED { + f.txParams.signMode = f.txConfig.SignModeHandler().DefaultMode() + } + + pubKey, err := f.keybase.GetPubKey(f.txParams.fromName) + if err != nil { + return nil, err + } + + addr, err := f.ac.BytesToString(pubKey.Address()) + if err != nil { + return nil, err + } + + signerData := signing.SignerData{ + ChainID: f.txParams.chainID, + AccountNumber: f.txParams.accountNumber, + Sequence: f.txParams.sequence, + PubKey: &anypb.Any{ + TypeUrl: codectypes.MsgTypeURL(pubKey), + Value: pubKey.Bytes(), + }, + Address: addr, + } + + // For SIGN_MODE_DIRECT, we need to set the SignerInfos before generating + // the sign bytes. This is done by calling setSignatures with a nil + // signature, which in turn calls setSignerInfos internally. + // + // For SIGN_MODE_LEGACY_AMINO, this step is not strictly necessary, + // but we include it for consistency across all sign modes. + // It does not affect the generated sign bytes for LEGACY_AMINO. + // + // By setting the signatures here, we ensure that the correct SignerInfos + // are in place for all subsequent operations, regardless of the sign mode. + sigData := SingleSignatureData{ + SignMode: f.txParams.signMode, + Signature: nil, + } + sig := Signature{ + PubKey: pubKey, + Data: &sigData, + Sequence: f.txParams.sequence, + } + + var prevSignatures []Signature + if !overwriteSig { + tx, err := f.getTx() + if err != nil { + return nil, err + } + + prevSignatures, err = tx.GetSignatures() + if err != nil { + return nil, err + } + } + // Overwrite or append signer infos. + var sigs []Signature + if overwriteSig { + sigs = []Signature{sig} + } else { + sigs = append(sigs, prevSignatures...) + sigs = append(sigs, sig) + } + if err := f.setSignatures(sigs...); err != nil { + return nil, err + } + + tx, err := f.getTx() + if err != nil { + return nil, err + } + + if err := checkMultipleSigners(tx); err != nil { + return nil, err + } + + bytesToSign, err := f.getSignBytesAdapter(ctx, signerData) + if err != nil { + return nil, err + } + + // Sign those bytes + sigBytes, err := f.keybase.Sign(f.txParams.fromName, bytesToSign, f.txParams.signMode) + if err != nil { + return nil, err + } + + // Construct the SignatureV2 struct + sigData = SingleSignatureData{ + SignMode: f.signMode(), + Signature: sigBytes, + } + sig = Signature{ + PubKey: pubKey, + Data: &sigData, + Sequence: f.txParams.sequence, + } + + if overwriteSig { + err = f.setSignatures(sig) + } else { + prevSignatures = append(prevSignatures, sig) + err = f.setSignatures(prevSignatures...) + } + + if err != nil { + return nil, fmt.Errorf("unable to set signatures on payload: %w", err) + } + + return f.getTx() +} + +// getSignBytesAdapter returns the sign bytes for a given transaction and sign mode. +func (f *Factory) getSignBytesAdapter(ctx context.Context, signerData signing.SignerData) ([]byte, error) { + txData, err := f.getSigningTxData() + if err != nil { + return nil, err + } + + // Generate the bytes to be signed. + return f.txConfig.SignModeHandler().GetSignBytes(ctx, f.signMode(), signerData, *txData) +} + +// WithGas returns a copy of the Factory with an updated gas value. +func (f *Factory) WithGas(gas uint64) { + f.txParams.gas = gas +} + +// WithSequence returns a copy of the Factory with an updated sequence number. +func (f *Factory) WithSequence(sequence uint64) { + f.txParams.sequence = sequence +} + +// WithAccountNumber returns a copy of the Factory with an updated account number. +func (f *Factory) WithAccountNumber(accnum uint64) { + f.txParams.accountNumber = accnum +} + +// sequence returns the sequence number. +func (f *Factory) sequence() uint64 { return f.txParams.sequence } + +// gasAdjustment returns the gas adjustment value. +func (f *Factory) gasAdjustment() float64 { return f.txParams.gasAdjustment } + +// simulateAndExecute returns whether to simulate and execute. +func (f *Factory) simulateAndExecute() bool { return f.txParams.simulateAndExecute } + +// signMode returns the sign mode. +func (f *Factory) signMode() apitxsigning.SignMode { return f.txParams.signMode } + +// getSimPK gets the public key to use for building a simulation tx. +// Note, we should only check for keys in the keybase if we are in simulate and execute mode, +// e.g. when using --gas=auto. +// When using --dry-run, we are is simulation mode only and should not check the keybase. +// Ref: https://github.com/cosmos/cosmos-sdk/issues/11283 +func (f *Factory) getSimPK() (cryptotypes.PubKey, error) { + var ( + err error + pk cryptotypes.PubKey = &secp256k1.PubKey{} + ) + + if f.txParams.simulateAndExecute && f.keybase != nil { + pk, err = f.keybase.GetPubKey(f.txParams.fromName) + if err != nil { + return nil, err + } + } else { + // When in dry-run mode, attempt to retrieve the account using the provided address. + // If the account retrieval fails, the default public key is used. + acc, err := f.accountRetriever.GetAccount(context.Background(), f.txParams.address) + if err != nil { + // If there is an error retrieving the account, return the default public key. + return pk, nil + } + // If the account is successfully retrieved, use its public key. + pk = acc.GetPubKey() + } + + return pk, nil +} + +// getSimSignatureData based on the pubKey type gets the correct SignatureData type +// to use for building a simulation tx. +func (f *Factory) getSimSignatureData(pk cryptotypes.PubKey) SignatureData { + multisigPubKey, ok := pk.(*multisig.LegacyAminoPubKey) + if !ok { + return &SingleSignatureData{SignMode: f.txParams.signMode} + } + + multiSignatureData := make([]SignatureData, 0, multisigPubKey.Threshold) + for i := uint32(0); i < multisigPubKey.Threshold; i++ { + multiSignatureData = append(multiSignatureData, &SingleSignatureData{ + SignMode: f.signMode(), + }) + } + + return &MultiSignatureData{ + BitArray: &apicrypto.CompactBitArray{}, + Signatures: multiSignatureData, + } +} + +func (f *Factory) getTx() (*wrappedTx, error) { + msgs, err := msgsV1toAnyV2(f.tx.msgs) + if err != nil { + return nil, err + } + + body := &apitx.TxBody{ + Messages: msgs, + Memo: f.tx.memo, + TimeoutHeight: f.tx.timeoutHeight, + TimeoutTimestamp: timestamppb.New(f.tx.timeoutTimestamp), + Unordered: f.tx.unordered, + ExtensionOptions: f.tx.extensionOptions, + NonCriticalExtensionOptions: f.tx.nonCriticalExtensionOptions, + } + + fee, err := f.getFee() + if err != nil { + return nil, err + } + + authInfo := &apitx.AuthInfo{ + SignerInfos: f.tx.signerInfos, + Fee: fee, + } + + bodyBytes, err := marshalOption.Marshal(body) + if err != nil { + return nil, err + } + + authInfoBytes, err := marshalOption.Marshal(authInfo) + if err != nil { + return nil, err + } + + txRawBytes, err := marshalOption.Marshal(&apitx.TxRaw{ + BodyBytes: bodyBytes, + AuthInfoBytes: authInfoBytes, + Signatures: f.tx.signatures, + }) + if err != nil { + return nil, err + } + + decodedTx, err := f.txConfig.Decoder().Decode(txRawBytes) + if err != nil { + return nil, err + } + + return newWrapperTx(f.cdc, decodedTx), nil +} + +// getSigningTxData returns a TxData with the current txState info. +func (f *Factory) getSigningTxData() (*signing.TxData, error) { + tx, err := f.getTx() + if err != nil { + return nil, err + } + + return &signing.TxData{ + Body: tx.Tx.Body, + AuthInfo: tx.Tx.AuthInfo, + BodyBytes: tx.TxRaw.BodyBytes, + AuthInfoBytes: tx.TxRaw.AuthInfoBytes, + BodyHasUnknownNonCriticals: tx.TxBodyHasUnknownNonCriticals, + }, nil +} + +// setSignatures sets the signatures for the transaction builder. +// It takes a variable number of Signature arguments and processes each one to extract the mode information and raw signature. +// It also converts the public key to the appropriate format and sets the signer information. +func (f *Factory) setSignatures(signatures ...Signature) error { + n := len(signatures) + signerInfos := make([]*apitx.SignerInfo, n) + rawSignatures := make([][]byte, n) + + for i, sig := range signatures { + var ( + modeInfo *apitx.ModeInfo + pubKey *codectypes.Any + err error + anyPk *anypb.Any + ) + + modeInfo, rawSignatures[i] = signatureDataToModeInfoAndSig(sig.Data) + if sig.PubKey != nil { + pubKey, err = codectypes.NewAnyWithValue(sig.PubKey) + if err != nil { + return err + } + anyPk = &anypb.Any{ + TypeUrl: pubKey.TypeUrl, + Value: pubKey.Value, + } + } + + signerInfos[i] = &apitx.SignerInfo{ + PublicKey: anyPk, + ModeInfo: modeInfo, + Sequence: sig.Sequence, + } + } + + f.tx.signerInfos = signerInfos + f.tx.signatures = rawSignatures + + return nil +} + +// getFee computes the transaction fee information. +// It returns a pointer to an apitx.Fee struct containing the fee amount, gas limit, payer, and granter information. +// If the granter or payer addresses are set, it converts them from bytes to string using the addressCodec. +func (f *Factory) getFee() (fee *apitx.Fee, err error) { + granterStr := "" + if f.tx.granter != nil { + granterStr, err = f.ac.BytesToString(f.tx.granter) + if err != nil { + return nil, err + } + } + + payerStr := "" + if f.tx.payer != nil { + payerStr, err = f.ac.BytesToString(f.tx.payer) + if err != nil { + return nil, err + } + } + + fee = &apitx.Fee{ + Amount: f.tx.fees, + GasLimit: f.tx.gasLimit, + Payer: payerStr, + Granter: granterStr, + } + + return fee, nil +} + +// setFeePayer sets the fee payer for the transaction. +func (f *Factory) setFeePayer(feePayer string) error { + if feePayer == "" { + return nil + } + + addr, err := f.ac.StringToBytes(feePayer) + if err != nil { + return err + } + f.tx.payer = addr + return nil +} + +// setFeeGranter sets the fee granter's address in the transaction builder. +// If the feeGranter string is empty, the function returns nil without setting an address. +// It converts the feeGranter string to bytes using the address codec and sets it as the granter address. +// Returns an error if the conversion fails. +func (f *Factory) setFeeGranter(feeGranter string) error { + if feeGranter == "" { + return nil + } + + addr, err := f.ac.StringToBytes(feeGranter) + if err != nil { + return err + } + f.tx.granter = addr + + return nil +} + +// msgsV1toAnyV2 converts a slice of transaction.Msg (v1) to a slice of anypb.Any (v2). +// It first converts each transaction.Msg into a codectypes.Any and then converts +// these into anypb.Any. +func msgsV1toAnyV2(msgs []transaction.Msg) ([]*anypb.Any, error) { + anys := make([]*codectypes.Any, len(msgs)) + for i, msg := range msgs { + anyMsg, err := codectypes.NewAnyWithValue(msg) + if err != nil { + return nil, err + } + anys[i] = anyMsg + } + + return intoAnyV2(anys), nil +} + +// intoAnyV2 converts a slice of codectypes.Any (v1) to a slice of anypb.Any (v2). +func intoAnyV2(v1s []*codectypes.Any) []*anypb.Any { + v2s := make([]*anypb.Any, len(v1s)) + for i, v1 := range v1s { + v2s[i] = &anypb.Any{ + TypeUrl: v1.TypeUrl, + Value: v1.Value, + } + } + return v2s +} + +// checkMultipleSigners checks that there can be maximum one DIRECT signer in +// a tx. +func checkMultipleSigners(tx Tx) error { + directSigners := 0 + sigsV2, err := tx.GetSignatures() + if err != nil { + return err + } + for _, sig := range sigsV2 { + directSigners += countDirectSigners(sig.Data) + if directSigners > 1 { + return errors.New("txs signed with CLI can have maximum 1 DIRECT signer") + } + } + + return nil +} + +// validateMemo validates the memo field. +func validateMemo(memo string) error { + // Prevent simple inclusion of a valid mnemonic in the memo field + if memo != "" && bip39.IsMnemonicValid(strings.ToLower(memo)) { + return errors.New("cannot provide a valid mnemonic seed in the memo field") + } + + return nil +} diff --git a/client/v2/tx/factory_test.go b/client/v2/tx/factory_test.go new file mode 100644 index 00000000000..39f21b38d0d --- /dev/null +++ b/client/v2/tx/factory_test.go @@ -0,0 +1,921 @@ +package tx + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/anypb" + + base "cosmossdk.io/api/cosmos/base/v1beta1" + apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + "cosmossdk.io/core/transaction" + "cosmossdk.io/x/tx/signing" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + countertypes "github.com/cosmos/cosmos-sdk/testutil/x/counter/types" +) + +var ( + signer = "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9" + addr, _ = ac.StringToBytes(signer) +) + +func TestFactory_prepareTxParams(t *testing.T) { + tests := []struct { + name string + txParams TxParameters + error bool + }{ + { + name: "no error", + txParams: TxParameters{ + AccountConfig: AccountConfig{ + address: addr, + }, + }, + }, + { + name: "without account", + txParams: TxParameters{}, + error: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var err error + tt.txParams, err = prepareTxParams(tt.txParams, mockAccountRetriever{}, false) + if (err != nil) != tt.error { + t.Errorf("Prepare() error = %v, wantErr %v", err, tt.error) + } + }) + } +} + +func TestFactory_BuildUnsignedTx(t *testing.T) { + tests := []struct { + name string + txParams TxParameters + msgs []transaction.Msg + error bool + }{ + { + name: "no error", + txParams: TxParameters{ + chainID: "demo", + AccountConfig: AccountConfig{ + address: addr, + }, + }, + msgs: []transaction.Msg{ + &countertypes.MsgIncreaseCounter{ + Signer: signer, + Count: 0, + }, + }, + }, + { + name: "fees and gas price provided", + txParams: TxParameters{ + chainID: "demo", + AccountConfig: AccountConfig{ + address: addr, + }, + GasConfig: GasConfig{ + gasPrices: []*base.DecCoin{ + { + Amount: "1000", + Denom: "stake", + }, + }, + }, + FeeConfig: FeeConfig{ + fees: []*base.Coin{ + { + Amount: "1000", + Denom: "stake", + }, + }, + }, + }, + msgs: []transaction.Msg{}, + error: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := NewFactory(keybase, cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, tt.txParams) + require.NoError(t, err) + require.NotNil(t, f) + err = f.BuildUnsignedTx(tt.msgs...) + if tt.error { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Nil(t, f.tx.signatures) + require.Nil(t, f.tx.signerInfos) + } + }) + } +} + +func TestFactory_calculateGas(t *testing.T) { + tests := []struct { + name string + txParams TxParameters + msgs []transaction.Msg + error bool + }{ + { + name: "no error", + txParams: TxParameters{ + chainID: "demo", + AccountConfig: AccountConfig{ + address: addr, + }, + GasConfig: GasConfig{ + gasAdjustment: 1, + }, + }, + msgs: []transaction.Msg{ + &countertypes.MsgIncreaseCounter{ + Signer: signer, + Count: 0, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := NewFactory(keybase, cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, tt.txParams) + require.NoError(t, err) + require.NotNil(t, f) + err = f.calculateGas(tt.msgs...) + if tt.error { + require.Error(t, err) + } else { + require.NoError(t, err) + require.NotZero(t, f.txParams.GasConfig) + } + }) + } +} + +func TestFactory_Simulate(t *testing.T) { + tests := []struct { + name string + txParams TxParameters + msgs []transaction.Msg + error bool + }{ + { + name: "no error", + txParams: TxParameters{ + chainID: "demo", + AccountConfig: AccountConfig{ + address: addr, + }, + GasConfig: GasConfig{ + gasAdjustment: 1, + }, + }, + msgs: []transaction.Msg{ + &countertypes.MsgIncreaseCounter{ + Signer: signer, + Count: 0, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := NewFactory(keybase, cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, tt.txParams) + require.NoError(t, err) + require.NotNil(t, f) + got, got1, err := f.Simulate(tt.msgs...) + if tt.error { + require.Error(t, err) + } else { + require.NoError(t, err) + require.NotNil(t, got) + require.NotZero(t, got1) + } + }) + } +} + +func TestFactory_BuildSimTx(t *testing.T) { + tests := []struct { + name string + txParams TxParameters + msgs []transaction.Msg + want []byte + error bool + }{ + { + name: "no error", + txParams: TxParameters{ + chainID: "demo", + AccountConfig: AccountConfig{ + address: addr, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := NewFactory(keybase, cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, tt.txParams) + require.NoError(t, err) + require.NotNil(t, f) + got, err := f.BuildSimTx(tt.msgs...) + if tt.error { + require.Error(t, err) + } else { + require.NoError(t, err) + require.NotNil(t, got) + } + }) + } +} + +func TestFactory_Sign(t *testing.T) { + tests := []struct { + name string + txParams TxParameters + wantErr bool + }{ + { + name: "no error", + txParams: TxParameters{ + chainID: "demo", + AccountConfig: AccountConfig{ + fromName: "alice", + address: addr, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := NewFactory(setKeyring(), cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, tt.txParams) + require.NoError(t, err) + require.NotNil(t, f) + + err = f.BuildUnsignedTx([]transaction.Msg{ + &countertypes.MsgIncreaseCounter{ + Signer: signer, + Count: 0, + }, + }...) + require.NoError(t, err) + + require.Nil(t, f.tx.signatures) + require.Nil(t, f.tx.signerInfos) + + tx, err := f.sign(context.Background(), true) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + sigs, err := tx.GetSignatures() + require.NoError(t, err) + require.NotNil(t, sigs) + require.NotNil(t, f.tx.signerInfos) + } + }) + } +} + +func TestFactory_getSignBytesAdapter(t *testing.T) { + tests := []struct { + name string + txParams TxParameters + error bool + }{ + { + name: "no error", + txParams: TxParameters{ + chainID: "demo", + signMode: apitxsigning.SignMode_SIGN_MODE_DIRECT, + AccountConfig: AccountConfig{ + address: addr, + }, + }, + }, + { + name: "signMode not specified", + txParams: TxParameters{ + chainID: "demo", + AccountConfig: AccountConfig{ + address: addr, + }, + }, + error: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := NewFactory(setKeyring(), cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, tt.txParams) + require.NoError(t, err) + require.NotNil(t, f) + + err = f.BuildUnsignedTx([]transaction.Msg{ + &countertypes.MsgIncreaseCounter{ + Signer: signer, + Count: 0, + }, + }...) + require.NoError(t, err) + + pk, err := f.keybase.GetPubKey("alice") + require.NoError(t, err) + require.NotNil(t, pk) + + addr, err := f.ac.BytesToString(pk.Address()) + require.NoError(t, err) + require.NotNil(t, addr) + + signerData := signing.SignerData{ + Address: addr, + ChainID: f.txParams.chainID, + AccountNumber: 0, + Sequence: 0, + PubKey: &anypb.Any{ + TypeUrl: codectypes.MsgTypeURL(pk), + Value: pk.Bytes(), + }, + } + + got, err := f.getSignBytesAdapter(context.Background(), signerData) + if tt.error { + require.Error(t, err) + } else { + require.NoError(t, err) + require.NotNil(t, got) + } + }) + } +} + +func Test_validateMemo(t *testing.T) { + tests := []struct { + name string + memo string + wantErr bool + }{ + { + name: "empty memo", + memo: "", + }, + { + name: "valid memo", + memo: "11245", + }, + { + name: "invalid Memo", + memo: "echo echo echo echo echo echo echo echo echo echo echo echo echo echo echo", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := validateMemo(tt.memo); (err != nil) != tt.wantErr { + t.Errorf("validateMemo() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestFactory_WithFunctions(t *testing.T) { + tests := []struct { + name string + txParams TxParameters + withFunc func(*Factory) + checkFunc func(*Factory) bool + }{ + { + name: "with gas", + txParams: TxParameters{ + AccountConfig: AccountConfig{ + address: addr, + }, + }, + withFunc: func(f *Factory) { + f.WithGas(1000) + }, + checkFunc: func(f *Factory) bool { + return f.txParams.GasConfig.gas == 1000 + }, + }, + { + name: "with sequence", + txParams: TxParameters{ + AccountConfig: AccountConfig{ + address: addr, + }, + }, + withFunc: func(f *Factory) { + f.WithSequence(10) + }, + checkFunc: func(f *Factory) bool { + return f.txParams.AccountConfig.sequence == 10 + }, + }, + { + name: "with account number", + txParams: TxParameters{ + AccountConfig: AccountConfig{ + address: addr, + }, + }, + withFunc: func(f *Factory) { + f.WithAccountNumber(123) + }, + checkFunc: func(f *Factory) bool { + return f.txParams.AccountConfig.accountNumber == 123 + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := NewFactory(setKeyring(), cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, tt.txParams) + require.NoError(t, err) + require.NotNil(t, f) + + tt.withFunc(&f) + require.True(t, tt.checkFunc(&f)) + }) + } +} + +func TestFactory_getTx(t *testing.T) { + tests := []struct { + name string + txSetter func(f *Factory) + checkResult func(Tx) + }{ + { + name: "empty tx", + txSetter: func(f *Factory) { + }, + checkResult: func(tx Tx) { + wTx, ok := tx.(*wrappedTx) + require.True(t, ok) + // require.Equal(t, []*anypb.Any(nil), wTx.Tx.Body.Messages) + require.Nil(t, wTx.Tx.Body.Messages) + require.Empty(t, wTx.Tx.Body.Memo) + require.Equal(t, uint64(0), wTx.Tx.Body.TimeoutHeight) + require.Equal(t, wTx.Tx.Body.Unordered, false) + require.Nil(t, wTx.Tx.Body.ExtensionOptions) + require.Nil(t, wTx.Tx.Body.NonCriticalExtensionOptions) + + require.Nil(t, wTx.Tx.AuthInfo.SignerInfos) + require.Nil(t, wTx.Tx.AuthInfo.Fee.Amount) + require.Equal(t, uint64(0), wTx.Tx.AuthInfo.Fee.GasLimit) + require.Empty(t, wTx.Tx.AuthInfo.Fee.Payer) + require.Empty(t, wTx.Tx.AuthInfo.Fee.Granter) + + require.Nil(t, wTx.Tx.Signatures) + }, + }, + { + name: "full tx", + txSetter: func(f *Factory) { + pk := secp256k1.GenPrivKey().PubKey() + addr, _ := f.ac.BytesToString(pk.Address()) + + f.tx.msgs = []transaction.Msg{&countertypes.MsgIncreaseCounter{ + Signer: addr, + Count: 0, + }} + + err := f.setFeePayer(addr) + require.NoError(t, err) + + f.tx.fees = []*base.Coin{{ + Denom: "cosmos", + Amount: "1000", + }} + + err = f.setSignatures([]Signature{{ + PubKey: pk, + Data: &SingleSignatureData{ + SignMode: apitxsigning.SignMode_SIGN_MODE_DIRECT, + Signature: nil, + }, + Sequence: 0, + }}...) + require.NoError(t, err) + }, + checkResult: func(tx Tx) { + wTx, ok := tx.(*wrappedTx) + require.True(t, ok) + require.True(t, len(wTx.Tx.Body.Messages) == 1) + + require.NotNil(t, wTx.Tx.AuthInfo.SignerInfos) + require.NotNil(t, wTx.Tx.AuthInfo.Fee.Amount) + + require.NotNil(t, wTx.Tx.Signatures) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := NewFactory(keybase, cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, TxParameters{}) + require.NoError(t, err) + tt.txSetter(&f) + got, err := f.getTx() + require.NoError(t, err) + require.NotNil(t, got) + tt.checkResult(got) + }) + } +} + +func TestFactory_getFee(t *testing.T) { + tests := []struct { + name string + feeAmount []*base.Coin + feeGranter string + feePayer string + }{ + { + name: "get fee with payer", + feeAmount: []*base.Coin{ + { + Denom: "cosmos", + Amount: "1000", + }, + }, + feeGranter: "", + feePayer: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + }, + { + name: "get fee with granter", + feeAmount: []*base.Coin{ + { + Denom: "cosmos", + Amount: "1000", + }, + }, + feeGranter: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + feePayer: "", + }, + { + name: "get fee with granter and granter", + feeAmount: []*base.Coin{ + { + Denom: "cosmos", + Amount: "1000", + }, + }, + feeGranter: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + feePayer: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := NewFactory(keybase, cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, TxParameters{}) + require.NoError(t, err) + f.tx.fees = tt.feeAmount + err = f.setFeeGranter(tt.feeGranter) + require.NoError(t, err) + err = f.setFeePayer(tt.feePayer) + require.NoError(t, err) + + fee, err := f.getFee() + require.NoError(t, err) + require.NotNil(t, fee) + + require.Equal(t, fee.Amount, tt.feeAmount) + require.Equal(t, fee.Granter, tt.feeGranter) + require.Equal(t, fee.Payer, tt.feePayer) + }) + } +} + +func TestFactory_getSigningTxData(t *testing.T) { + tests := []struct { + name string + txSetter func(f *Factory) + }{ + { + name: "empty tx", + txSetter: func(f *Factory) {}, + }, + { + name: "full tx", + txSetter: func(f *Factory) { + pk := secp256k1.GenPrivKey().PubKey() + addr, _ := ac.BytesToString(pk.Address()) + + f.tx.msgs = []transaction.Msg{&countertypes.MsgIncreaseCounter{ + Signer: addr, + Count: 0, + }} + + err := f.setFeePayer(addr) + require.NoError(t, err) + + f.tx.fees = []*base.Coin{{ + Denom: "cosmos", + Amount: "1000", + }} + + err = f.setSignatures([]Signature{{ + PubKey: pk, + Data: &SingleSignatureData{ + SignMode: apitxsigning.SignMode_SIGN_MODE_DIRECT, + Signature: []byte("signature"), + }, + Sequence: 0, + }}...) + require.NoError(t, err) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := NewFactory(keybase, cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, TxParameters{}) + require.NoError(t, err) + tt.txSetter(&f) + got, err := f.getSigningTxData() + require.NoError(t, err) + require.NotNil(t, got) + }) + } +} + +func TestFactoryr_setMsgs(t *testing.T) { + tests := []struct { + name string + msgs []transaction.Msg + wantErr bool + }{ + { + name: "set msgs", + msgs: []transaction.Msg{ + &countertypes.MsgIncreaseCounter{ + Signer: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + Count: 0, + }, + &countertypes.MsgIncreaseCounter{ + Signer: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + Count: 1, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := NewFactory(keybase, cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, TxParameters{}) + require.NoError(t, err) + f.tx.msgs = tt.msgs + require.NoError(t, err) + require.Equal(t, len(tt.msgs), len(f.tx.msgs)) + + for i, msg := range tt.msgs { + require.Equal(t, msg, f.tx.msgs[i]) + } + }) + } +} + +func TestFactory_SetMemo(t *testing.T) { + tests := []struct { + name string + memo string + }{ + { + name: "set memo", + memo: "test", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := NewFactory(keybase, cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, TxParameters{}) + require.NoError(t, err) + f.tx.memo = tt.memo + require.Equal(t, f.tx.memo, tt.memo) + }) + } +} + +func TestFactory_SetFeeAmount(t *testing.T) { + tests := []struct { + name string + coins []*base.Coin + }{ + { + name: "set coins", + coins: []*base.Coin{ + { + Denom: "cosmos", + Amount: "1000", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := NewFactory(keybase, cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, TxParameters{}) + require.NoError(t, err) + f.tx.fees = tt.coins + require.Equal(t, len(tt.coins), len(f.tx.fees)) + + for i, coin := range tt.coins { + require.Equal(t, coin.Amount, f.tx.fees[i].Amount) + } + }) + } +} + +func TestFactory_SetGasLimit(t *testing.T) { + tests := []struct { + name string + gasLimit uint64 + }{ + { + name: "set gas limit", + gasLimit: 1, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := NewFactory(keybase, cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, TxParameters{}) + require.NoError(t, err) + f.tx.gasLimit = tt.gasLimit + require.Equal(t, f.tx.gasLimit, tt.gasLimit) + }) + } +} + +func TestFactory_SetUnordered(t *testing.T) { + tests := []struct { + name string + unordered bool + }{ + { + name: "unordered", + unordered: true, + }, + { + name: "not unordered", + unordered: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := NewFactory(keybase, cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, TxParameters{}) + require.NoError(t, err) + f.tx.unordered = tt.unordered + require.Equal(t, f.tx.unordered, tt.unordered) + }) + } +} + +func TestFactory_setSignatures(t *testing.T) { + tests := []struct { + name string + signatures func() []Signature + }{ + { + name: "set empty single signature", + signatures: func() []Signature { + return []Signature{{ + PubKey: secp256k1.GenPrivKey().PubKey(), + Data: &SingleSignatureData{ + SignMode: apitxsigning.SignMode_SIGN_MODE_DIRECT, + Signature: nil, + }, + Sequence: 0, + }} + }, + }, + { + name: "set single signature", + signatures: func() []Signature { + return []Signature{{ + PubKey: secp256k1.GenPrivKey().PubKey(), + Data: &SingleSignatureData{ + SignMode: apitxsigning.SignMode_SIGN_MODE_DIRECT, + Signature: []byte("signature"), + }, + Sequence: 0, + }} + }, + }, + { + name: "set empty multi signature", + signatures: func() []Signature { + return []Signature{{ + PubKey: multisig.NewLegacyAminoPubKey(1, []cryptotypes.PubKey{secp256k1.GenPrivKey().PubKey()}), + Data: &MultiSignatureData{ + BitArray: nil, + Signatures: []SignatureData{ + &SingleSignatureData{ + SignMode: apitxsigning.SignMode_SIGN_MODE_DIRECT, + Signature: nil, + }, + }, + }, + Sequence: 0, + }} + }, + }, + { + name: "set multi signature", + signatures: func() []Signature { + return []Signature{{ + PubKey: multisig.NewLegacyAminoPubKey(1, []cryptotypes.PubKey{secp256k1.GenPrivKey().PubKey()}), + Data: &MultiSignatureData{ + BitArray: nil, + Signatures: []SignatureData{ + &SingleSignatureData{ + SignMode: apitxsigning.SignMode_SIGN_MODE_DIRECT, + Signature: []byte("signature"), + }, + }, + }, + Sequence: 0, + }} + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cryptocodec.RegisterInterfaces(cdc.InterfaceRegistry()) + f, err := NewFactory(keybase, cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, TxParameters{}) + require.NoError(t, err) + sigs := tt.signatures() + err = f.setSignatures(sigs...) + require.NoError(t, err) + tx, err := f.getTx() + require.NoError(t, err) + signatures, err := tx.GetSignatures() + require.NoError(t, err) + require.Equal(t, len(sigs), len(signatures)) + for i := range signatures { + require.Equal(t, sigs[i].PubKey, signatures[i].PubKey) + } + }) + } +} + +/////////////////////// + +func Test_msgsV1toAnyV2(t *testing.T) { + tests := []struct { + name string + msgs []transaction.Msg + }{ + { + name: "convert msgV1 to V2", + msgs: []transaction.Msg{ + &countertypes.MsgIncreaseCounter{ + Signer: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + Count: 0, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := msgsV1toAnyV2(tt.msgs) + require.NoError(t, err) + require.NotNil(t, got) + }) + } +} + +func Test_intoAnyV2(t *testing.T) { + tests := []struct { + name string + msgs []*codectypes.Any + }{ + { + name: "any to v2", + msgs: []*codectypes.Any{ + { + TypeUrl: "/random/msg", + Value: []byte("random message"), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := intoAnyV2(tt.msgs) + require.NotNil(t, got) + require.Equal(t, len(got), len(tt.msgs)) + for i, msg := range got { + require.Equal(t, msg.TypeUrl, tt.msgs[i].TypeUrl) + require.Equal(t, msg.Value, tt.msgs[i].Value) + } + }) + } +} diff --git a/client/v2/tx/flags.go b/client/v2/tx/flags.go new file mode 100644 index 00000000000..6ef8584042f --- /dev/null +++ b/client/v2/tx/flags.go @@ -0,0 +1,52 @@ +package tx + +import ( + "fmt" + "strconv" +) + +// Flag constants for transaction-related flags +const ( + defaultGasLimit = 200000 + gasFlagAuto = "auto" + + flagTimeoutTimestamp = "timeout-timestamp" + flagChainID = "chain-id" + flagNote = "note" + flagSignMode = "sign-mode" + flagAccountNumber = "account-number" + flagSequence = "sequence" + flagFrom = "from" + flagDryRun = "dry-run" + flagGas = "gas" + flagGasAdjustment = "gas-adjustment" + flagGasPrices = "gas-prices" + flagFees = "fees" + flagFeePayer = "fee-payer" + flagFeeGranter = "fee-granter" + flagUnordered = "unordered" + flagOffline = "offline" + flagGenerateOnly = "generate-only" +) + +// parseGasSetting parses a string gas value. The value may either be 'auto', +// which indicates a transaction should be executed in simulate mode to +// automatically find a sufficient gas value, or a string integer. It returns an +// error if a string integer is provided which cannot be parsed. +func parseGasSetting(gasStr string) (bool, uint64, error) { + switch gasStr { + case "": + return false, defaultGasLimit, nil + + case gasFlagAuto: + return true, 0, nil + + default: + gas, err := strconv.ParseUint(gasStr, 10, 64) + if err != nil { + return false, 0, fmt.Errorf("gas must be either integer or %s", gasFlagAuto) + } + + return false, gas, nil + } +} diff --git a/client/v2/tx/signature.go b/client/v2/tx/signature.go new file mode 100644 index 00000000000..66235380072 --- /dev/null +++ b/client/v2/tx/signature.go @@ -0,0 +1,197 @@ +package tx + +import ( + "errors" + "fmt" + + apicrypto "cosmossdk.io/api/cosmos/crypto/multisig/v1beta1" + apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + apitx "cosmossdk.io/api/cosmos/tx/v1beta1" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" +) + +// Signature holds the necessary components to verify transaction signatures. +type Signature struct { + PubKey cryptotypes.PubKey // Public key for signature verification. + Data SignatureData // Signature data containing the actual signatures. + Sequence uint64 // Account sequence, relevant for SIGN_MODE_DIRECT. +} + +// SignatureData defines an interface for different signature data types. +type SignatureData interface { + isSignatureData() +} + +// SingleSignatureData stores a single signer's signature and its mode. +type SingleSignatureData struct { + SignMode apitxsigning.SignMode // Mode of the signature. + Signature []byte // Actual binary signature. +} + +// MultiSignatureData encapsulates signatures from a multisig transaction. +type MultiSignatureData struct { + BitArray *apicrypto.CompactBitArray // Bitmap of signers. + Signatures []SignatureData // Individual signatures. +} + +func (m *SingleSignatureData) isSignatureData() {} +func (m *MultiSignatureData) isSignatureData() {} + +// signatureDataToModeInfoAndSig converts SignatureData to ModeInfo and its corresponding raw signature. +func signatureDataToModeInfoAndSig(data SignatureData) (*apitx.ModeInfo, []byte) { + if data == nil { + return nil, nil + } + + switch data := data.(type) { + case *SingleSignatureData: + return &apitx.ModeInfo{ + Sum: &apitx.ModeInfo_Single_{ + Single: &apitx.ModeInfo_Single{Mode: data.SignMode}, + }, + }, data.Signature + case *MultiSignatureData: + modeInfos := make([]*apitx.ModeInfo, len(data.Signatures)) + sigs := make([][]byte, len(data.Signatures)) + + for i, d := range data.Signatures { + modeInfos[i], sigs[i] = signatureDataToModeInfoAndSig(d) + } + + multisig := cryptotypes.MultiSignature{Signatures: sigs} + sig, err := multisig.Marshal() + if err != nil { + panic(err) + } + + return &apitx.ModeInfo{ + Sum: &apitx.ModeInfo_Multi_{ + Multi: &apitx.ModeInfo_Multi{ + Bitarray: data.BitArray, + ModeInfos: modeInfos, + }, + }, + }, sig + default: + panic(fmt.Sprintf("unexpected signature data type %T", data)) + } +} + +// modeInfoAndSigToSignatureData converts ModeInfo and a raw signature to SignatureData. +func modeInfoAndSigToSignatureData(modeInfo *apitx.ModeInfo, sig []byte) (SignatureData, error) { + switch mi := modeInfo.Sum.(type) { + case *apitx.ModeInfo_Single_: + return &SingleSignatureData{ + SignMode: mi.Single.Mode, + Signature: sig, + }, nil + + case *apitx.ModeInfo_Multi_: + multi := mi.Multi + + sigs, err := decodeMultiSignatures(sig) + if err != nil { + return nil, err + } + + sigsV2 := make([]SignatureData, len(sigs)) + for i, mi := range multi.ModeInfos { + sigsV2[i], err = modeInfoAndSigToSignatureData(mi, sigs[i]) + if err != nil { + return nil, err + } + } + return &MultiSignatureData{ + BitArray: multi.Bitarray, + Signatures: sigsV2, + }, nil + } + + return nil, fmt.Errorf("unsupported ModeInfo type %T", modeInfo) +} + +// decodeMultiSignatures decodes a byte array into individual signatures. +func decodeMultiSignatures(bz []byte) ([][]byte, error) { + multisig := cryptotypes.MultiSignature{} + + err := multisig.Unmarshal(bz) + if err != nil { + return nil, err + } + + if len(multisig.XXX_unrecognized) > 0 { + return nil, errors.New("unrecognized fields in MultiSignature") + } + return multisig.Signatures, nil +} + +// signatureDataToProto converts a SignatureData interface to a protobuf SignatureDescriptor_Data. +// This function supports both SingleSignatureData and MultiSignatureData types. +// For SingleSignatureData, it directly maps the signature mode and signature bytes to the protobuf structure. +// For MultiSignatureData, it recursively converts each signature in the collection to the corresponding protobuf structure. +func signatureDataToProto(data SignatureData) (*apitxsigning.SignatureDescriptor_Data, error) { + switch data := data.(type) { + case *SingleSignatureData: + // Handle single signature data conversion. + return &apitxsigning.SignatureDescriptor_Data{ + Sum: &apitxsigning.SignatureDescriptor_Data_Single_{ + Single: &apitxsigning.SignatureDescriptor_Data_Single{ + Mode: data.SignMode, + Signature: data.Signature, + }, + }, + }, nil + case *MultiSignatureData: + var err error + descDatas := make([]*apitxsigning.SignatureDescriptor_Data, len(data.Signatures)) + + for i, j := range data.Signatures { + descDatas[i], err = signatureDataToProto(j) + if err != nil { + return nil, err + } + } + return &apitxsigning.SignatureDescriptor_Data{ + Sum: &apitxsigning.SignatureDescriptor_Data_Multi_{ + Multi: &apitxsigning.SignatureDescriptor_Data_Multi{ + Bitarray: data.BitArray, + Signatures: descDatas, + }, + }, + }, nil + } + + // Return an error if the data type is not supported. + return nil, fmt.Errorf("unexpected signature data type %T", data) +} + +// SignatureDataFromProto converts a protobuf SignatureDescriptor_Data to a SignatureData interface. +// This function supports both Single and Multi signature data types. +func SignatureDataFromProto(descData *apitxsigning.SignatureDescriptor_Data) (SignatureData, error) { + switch descData := descData.Sum.(type) { + case *apitxsigning.SignatureDescriptor_Data_Single_: + return &SingleSignatureData{ + SignMode: descData.Single.Mode, + Signature: descData.Single.Signature, + }, nil + case *apitxsigning.SignatureDescriptor_Data_Multi_: + var err error + multi := descData.Multi + data := make([]SignatureData, len(multi.Signatures)) + + for i, j := range multi.Signatures { + data[i], err = SignatureDataFromProto(j) + if err != nil { + return nil, err + } + } + + return &MultiSignatureData{ + BitArray: multi.Bitarray, + Signatures: data, + }, nil + } + + return nil, fmt.Errorf("unexpected signature data type %T", descData) +} diff --git a/client/v2/tx/signature_test.go b/client/v2/tx/signature_test.go new file mode 100644 index 00000000000..dd78add38d3 --- /dev/null +++ b/client/v2/tx/signature_test.go @@ -0,0 +1,143 @@ +package tx + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/require" + + apimultisig "cosmossdk.io/api/cosmos/crypto/multisig/v1beta1" + apisigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + apitx "cosmossdk.io/api/cosmos/tx/v1beta1" +) + +func TestSignatureDataToModeInfoAndSig(t *testing.T) { + tests := []struct { + name string + data SignatureData + mIResult *apitx.ModeInfo + sigResult []byte + }{ + { + name: "single signature", + data: &SingleSignatureData{ + SignMode: apisigning.SignMode_SIGN_MODE_DIRECT, + Signature: []byte("signature"), + }, + mIResult: &apitx.ModeInfo{ + Sum: &apitx.ModeInfo_Single_{ + Single: &apitx.ModeInfo_Single{Mode: apisigning.SignMode_SIGN_MODE_DIRECT}, + }, + }, + sigResult: []byte("signature"), + }, + { + name: "multi signature", + data: &MultiSignatureData{ + BitArray: nil, + Signatures: []SignatureData{ + &SingleSignatureData{ + SignMode: apisigning.SignMode_SIGN_MODE_DIRECT, + Signature: []byte("signature"), + }, + }, + }, + mIResult: &apitx.ModeInfo{ + Sum: &apitx.ModeInfo_Multi_{ + Multi: &apitx.ModeInfo_Multi{ + Bitarray: nil, + ModeInfos: []*apitx.ModeInfo{ + { + Sum: &apitx.ModeInfo_Single_{ + Single: &apitx.ModeInfo_Single{Mode: apisigning.SignMode_SIGN_MODE_DIRECT}, + }, + }, + }, + }, + }, + }, + sigResult: []byte("\n\tsignature"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + modeInfo, signature := signatureDataToModeInfoAndSig(tt.data) + require.Equal(t, tt.mIResult, modeInfo) + require.Equal(t, tt.sigResult, signature) + }) + } +} + +func TestModeInfoAndSigToSignatureData(t *testing.T) { + type args struct { + modeInfo func() *apitx.ModeInfo + sig []byte + } + tests := []struct { + name string + args args + want SignatureData + wantErr bool + }{ + { + name: "to SingleSignatureData", + args: args{ + modeInfo: func() *apitx.ModeInfo { + return &apitx.ModeInfo{ + Sum: &apitx.ModeInfo_Single_{ + Single: &apitx.ModeInfo_Single{Mode: apisigning.SignMode_SIGN_MODE_DIRECT}, + }, + } + }, + sig: []byte("signature"), + }, + want: &SingleSignatureData{ + SignMode: apisigning.SignMode_SIGN_MODE_DIRECT, + Signature: []byte("signature"), + }, + }, + { + name: "to MultiSignatureData", + args: args{ + modeInfo: func() *apitx.ModeInfo { + return &apitx.ModeInfo{ + Sum: &apitx.ModeInfo_Multi_{ + Multi: &apitx.ModeInfo_Multi{ + Bitarray: &apimultisig.CompactBitArray{}, + ModeInfos: []*apitx.ModeInfo{ + { + Sum: &apitx.ModeInfo_Single_{ + Single: &apitx.ModeInfo_Single{Mode: apisigning.SignMode_SIGN_MODE_DIRECT}, + }, + }, + }, + }, + }, + } + }, + sig: []byte("\n\tsignature"), + }, + want: &MultiSignatureData{ // Changed from SingleSignatureData to MultiSignatureData + BitArray: &apimultisig.CompactBitArray{}, + Signatures: []SignatureData{ + &SingleSignatureData{ + SignMode: apisigning.SignMode_SIGN_MODE_DIRECT, + Signature: []byte("signature"), + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := modeInfoAndSigToSignatureData(tt.args.modeInfo(), tt.args.sig) + if (err != nil) != tt.wantErr { + t.Errorf("ModeInfoAndSigToSignatureData() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ModeInfoAndSigToSignatureData() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/client/v2/tx/tx.go b/client/v2/tx/tx.go new file mode 100644 index 00000000000..64e0199d817 --- /dev/null +++ b/client/v2/tx/tx.go @@ -0,0 +1,229 @@ +package tx + +import ( + "bufio" + "errors" + "fmt" + "os" + + "github.com/cosmos/gogoproto/proto" + "github.com/spf13/pflag" + + apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + "cosmossdk.io/client/v2/internal/account" + "cosmossdk.io/core/transaction" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/input" + "github.com/cosmos/cosmos-sdk/crypto/keyring" +) + +// GenerateOrBroadcastTxCLI will either generate and print an unsigned transaction +// or sign it and broadcast it returning an error upon failure. +func GenerateOrBroadcastTxCLI(ctx client.Context, flagSet *pflag.FlagSet, msgs ...transaction.Msg) error { + if err := validateMessages(msgs...); err != nil { + return err + } + + txf, err := newFactory(ctx, flagSet) + if err != nil { + return err + } + + genOnly, _ := flagSet.GetBool(flagGenerateOnly) + if genOnly { + return generateOnly(ctx, txf, msgs...) + } + + isDryRun, _ := flagSet.GetBool(flagDryRun) + if isDryRun { + return dryRun(txf, msgs...) + } + + return BroadcastTx(ctx, txf, msgs...) +} + +// newFactory creates a new transaction Factory based on the provided context and flag set. +// It initializes a new CLI keyring, extracts transaction parameters from the flag set, +// configures transaction settings, and sets up an account retriever for the transaction Factory. +func newFactory(ctx client.Context, flagSet *pflag.FlagSet) (Factory, error) { + k, err := keyring.NewAutoCLIKeyring(ctx.Keyring, ctx.AddressCodec) + if err != nil { + return Factory{}, err + } + + txConfig, err := NewTxConfig(ConfigOptions{ + AddressCodec: ctx.AddressCodec, + Cdc: ctx.Codec, + ValidatorAddressCodec: ctx.ValidatorAddressCodec, + EnablesSignModes: ctx.TxConfig.SignModeHandler().SupportedModes(), + }) + if err != nil { + return Factory{}, err + } + + accRetriever := account.NewAccountRetriever(ctx.AddressCodec, ctx, ctx.InterfaceRegistry) + + txf, err := NewFactoryFromFlagSet(flagSet, k, ctx.Codec, accRetriever, txConfig, ctx.AddressCodec, ctx) + if err != nil { + return Factory{}, err + } + + return txf, nil +} + +// validateMessages validates all msgs before generating or broadcasting the tx. +// We were calling ValidateBasic separately in each CLI handler before. +// Right now, we're factorizing that call inside this function. +// ref: https://github.com/cosmos/cosmos-sdk/pull/9236#discussion_r623803504 +func validateMessages(msgs ...transaction.Msg) error { + for _, msg := range msgs { + m, ok := msg.(HasValidateBasic) + if !ok { + continue + } + + if err := m.ValidateBasic(); err != nil { + return err + } + } + + return nil +} + +// generateOnly prepares the transaction and prints the unsigned transaction string. +// It first calls Prepare on the transaction factory to set up any necessary pre-conditions. +// If preparation is successful, it generates an unsigned transaction string using the provided messages. +func generateOnly(ctx client.Context, txf Factory, msgs ...transaction.Msg) error { + uTx, err := txf.UnsignedTxString(msgs...) + if err != nil { + return err + } + + return ctx.PrintString(uTx) +} + +// dryRun performs a dry run of the transaction to estimate the gas required. +// It prepares the transaction factory and simulates the transaction with the provided messages. +func dryRun(txf Factory, msgs ...transaction.Msg) error { + _, gas, err := txf.Simulate(msgs...) + if err != nil { + return err + } + + _, err = fmt.Fprintf(os.Stderr, "%s\n", GasEstimateResponse{GasEstimate: gas}) + return err +} + +// SimulateTx simulates a tx and returns the simulation response obtained by the query. +func SimulateTx(ctx client.Context, flagSet *pflag.FlagSet, msgs ...transaction.Msg) (proto.Message, error) { + txf, err := newFactory(ctx, flagSet) + if err != nil { + return nil, err + } + + simulation, _, err := txf.Simulate(msgs...) + return simulation, err +} + +// BroadcastTx attempts to generate, sign and broadcast a transaction with the +// given set of messages. It will also simulate gas requirements if necessary. +// It will return an error upon failure. +func BroadcastTx(clientCtx client.Context, txf Factory, msgs ...transaction.Msg) error { + if txf.simulateAndExecute() { + err := txf.calculateGas(msgs...) + if err != nil { + return err + } + } + + err := txf.BuildUnsignedTx(msgs...) + if err != nil { + return err + } + + if !clientCtx.SkipConfirm { + encoder := txf.txConfig.TxJSONEncoder() + if encoder == nil { + return errors.New("failed to encode transaction: tx json encoder is nil") + } + + unsigTx, err := txf.getTx() + if err != nil { + return err + } + txBytes, err := encoder(unsigTx) + if err != nil { + return fmt.Errorf("failed to encode transaction: %w", err) + } + + if err := clientCtx.PrintRaw(txBytes); err != nil { + _, _ = fmt.Fprintf(os.Stderr, "error: %v\n%s\n", err, txBytes) + } + + buf := bufio.NewReader(os.Stdin) + ok, err := input.GetConfirmation("confirm transaction before signing and broadcasting", buf, os.Stderr) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "error: %v\ncanceled transaction\n", err) + return err + } + if !ok { + _, _ = fmt.Fprintln(os.Stderr, "canceled transaction") + return nil + } + } + + signedTx, err := txf.sign(clientCtx.CmdContext, true) + if err != nil { + return err + } + + txBytes, err := txf.txConfig.TxEncoder()(signedTx) + if err != nil { + return err + } + + // broadcast to a CometBFT node + res, err := clientCtx.BroadcastTx(txBytes) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) +} + +// countDirectSigners counts the number of DIRECT signers in a signature data. +func countDirectSigners(sigData SignatureData) int { + switch data := sigData.(type) { + case *SingleSignatureData: + if data.SignMode == apitxsigning.SignMode_SIGN_MODE_DIRECT { + return 1 + } + + return 0 + case *MultiSignatureData: + directSigners := 0 + for _, d := range data.Signatures { + directSigners += countDirectSigners(d) + } + + return directSigners + default: + panic("unreachable case") + } +} + +// getSignMode returns the corresponding apitxsigning.SignMode based on the provided mode string. +func getSignMode(mode string) apitxsigning.SignMode { + switch mode { + case "direct": + return apitxsigning.SignMode_SIGN_MODE_DIRECT + case "direct-aux": + return apitxsigning.SignMode_SIGN_MODE_DIRECT_AUX + case "amino-json": + return apitxsigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON + case "textual": + return apitxsigning.SignMode_SIGN_MODE_TEXTUAL + } + return apitxsigning.SignMode_SIGN_MODE_UNSPECIFIED +} diff --git a/client/v2/tx/types.go b/client/v2/tx/types.go new file mode 100644 index 00000000000..ee60c27065b --- /dev/null +++ b/client/v2/tx/types.go @@ -0,0 +1,214 @@ +package tx + +import ( + "fmt" + "time" + + "github.com/spf13/pflag" + "google.golang.org/protobuf/types/known/anypb" + + base "cosmossdk.io/api/cosmos/base/v1beta1" + apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + apitx "cosmossdk.io/api/cosmos/tx/v1beta1" + keyring2 "cosmossdk.io/client/v2/autocli/keyring" + "cosmossdk.io/client/v2/internal/coins" + "cosmossdk.io/core/address" + "cosmossdk.io/core/transaction" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" +) + +// HasValidateBasic is a copy of types.HasValidateBasic to avoid sdk import. +type HasValidateBasic interface { + // ValidateBasic does a simple validation check that + // doesn't require access to any other information. + ValidateBasic() error +} + +// TxParameters defines the parameters required for constructing a transaction. +type TxParameters struct { + timeoutTimestamp time.Time // timeoutTimestamp indicates a timestamp after which the transaction is no longer valid. + chainID string // chainID specifies the unique identifier of the blockchain where the transaction will be processed. + memo string // memo contains any arbitrary memo to be attached to the transaction. + signMode apitxsigning.SignMode // signMode determines the signing mode to be used for the transaction. + + AccountConfig // AccountConfig includes information about the transaction originator's account. + GasConfig // GasConfig specifies the gas settings for the transaction. + FeeConfig // FeeConfig details the fee associated with the transaction. + ExecutionOptions // ExecutionOptions includes settings that modify how the transaction is executed. +} + +// AccountConfig defines the 'account' related fields in a transaction. +type AccountConfig struct { + // accountNumber is the unique identifier for the account. + accountNumber uint64 + // sequence is the sequence number of the transaction. + sequence uint64 + // fromName is the name of the account sending the transaction. + fromName string + // fromAddress is the address of the account sending the transaction. + fromAddress string + // address is the byte representation of the account address. + address []byte +} + +// GasConfig defines the 'gas' related fields in a transaction. +// GasConfig defines the gas-related settings for a transaction. +type GasConfig struct { + gas uint64 // gas is the amount of gas requested for the transaction. + gasAdjustment float64 // gasAdjustment is the factor by which the estimated gas is multiplied to calculate the final gas limit. + gasPrices []*base.DecCoin // gasPrices is a list of denominations of DecCoin used to calculate the fee paid for the gas. +} + +// NewGasConfig creates a new GasConfig with the specified gas, gasAdjustment, and gasPrices. +// If the provided gas value is zero, it defaults to a predefined value (defaultGas). +// The gasPrices string is parsed into a slice of DecCoin. +func NewGasConfig(gas uint64, gasAdjustment float64, gasPrices string) (GasConfig, error) { + parsedGasPrices, err := coins.ParseDecCoins(gasPrices) + if err != nil { + return GasConfig{}, err + } + + return GasConfig{ + gas: gas, + gasAdjustment: gasAdjustment, + gasPrices: parsedGasPrices, + }, nil +} + +// FeeConfig holds the fee details for a transaction. +type FeeConfig struct { + fees []*base.Coin // fees are the amounts paid for the transaction. + feePayer string // feePayer is the account responsible for paying the fees. + feeGranter string // feeGranter is the account granting the fee payment if different from the payer. +} + +// NewFeeConfig creates a new FeeConfig with the specified fees, feePayer, and feeGranter. +// It parses the fees string into a slice of Coin, handling normalization. +func NewFeeConfig(fees, feePayer, feeGranter string) (FeeConfig, error) { + parsedFees, err := coins.ParseCoinsNormalized(fees) + if err != nil { + return FeeConfig{}, err + } + + return FeeConfig{ + fees: parsedFees, + feePayer: feePayer, + feeGranter: feeGranter, + }, nil +} + +// ExecutionOptions defines the transaction execution options ran by the client +type ExecutionOptions struct { + unordered bool // unordered indicates if the transaction execution order is not guaranteed. + simulateAndExecute bool // simulateAndExecute indicates if the transaction should be simulated before execution. +} + +// GasEstimateResponse defines a response definition for tx gas estimation. +type GasEstimateResponse struct { + GasEstimate uint64 `json:"gas_estimate" yaml:"gas_estimate"` +} + +func (gr GasEstimateResponse) String() string { + return fmt.Sprintf("gas estimate: %d", gr.GasEstimate) +} + +// txState represents the internal state of a transaction. +type txState struct { + msgs []transaction.Msg + timeoutHeight uint64 + timeoutTimestamp time.Time + granter []byte + payer []byte + unordered bool + memo string + gasLimit uint64 + fees []*base.Coin + signerInfos []*apitx.SignerInfo + signatures [][]byte + + extensionOptions []*anypb.Any + nonCriticalExtensionOptions []*anypb.Any +} + +// Tx defines the interface for transaction operations. +type Tx interface { + transaction.Tx + + // GetSigners fetches the addresses of the signers of the transaction. + GetSigners() ([][]byte, error) + // GetPubKeys retrieves the public keys of the signers of the transaction. + GetPubKeys() ([]cryptotypes.PubKey, error) + // GetSignatures fetches the signatures attached to the transaction. + GetSignatures() ([]Signature, error) +} + +// txParamsFromFlagSet extracts the transaction parameters from the provided FlagSet. +func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring, ac address.Codec) (params TxParameters, err error) { + timestampUnix, _ := flags.GetInt64(flagTimeoutTimestamp) + timeoutTimestamp := time.Unix(timestampUnix, 0) + chainID, _ := flags.GetString(flagChainID) + memo, _ := flags.GetString(flagNote) + signMode, _ := flags.GetString(flagSignMode) + + accNumber, _ := flags.GetUint64(flagAccountNumber) + sequence, _ := flags.GetUint64(flagSequence) + from, _ := flags.GetString(flagFrom) + + var fromName, fromAddress string + var addr []byte + isDryRun, _ := flags.GetBool(flagDryRun) + if isDryRun { + addr, err = ac.StringToBytes(from) + } else { + fromName, fromAddress, _, err = keybase.KeyInfo(from) + if err == nil { + addr, err = ac.StringToBytes(fromAddress) + } + } + if err != nil { + return params, err + } + + gas, _ := flags.GetString(flagGas) + simulate, gasValue, _ := parseGasSetting(gas) + gasAdjustment, _ := flags.GetFloat64(flagGasAdjustment) + gasPrices, _ := flags.GetString(flagGasPrices) + + fees, _ := flags.GetString(flagFees) + feePayer, _ := flags.GetString(flagFeePayer) + feeGrater, _ := flags.GetString(flagFeeGranter) + + unordered, _ := flags.GetBool(flagUnordered) + + gasConfig, err := NewGasConfig(gasValue, gasAdjustment, gasPrices) + if err != nil { + return params, err + } + feeConfig, err := NewFeeConfig(fees, feePayer, feeGrater) + if err != nil { + return params, err + } + + txParams := TxParameters{ + timeoutTimestamp: timeoutTimestamp, + chainID: chainID, + memo: memo, + signMode: getSignMode(signMode), + AccountConfig: AccountConfig{ + accountNumber: accNumber, + sequence: sequence, + fromName: fromName, + fromAddress: fromAddress, + address: addr, + }, + GasConfig: gasConfig, + FeeConfig: feeConfig, + ExecutionOptions: ExecutionOptions{ + unordered: unordered, + simulateAndExecute: simulate, + }, + } + + return txParams, nil +} diff --git a/client/v2/tx/wrapper.go b/client/v2/tx/wrapper.go new file mode 100644 index 00000000000..fbcf62126bf --- /dev/null +++ b/client/v2/tx/wrapper.go @@ -0,0 +1,115 @@ +package tx + +import ( + "fmt" + "reflect" + "strings" + + "github.com/cosmos/gogoproto/proto" + "google.golang.org/protobuf/types/known/anypb" + + "cosmossdk.io/core/transaction" + "cosmossdk.io/x/tx/decode" + + "github.com/cosmos/cosmos-sdk/codec" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" +) + +var ( + _ transaction.Tx = wrappedTx{} + _ Tx = wrappedTx{} +) + +// wrappedTx wraps a transaction and provides a codec for binary encoding/decoding. +type wrappedTx struct { + *decode.DecodedTx + + cdc codec.BinaryCodec +} + +func newWrapperTx(cdc codec.BinaryCodec, decodedTx *decode.DecodedTx) *wrappedTx { + return &wrappedTx{ + DecodedTx: decodedTx, + cdc: cdc, + } +} + +// GetSigners fetches the addresses of the signers of the transaction. +func (w wrappedTx) GetSigners() ([][]byte, error) { + return w.Signers, nil +} + +// GetPubKeys retrieves the public keys of the signers from the transaction's SignerInfos. +func (w wrappedTx) GetPubKeys() ([]cryptotypes.PubKey, error) { + signerInfos := w.Tx.AuthInfo.SignerInfos + pks := make([]cryptotypes.PubKey, len(signerInfos)) + + for i, si := range signerInfos { + // NOTE: it is okay to leave this nil if there is no PubKey in the SignerInfo. + // PubKey's can be left unset in SignerInfo. + if si.PublicKey == nil { + continue + } + maybePk, err := w.decodeAny(si.PublicKey) + if err != nil { + return nil, err + } + pk, ok := maybePk.(cryptotypes.PubKey) + if !ok { + return nil, fmt.Errorf("invalid public key type: %T", maybePk) + } + pks[i] = pk + } + + return pks, nil +} + +// GetSignatures fetches the signatures attached to the transaction. +func (w wrappedTx) GetSignatures() ([]Signature, error) { + signerInfos := w.Tx.AuthInfo.SignerInfos + sigs := w.Tx.Signatures + + pubKeys, err := w.GetPubKeys() + if err != nil { + return nil, err + } + signatures := make([]Signature, len(sigs)) + + for i, si := range signerInfos { + if si.ModeInfo == nil || si.ModeInfo.Sum == nil { + signatures[i] = Signature{ + PubKey: pubKeys[i], + } + } else { + sigData, err := modeInfoAndSigToSignatureData(si.ModeInfo, sigs[i]) + if err != nil { + return nil, err + } + signatures[i] = Signature{ + PubKey: pubKeys[i], + Data: sigData, + Sequence: si.GetSequence(), + } + } + } + + return signatures, nil +} + +// decodeAny decodes a protobuf Any message into a concrete proto.Message. +func (w wrappedTx) decodeAny(anyPb *anypb.Any) (proto.Message, error) { + name := anyPb.GetTypeUrl() + if i := strings.LastIndexByte(name, '/'); i >= 0 { + name = name[i+len("/"):] + } + typ := proto.MessageType(name) + if typ == nil { + return nil, fmt.Errorf("unknown type: %s", name) + } + v1 := reflect.New(typ.Elem()).Interface().(proto.Message) + err := w.cdc.Unmarshal(anyPb.GetValue(), v1) + if err != nil { + return nil, err + } + return v1, nil +} diff --git a/codec/unknownproto/unknown_fields.go b/codec/unknownproto/unknown_fields.go index 5fa80001c0f..07759456e29 100644 --- a/codec/unknownproto/unknown_fields.go +++ b/codec/unknownproto/unknown_fields.go @@ -222,7 +222,7 @@ var checks = [...]map[descriptorpb.FieldDescriptorProto_Type]bool{ descriptorpb.FieldDescriptorProto_TYPE_MESSAGE: true, // The following types can be packed repeated. // ref: "Only repeated fields of primitive numeric types (types which use the varint, 32-bit, or 64-bit wire types) can be declared "packed"." - // ref: https://developers.google.com/protocol-buffers/docs/encoding#packed + // ref: https://protobuf.dev/programming-guides/encoding/#packed descriptorpb.FieldDescriptorProto_TYPE_INT32: true, descriptorpb.FieldDescriptorProto_TYPE_INT64: true, descriptorpb.FieldDescriptorProto_TYPE_UINT32: true, @@ -255,7 +255,7 @@ var checks = [...]map[descriptorpb.FieldDescriptorProto_Type]bool{ } // canEncodeType returns true if the wireType is suitable for encoding the descriptor type. -// See https://developers.google.com/protocol-buffers/docs/encoding#structure. +// See https://protobuf.dev/programming-guides/encoding/#structure. func canEncodeType(wireType protowire.Type, descType descriptorpb.FieldDescriptorProto_Type) bool { if iwt := int(wireType); iwt < 0 || iwt >= len(checks) { return false diff --git a/collections/go.mod b/collections/go.mod index 5a491c211e2..e1d9dcef95d 100644 --- a/collections/go.mod +++ b/collections/go.mod @@ -3,7 +3,7 @@ module cosmossdk.io/collections go 1.23 require ( - cosmossdk.io/core v1.0.0-alpha.3 + cosmossdk.io/core v1.0.0-alpha.4 cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29 cosmossdk.io/schema v0.3.0 github.com/stretchr/testify v1.9.0 diff --git a/collections/go.sum b/collections/go.sum index 5978e559555..ad552827598 100644 --- a/collections/go.sum +++ b/collections/go.sum @@ -1,5 +1,5 @@ -cosmossdk.io/core v1.0.0-alpha.3 h1:pnxaYAas7llXgVz1lM7X6De74nWrhNKnB3yMKe4OUUA= -cosmossdk.io/core v1.0.0-alpha.3/go.mod h1:3u9cWq1FAVtiiCrDPpo4LhR+9V6k/ycSG4/Y/tREWCY= +cosmossdk.io/core v1.0.0-alpha.4 h1:9iuroT9ejDYETCsGkzkvs/wAY/5UFl7nCIINFRxyMJY= +cosmossdk.io/core v1.0.0-alpha.4/go.mod h1:3u9cWq1FAVtiiCrDPpo4LhR+9V6k/ycSG4/Y/tREWCY= cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29 h1:NxxUo0GMJUbIuVg0R70e3cbn9eFTEuMr7ev1AFvypdY= cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29/go.mod h1:8s2tPeJtSiQuoyPmr2Ag7meikonISO4Fv4MoO8+ORrs= cosmossdk.io/schema v0.3.0 h1:01lcaM4trhzZ1HQTfTV8z6Ma1GziOZ/YmdzBN3F720c= diff --git a/core/testing/go.mod b/core/testing/go.mod index 8c20811c6c9..95f3b6a38c5 100644 --- a/core/testing/go.mod +++ b/core/testing/go.mod @@ -3,7 +3,7 @@ module cosmossdk.io/core/testing go 1.23 require ( - cosmossdk.io/core v1.0.0-alpha.3 + cosmossdk.io/core v1.0.0-alpha.4 github.com/golang/mock v1.6.0 github.com/tidwall/btree v1.7.0 ) diff --git a/core/testing/go.sum b/core/testing/go.sum index d1db25cac6f..9fc6d98fad4 100644 --- a/core/testing/go.sum +++ b/core/testing/go.sum @@ -1,5 +1,5 @@ -cosmossdk.io/core v1.0.0-alpha.3 h1:pnxaYAas7llXgVz1lM7X6De74nWrhNKnB3yMKe4OUUA= -cosmossdk.io/core v1.0.0-alpha.3/go.mod h1:3u9cWq1FAVtiiCrDPpo4LhR+9V6k/ycSG4/Y/tREWCY= +cosmossdk.io/core v1.0.0-alpha.4 h1:9iuroT9ejDYETCsGkzkvs/wAY/5UFl7nCIINFRxyMJY= +cosmossdk.io/core v1.0.0-alpha.4/go.mod h1:3u9cWq1FAVtiiCrDPpo4LhR+9V6k/ycSG4/Y/tREWCY= cosmossdk.io/schema v0.3.0 h1:01lcaM4trhzZ1HQTfTV8z6Ma1GziOZ/YmdzBN3F720c= cosmossdk.io/schema v0.3.0/go.mod h1:RDAhxIeNB4bYqAlF4NBJwRrgtnciMcyyg0DOKnhNZQQ= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= diff --git a/crypto/README.md b/crypto/README.md new file mode 100644 index 00000000000..269d840c6e8 --- /dev/null +++ b/crypto/README.md @@ -0,0 +1,53 @@ +# Crypto + +The `crypto` directory contains the components responsible for handling cryptographic operations, key management, and secure interactions with hardware wallets. + +## Components + +### Keyring + +Keyring is the primary interface for managing cryptographic keys. It provides a unified API to create, store, retrieve, and manage keys securely across different storage backends. + +#### Supported Backends + +* **OS**: Uses the operating system's default credential store. +* **File**: Stores encrypted keyring in the application's configuration directory. +* **KWallet**: Integrates with KDE Wallet Manager. +* **Pass**: Leverages the `pass` command-line utility. +* **Keyctl**: Uses Linux's kernel security key management system. +* **Test**: Stores (insecurely) keys to disk for testing purposes. +* **Memory**: Provides transient storage where keys are discarded when the process terminates. + +### Codec + +The Codec component handles serialization and deserialization of cryptographic structures in the crypto package. It ensures proper encoding of keys, signatures, and other artifacts for storage and transmission. The Codec also manages conversion between CometBFT and SDK key formats. + +### Ledger Integration + +Support for Ledger hardware wallets is integrated to provide enhanced security for key management and signing operations. The Ledger integration supports SECP256K1 keys and offers various features: + +#### Key Features + +* **Public Key Retrieval**: Supports both safe (with user verification) and unsafe (without user verification) methods to retrieve public keys from the Ledger device. +* **Address Generation**: Can generate and display Bech32 addresses on the Ledger device for user verification. +* **Transaction Signing**: Allows signing of transactions with user confirmation on the Ledger device. +* **Multiple HD Path Support**: Supports various BIP44 derivation paths for key generation and management. +* **Customizable Options**: Provides options to customize Ledger usage, including app name, public key creation, and DER to BER signature conversion. + +#### Implementation Details + +* The integration is built to work with or without CGO. +* It includes a mock implementation for testing purposes, which can be enabled with the `test_ledger_mock` build tag. +* The real Ledger device interaction is implemented when the `ledger` build tag is used. +* The integration supports both SIGN_MODE_LEGACY_AMINO_JSON and SIGN_MODE_TEXTUAL signing modes. + +#### Usage Considerations + +* Ledger support requires the appropriate Cosmos app to be installed and opened on the Ledger device. +* The integration includes safeguards to prevent key overwriting and ensures that the correct device and app are being used. + +#### Security Notes + +* The integration includes methods to validate keys and addresses with user confirmation on the Ledger device. +* It's recommended to use the safe methods that require user verification for critical operations like key generation and address display. +* The mock implementation should only be used for testing and development purposes, not in production environments. \ No newline at end of file diff --git a/crypto/codec/pubkey.go b/crypto/codec/pubkey.go new file mode 100644 index 00000000000..c45f258517b --- /dev/null +++ b/crypto/codec/pubkey.go @@ -0,0 +1,53 @@ +package codec + +import ( + "cosmossdk.io/errors" + + cryptokeys "github.com/cosmos/cosmos-sdk/crypto/keys" + bls12_381 "github.com/cosmos/cosmos-sdk/crypto/keys/bls12_381" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +func PubKeyToProto(pk cryptokeys.JSONPubkey) (cryptotypes.PubKey, error) { + switch pk.KeyType { + case ed25519.PubKeyName: + return &ed25519.PubKey{ + Key: pk.Value, + }, nil + case secp256k1.PubKeyName: + return &secp256k1.PubKey{ + Key: pk.Value, + }, nil + case bls12_381.PubKeyName: + return &bls12_381.PubKey{ + Key: pk.Value, + }, nil + default: + return nil, errors.Wrapf(sdkerrors.ErrInvalidType, "cannot convert %v to proto public key", pk) + } +} + +func PubKeyFromProto(pk cryptotypes.PubKey) (cryptokeys.JSONPubkey, error) { + switch pk := pk.(type) { + case *ed25519.PubKey: + return cryptokeys.JSONPubkey{ + KeyType: ed25519.PubKeyName, + Value: pk.Bytes(), + }, nil + case *secp256k1.PubKey: + return cryptokeys.JSONPubkey{ + KeyType: secp256k1.PubKeyName, + Value: pk.Bytes(), + }, nil + case *bls12_381.PubKey: + return cryptokeys.JSONPubkey{ + KeyType: bls12_381.PubKeyName, + Value: pk.Bytes(), + }, nil + default: + return cryptokeys.JSONPubkey{}, errors.Wrapf(sdkerrors.ErrInvalidType, "cannot convert %v from proto public key", pk) + } +} diff --git a/crypto/keyring/autocli.go b/crypto/keyring/autocli.go index 0dd91ff60a4..26f39b3e436 100644 --- a/crypto/keyring/autocli.go +++ b/crypto/keyring/autocli.go @@ -2,6 +2,7 @@ package keyring import ( signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + "cosmossdk.io/core/address" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" @@ -21,15 +22,22 @@ type autoCLIKeyring interface { // Sign signs the given bytes with the key with the given name. Sign(name string, msg []byte, signMode signingv1beta1.SignMode) ([]byte, error) + + // KeyType returns the type of the key. + KeyType(name string) (uint, error) + + // KeyInfo given a key name or address returns key name, key address and key type. + KeyInfo(name string) (string, string, uint, error) } // NewAutoCLIKeyring wraps the SDK keyring and make it compatible with the AutoCLI keyring interfaces. -func NewAutoCLIKeyring(kr Keyring) (autoCLIKeyring, error) { - return &autoCLIKeyringAdapter{kr}, nil +func NewAutoCLIKeyring(kr Keyring, ac address.Codec) (autoCLIKeyring, error) { + return &autoCLIKeyringAdapter{kr, ac}, nil } type autoCLIKeyringAdapter struct { Keyring + ac address.Codec } func (a *autoCLIKeyringAdapter) List() ([]string, error) { @@ -84,3 +92,40 @@ func (a *autoCLIKeyringAdapter) Sign(name string, msg []byte, signMode signingv1 signBytes, _, err := a.Keyring.Sign(record.Name, msg, sdkSignMode) return signBytes, err } + +func (a *autoCLIKeyringAdapter) KeyType(name string) (uint, error) { + record, err := a.Keyring.Key(name) + if err != nil { + return 0, err + } + + return uint(record.GetType()), nil +} + +func (a *autoCLIKeyringAdapter) KeyInfo(nameOrAddr string) (string, string, uint, error) { + addr, err := a.ac.StringToBytes(nameOrAddr) + if err != nil { + // If conversion fails, it's likely a name, not an address + record, err := a.Keyring.Key(nameOrAddr) + if err != nil { + return "", "", 0, err + } + addr, err = record.GetAddress() + if err != nil { + return "", "", 0, err + } + addrStr, err := a.ac.BytesToString(addr) + if err != nil { + return "", "", 0, err + } + return record.Name, addrStr, uint(record.GetType()), nil + } + + // If conversion succeeds, it's an address, get the key info by address + record, err := a.Keyring.KeyByAddress(addr) + if err != nil { + return "", "", 0, err + } + + return record.Name, nameOrAddr, uint(record.GetType()), nil +} diff --git a/crypto/keys/jsonkey.go b/crypto/keys/jsonkey.go new file mode 100644 index 00000000000..8edbd91e724 --- /dev/null +++ b/crypto/keys/jsonkey.go @@ -0,0 +1,41 @@ +package keys + +import ( + "github.com/cosmos/cosmos-sdk/crypto/keys/bls12_381" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/crypto/types" +) + +// JSONPubKey defines a public key that are parse from JSON file. +// convert PubKey to JSONPubKey needs a in between step +type JSONPubkey struct { + KeyType string `json:"type"` + Value []byte `json:"value"` +} + +func (pk JSONPubkey) Address() types.Address { + switch pk.KeyType { + case ed25519.PubKeyName: + ed25519 := ed25519.PubKey{ + Key: pk.Value, + } + return ed25519.Address() + case secp256k1.PubKeyName: + secp256k1 := secp256k1.PubKey{ + Key: pk.Value, + } + return secp256k1.Address() + case bls12_381.PubKeyName: + bls12_381 := bls12_381.PubKey{ + Key: pk.Value, + } + return bls12_381.Address() + default: + return nil + } +} + +func (pk JSONPubkey) Bytes() []byte { + return pk.Value +} diff --git a/crypto/ledger/ledger_secp256k1.go b/crypto/ledger/ledger_secp256k1.go index 67dcc73981a..3449d0bfd1b 100644 --- a/crypto/ledger/ledger_secp256k1.go +++ b/crypto/ledger/ledger_secp256k1.go @@ -341,6 +341,12 @@ func getPubKeyUnsafe(device SECP256K1, path hd.BIP44Params) (types.PubKey, error func getPubKeyAddrSafe(device SECP256K1, path hd.BIP44Params, hrp string) (types.PubKey, string, error) { publicKey, addr, err := device.GetAddressPubKeySECP256K1(path.DerivationPath(), hrp) if err != nil { + // Check special case if user is trying to use an index > 100 + if path.AddressIndex > 100 { + return nil, "", fmt.Errorf("%w: cannot derive paths where index > 100: %s "+ + "This is a security measure to avoid very hard to find derivation paths introduced by a possible attacker. "+ + "You can disable this by setting expert mode in your ledger device. Do this at your own risk", err, path) + } return nil, "", fmt.Errorf("%w: address rejected for path %s", err, path) } diff --git a/depinject/go.mod b/depinject/go.mod index 12e18e6a5c5..b4da7af0110 100644 --- a/depinject/go.mod +++ b/depinject/go.mod @@ -8,7 +8,7 @@ require ( github.com/stretchr/testify v1.9.0 golang.org/x/exp v0.0.0-20231006140011-7918f672742d google.golang.org/grpc v1.67.1 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 gotest.tools/v3 v3.5.1 sigs.k8s.io/yaml v1.4.0 ) diff --git a/depinject/go.sum b/depinject/go.sum index 252c8bd7d02..84f26c7a7c8 100644 --- a/depinject/go.sum +++ b/depinject/go.sum @@ -50,8 +50,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/docs/architecture/PROCESS.md b/docs/architecture/PROCESS.md index bca4cdf51b1..15e11639c40 100644 --- a/docs/architecture/PROCESS.md +++ b/docs/architecture/PROCESS.md @@ -1,8 +1,8 @@ # ADR Creation Process 1. Copy the `adr-template.md` file. Use the following filename pattern: `adr-next_number-title.md` -2. Create a draft Pull Request if you want to get an early feedback. -3. Make sure the context and solution is clear and well documented. +2. Create a draft Pull Request if you want to get early feedback. +3. Make sure the context and solution are clear and well documented. 4. Add an entry to a list in the [README](./README.md) file. 5. Create a Pull Request to propose a new ADR. @@ -14,7 +14,7 @@ An ADR is a document to document an implementation and design that may or may no ADR creation is an **iterative** process. Instead of having a high amount of communication overhead, an ADR is used when there is already a decision made and implementation details need to be added. The ADR should document what the collective consensus for the specific issue is and how to solve it. -1. Every ADR should start with either an RFC or discussion where consensus has been met. +1. Every ADR should start with either an RFC or a discussion where consensus has been met. 2. Once consensus is met, a GitHub Pull Request (PR) is created with a new document based on the `adr-template.md`. @@ -44,9 +44,9 @@ DRAFT -> PROPOSED -> LAST CALL yyyy-mm-dd -> ACCEPTED | REJECTED -> SUPERSEDED b ABANDONED ``` -* `DRAFT`: [optional] an ADR which is work in progress, not being ready for a general review. This is to present an early work and get an early feedback in a Draft Pull Request form. -* `PROPOSED`: an ADR covering a full solution architecture and still in the review - project stakeholders haven't reached an agreed yet. -* `LAST CALL `: [optional] clear notify that we are close to accept updates. Changing a status to `LAST CALL` means that social consensus (of Cosmos SDK maintainers) has been reached and we still want to give it a time to let the community react or analyze. +* `DRAFT`: [optional] an ADR which is a work in progress, not being ready for a general review. This is to present an early work and get early feedback in a Draft Pull Request form. +* `PROPOSED`: an ADR covering a full solution architecture and still in the review - project stakeholders haven't reached an agreement yet. +* `LAST CALL `: [optional] Notify that we are close to accepting updates. Changing a status to `LAST CALL` means that social consensus (of Cosmos SDK maintainers) has been reached and we still want to give it a time to let the community react or analyze. * `ACCEPTED`: ADR which will represent a currently implemented or to be implemented architecture design. * `REJECTED`: ADR can go from PROPOSED or ACCEPTED to rejected if the consensus among project stakeholders will decide so. * `SUPERSEDED by ADR-xxx`: ADR which has been superseded by a new ADR. diff --git a/docs/architecture/adr-002-docs-structure.md b/docs/architecture/adr-002-docs-structure.md index 5819151fc1d..aea7d60e863 100644 --- a/docs/architecture/adr-002-docs-structure.md +++ b/docs/architecture/adr-002-docs-structure.md @@ -40,7 +40,7 @@ docs/ The files in each sub-folders do not matter and will likely change. What matters is the sectioning: * `README`: Landing page of the docs. -* `intro`: Introductory material. Goal is to have a short explainer of the Cosmos SDK and then channel people to the resource they need. The [Cosmos SDK tutorial](https://github.com/cosmos/sdk-application-tutorial/) will be highlighted, as well as the `godocs`. +* `intro`: Introductory material. Goal is to have a short explainer of the Cosmos SDK and then channel people to the resources they need. The [Cosmos SDK tutorial](https://github.com/cosmos/sdk-application-tutorial/) will be highlighted, as well as the `godocs`. * `concepts`: Contains high-level explanations of the abstractions of the Cosmos SDK. It does not contain specific code implementation and does not need to be updated often. **It is not an API specification of the interfaces**. API spec is the `godoc`. * `clients`: Contains specs and info about the various Cosmos SDK clients. * `spec`: Contains specs of modules, and others. @@ -69,7 +69,7 @@ Accepted * The `/docs` folder now only contains Cosmos SDK and gaia related material. Later, it will only contain Cosmos SDK related material. * Developers only have to update `/docs` folder when they open a PR (and not `/examples` for example). * Easier for developers to find what they need to update in the docs thanks to reworked architecture. -* Cleaner vuepress build for website docs. +* Cleaner `vuepress` build for website docs. * Will help build an executable doc (cf https://github.com/cosmos/cosmos-sdk/issues/2611) ### Neutral diff --git a/docs/architecture/adr-003-dynamic-capability-store.md b/docs/architecture/adr-003-dynamic-capability-store.md index 89f0c996755..ce8aa896329 100644 --- a/docs/architecture/adr-003-dynamic-capability-store.md +++ b/docs/architecture/adr-003-dynamic-capability-store.md @@ -34,8 +34,8 @@ chain is running. The `CapabilityKeeper` will include a persistent `KVStore`, a `MemoryStore`, and an in-memory map. The persistent `KVStore` tracks which capability is owned by which modules. -The `MemoryStore` stores a forward mapping that map from module name, capability tuples to capability names and -a reverse mapping that map from module name, capability name to the capability index. +The `MemoryStore` stores a forward mapping that maps from module name, capability tuples to capability names and +a reverse mapping that maps from module name, capability name to the capability index. Since we cannot marshal the capability into a `KVStore` and unmarshal without changing the memory location of the capability, the reverse mapping in the KVStore will simply map to an index. This index can then be used as a key in the ephemeral go-map to retrieve the capability at the original memory location. @@ -277,7 +277,7 @@ ck.InitialiseAndSeal(initialContext) #### Creating, passing, claiming and using capabilities -Consider the case where `mod1` wants to create a capability, associate it with a resource (e.g. an IBC channel) by name, then pass it to `mod2` which will use it later: +Consider the case where `mod1` wants to create a capability, associate it with a resource (e.g. an IBC channel) by name, and then pass it to `mod2` which will use it later: Module 1 would have the following code: @@ -327,12 +327,12 @@ Proposed. ### Positive * Dynamic capability support. -* Allows CapabilityKeeper to return same capability pointer from go-map while reverting any writes to the persistent `KVStore` and in-memory `MemoryStore` on tx failure. +* Allows CapabilityKeeper to return the same capability pointer from go-map while reverting any writes to the persistent `KVStore` and in-memory `MemoryStore` on tx failure. ### Negative * Requires an additional keeper. -* Some overlap with existing `StoreKey` system (in the future they could be combined, since this is a superset functionality-wise). +* Some overlap with the existing `StoreKey` system (in the future they could be combined, since this is a superset functionality-wise). * Requires an extra level of indirection in the reverse mapping, since MemoryStore must map to index which must then be used as key in a go map to retrieve the actual capability ### Neutral diff --git a/docs/architecture/adr-010-modular-antehandler.md b/docs/architecture/adr-010-modular-antehandler.md index 4eb5b8855f7..4fa6e4f2c3d 100644 --- a/docs/architecture/adr-010-modular-antehandler.md +++ b/docs/architecture/adr-010-modular-antehandler.md @@ -29,7 +29,7 @@ Pros: Cons: 1. Improves granularity but still cannot get more granular than a per-module basis. e.g. If auth's `AnteHandle` function is in charge of validating memo and signatures, users cannot swap the signature-checking functionality while keeping the rest of auth's `AnteHandle` functionality. -2. Module AnteHandler are run one after the other. There is no way for one AnteHandler to wrap or "decorate" another. +2. Module AnteHandlers are run one after the other. There is no way for one AnteHandler to wrap or "decorate" another. ### Decorator Pattern @@ -157,7 +157,7 @@ app.SetAnteHandler(mm.GetAnteHandler()) #### Custom Workflow -This is an example workflow for a user that wants to implement custom antehandler logic. In this example, the user wants to implement custom signature verification and change the order of antehandler so that validate memo runs before signature verification. +This is an example workflow for a user who wants to implement custom antehandler logic. In this example, the user wants to implement custom signature verification and change the order of antehandler so that validate memo runs before signature verification. ##### User Code @@ -192,7 +192,7 @@ In addition, this approach will not break any core Cosmos SDK API's. Since we pr Allow Decorator interface that can be chained together to create a Cosmos SDK AnteHandler. -This allows users to choose between implementing an AnteHandler by themselves and setting it in the baseapp, or use the decorator pattern to chain their custom decorators with the Cosmos SDK provided decorators in the order they wish. +This allows users to choose between implementing an AnteHandler by themselves and setting it in the baseapp, or using the decorator pattern to chain their custom decorators with the Cosmos SDK provided decorators in the order they wish. ```go // An AnteDecorator wraps an AnteHandler, and can do pre- and post-processing on the next AnteHandler diff --git a/docs/architecture/adr-012-state-accessors.md b/docs/architecture/adr-012-state-accessors.md index 93600000ff1..49a9ba1fd94 100644 --- a/docs/architecture/adr-012-state-accessors.md +++ b/docs/architecture/adr-012-state-accessors.md @@ -15,7 +15,7 @@ value and finally unmarshal. Usually this is done by declaring `Keeper.GetXXX` a which are repetitive and hard to maintain. Second, this makes it harder to align with the object capability theorem: the right to access the -state is defined as a `StoreKey`, which gives full access on the entire Merkle tree, so a module cannot +state is defined as a `StoreKey`, which gives full access to the entire Merkle tree, so a module cannot send the access right to a specific key-value pair (or a set of key-value pairs) to another module safely. Finally, because the getter/setter functions are defined as methods of a module's `Keeper`, the reviewers diff --git a/docs/architecture/adr-013-metrics.md b/docs/architecture/adr-013-metrics.md index b0808d46251..e2bce3a0421 100644 --- a/docs/architecture/adr-013-metrics.md +++ b/docs/architecture/adr-013-metrics.md @@ -10,7 +10,7 @@ Proposed ## Context -Telemetry is paramount into debugging and understanding what the application is doing and how it is +Telemetry is paramount in debugging and understanding what the application is doing and how it is performing. We aim to expose metrics from modules and other core parts of the Cosmos SDK. In addition, we should aim to support multiple configurable sinks that an operator may choose from. @@ -148,7 +148,7 @@ func (k BaseKeeper) MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) ### Positive -* Exposure into the performance and behavior of an application +* Exposure to the performance and behavior of an application ### Negative diff --git a/docs/architecture/adr-016-validator-consensus-key-rotation.md b/docs/architecture/adr-016-validator-consensus-key-rotation.md index 7085f033c63..998627a43de 100644 --- a/docs/architecture/adr-016-validator-consensus-key-rotation.md +++ b/docs/architecture/adr-016-validator-consensus-key-rotation.md @@ -7,7 +7,7 @@ ## Context -Validator consensus key rotation feature has been discussed and requested for a long time, for the sake of safer validator key management policy (e.g. https://github.com/tendermint/tendermint/issues/1136). So, we suggest one of the simplest form of validator consensus key rotation implementation mostly onto Cosmos SDK. +Validator consensus key rotation feature has been discussed and requested for a long time, for the sake of safer validator key management policy (e.g. https://github.com/tendermint/tendermint/issues/1136). So, we suggest one of the simplest forms of validator consensus key rotation implementation mostly onto Cosmos SDK. We don't need to make any update on consensus logic in Tendermint because Tendermint does not have any mapping information of consensus key and validator operator key, meaning that from Tendermint point of view, a consensus key rotation of a validator is simply a replacement of a consensus key to another. @@ -21,33 +21,33 @@ Also, it should be noted that this ADR includes only the simplest form of consen * create and broadcast a transaction with a `MsgRotateConsPubKey` that states the new consensus key is now coupled with the validator operator with signature from the validator's operator key. * old consensus key becomes unable to participate on consensus immediately after the update of key mapping state on-chain. * start validating with new consensus key. -* validators using HSM and KMS should update the consensus key in HSM to use the new rotated key after the height `h` when `MsgRotateConsPubKey` committed to the blockchain. +* validators using HSM and KMS should update the consensus key in HSM to use the new rotated key after the height `h` when `MsgRotateConsPubKey` is committed to the blockchain. ### Considerations * consensus key mapping information management strategy - * store history of each key mapping changes in the kvstore. - * the state machine can search corresponding consensus key paired with given validator operator for any arbitrary height in a recent unbonding period. + * store history of each key mapping change in the kvstore. + * the state machine can search corresponding consensus key paired with the given validator operator for any arbitrary height in a recent unbonding period. * the state machine does not need any historical mapping information which is past more than unbonding period. * key rotation costs related to LCD and IBC * LCD and IBC will have traffic/computation burden when there exists frequent power changes * In current Tendermint design, consensus key rotations are seen as power changes from LCD or IBC perspective - * Therefore, to minimize unnecessary frequent key rotation behavior, we limited maximum number of rotation in recent unbonding period and also applied exponentially increasing rotation fee + * Therefore, to minimize unnecessary frequent key rotation behavior, we limited the maximum number of rotations in recent unbonding period and also applied an exponentially increasing rotation fee * limits * rotations are limited to 1 time in an unbonding window. In future rewrites of the staking module it could be made to happen more times than 1 - * parameters can be decided by governance and stored in genesis file. + * parameters can be decided by governance and stored in the genesis file. * key rotation fee * a validator should pay `KeyRotationFee` to rotate the consensus key which is calculated as below * `KeyRotationFee` = (max(`VotingPowerPercentage`, 1)* `InitialKeyRotationFee`) * 2^(number of rotations in `ConsPubKeyRotationHistory` in recent unbonding period) * evidence module - * evidence module can search corresponding consensus key for any height from slashing keeper so that it can decide which consensus key is supposed to be used for given height. + * evidence module can search corresponding consensus key for any height from slashing keeper so that it can decide which consensus key is supposed to be used for a given height. * abci.ValidatorUpdate * tendermint already has ability to change a consensus key by ABCI communication(`ValidatorUpdate`). * validator consensus key update can be done via creating new + delete old by change the power to zero. * therefore, we expect we even do not need to change tendermint codebase at all to implement this feature. * new genesis parameters in `staking` module - * `MaxConsPubKeyRotations` : maximum number of rotation can be executed by a validator in recent unbonding period. default value 10 is suggested(11th key rotation will be rejected) - * `InitialKeyRotationFee` : the initial key rotation fee when no key rotation has happened in recent unbonding period. default value 1atom is suggested(1atom fee for the first key rotation in recent unbonding period) + * `MaxConsPubKeyRotations` : maximum number of rotations can be executed by a validator in the recent unbonding period. default value 10 is suggested(11th key rotation will be rejected) + * `InitialKeyRotationFee` : the initial key rotation fee when no key rotation has happened in the recent unbonding period. default value 1atom is suggested(1atom fee for the first key rotation in recent unbonding period) ### Workflow @@ -64,7 +64,7 @@ Also, it should be noted that this ADR includes only the simplest form of consen 3. `handleMsgRotateConsPubKey` gets `MsgRotateConsPubKey`, calls `RotateConsPubKey` with emits event 4. `RotateConsPubKey` * checks if `NewPubKey` is not duplicated on `ValidatorsByConsAddr` - * checks if the validator is does not exceed parameter `MaxConsPubKeyRotations` by iterating `ConsPubKeyRotationHistory` + * checks if the validator does not exceed parameter `MaxConsPubKeyRotations` by iterating `ConsPubKeyRotationHistory` * checks if the signing account has enough balance to pay `KeyRotationFee` * pays `KeyRotationFee` to community fund * overwrites `NewPubKey` in `validator.ConsPubKey` @@ -81,7 +81,7 @@ Also, it should be noted that this ADR includes only the simplest form of consen } ``` -5. `ApplyAndReturnValidatorSetUpdates` checks if there is `ConsPubKeyRotationHistory` with `ConsPubKeyRotationHistory.RotatedHeight == ctx.BlockHeight()` and if so, generates 2 `ValidatorUpdate` , one for a remove validator and one for create new validator +5. `ApplyAndReturnValidatorSetUpdates` checks if there is `ConsPubKeyRotationHistory` with `ConsPubKeyRotationHistory.RotatedHeight == ctx.BlockHeight()` and if so, generates 2 `ValidatorUpdate` , one for a remove validator and one for create a new validator ```go abci.ValidatorUpdate{ diff --git a/docs/architecture/adr-019-protobuf-state-encoding.md b/docs/architecture/adr-019-protobuf-state-encoding.md index 5ad1b953e79..267896d9621 100644 --- a/docs/architecture/adr-019-protobuf-state-encoding.md +++ b/docs/architecture/adr-019-protobuf-state-encoding.md @@ -21,7 +21,7 @@ and JSON object encoding over the wire bringing parity between logical objects a From the Amino docs: > Amino is an object encoding specification. It is a subset of Proto3 with an extension for interface -> support. See the [Proto3 spec](https://developers.google.com/protocol-buffers/docs/proto3) for more +> support. See the [Proto3 spec](https://protobuf.dev/programming-guides/proto3/) for more > information on Proto3, which Amino is largely compatible with (but not with Proto2). > > The goal of the Amino encoding protocol is to bring parity into logic objects and persistence objects. @@ -56,7 +56,7 @@ made to address client-side encoding. ## Decision -We will adopt [Protocol Buffers](https://developers.google.com/protocol-buffers) for serializing +We will adopt [Protocol Buffers](https://protobuf.dev) for serializing persisted structured data in the Cosmos SDK while providing a clean mechanism and developer UX for applications wishing to continue to use Amino. We will provide this mechanism by updating modules to accept a codec interface, `Marshaler`, instead of a concrete Amino codec. Furthermore, the Cosmos SDK diff --git a/docs/architecture/adr-023-protobuf-naming.md b/docs/architecture/adr-023-protobuf-naming.md index a192dfce326..01bfd8bd94c 100644 --- a/docs/architecture/adr-023-protobuf-naming.md +++ b/docs/architecture/adr-023-protobuf-naming.md @@ -11,7 +11,7 @@ Accepted ## Context -Protocol Buffers provide a basic [style guide](https://developers.google.com/protocol-buffers/docs/style) +Protocol Buffers provide a basic [style guide](https://protobuf.dev/programming-guides/style/) and [Buf](https://buf.build/docs/style-guide) builds upon that. To the extent possible, we want to follow industry accepted guidelines and wisdom for the effective usage of protobuf, deviating from those only when there is clear diff --git a/docs/architecture/adr-027-deterministic-protobuf-serialization.md b/docs/architecture/adr-027-deterministic-protobuf-serialization.md index 66ce6e2b75e..41e0d28e33d 100644 --- a/docs/architecture/adr-027-deterministic-protobuf-serialization.md +++ b/docs/architecture/adr-027-deterministic-protobuf-serialization.md @@ -15,7 +15,7 @@ Fully deterministic structure serialization, which works across many languages a is needed when signing messages. We need to be sure that whenever we serialize a data structure, no matter in which supported language, the raw bytes will stay the same. -[Protobuf](https://developers.google.com/protocol-buffers/docs/proto3) +[Protobuf](https://protobuf.dev/programming-guides/proto3/) serialization is not bijective (i.e. there exist a practically unlimited number of valid binary representations for a given protobuf document)1. @@ -55,7 +55,7 @@ reject documents containing maps as invalid input. ### Background - Protobuf3 Encoding Most numeric types in protobuf3 are encoded as -[varints](https://developers.google.com/protocol-buffers/docs/encoding#varints). +[varints](https://protobuf.dev/programming-guides/encoding/#varints). Varints are at most 10 bytes, and since each varint byte has 7 bits of data, varints are a representation of `uint70` (70-bit unsigned integer). When encoding, numeric values are casted from their base type to `uint70`, and when @@ -74,15 +74,15 @@ encoding malleability. ### Serialization rules The serialization is based on the -[protobuf3 encoding](https://developers.google.com/protocol-buffers/docs/encoding) +[protobuf3 encoding](https://protobuf.dev/programming-guides/encoding/) with the following additions: 1. Fields must be serialized only once in ascending order 2. Extra fields or any extra data must not be added -3. [Default values](https://developers.google.com/protocol-buffers/docs/proto3#default) +3. [Default values](https://protobuf.dev/programming-guides/proto3/#default) must be omitted 4. `repeated` fields of scalar numeric types must use - [packed encoding](https://developers.google.com/protocol-buffers/docs/encoding#packed) + [packed encoding](https://protobuf.dev/programming-guides/encoding/#packed) 5. Varint encoding must not be longer than needed: * No trailing zero bytes (in little endian, i.e. no leading zeroes in big endian). Per rule 3 above, the default value of `0` must be omitted, so @@ -288,27 +288,27 @@ the need of implementing a custom serializer that adheres to this standard (and implementation detail and the details of any particular implementation may change in the future. Therefore, protocol buffer parsers must be able to parse fields in any order._ from - https://developers.google.com/protocol-buffers/docs/encoding#order -* 2 https://developers.google.com/protocol-buffers/docs/encoding#signed_integers + https://protobuf.dev/programming-guides/encoding/#order +* 2 https://protobuf.dev/programming-guides/encoding/#signed_integers * 3 _Note that for scalar message fields, once a message is parsed there's no way of telling whether a field was explicitly set to the default value (for example whether a boolean was set to false) or just not set at all: you should bear this in mind when defining your message types. For example, don't have a boolean that switches on some behavior when set to false if you don't want that behavior to also happen by default._ from - https://developers.google.com/protocol-buffers/docs/proto3#default + https://protobuf.dev/programming-guides/proto3/#default * 4 _When a message is parsed, if the encoded message does not contain a particular singular element, the corresponding field in the parsed object is set to the default value for that field._ from - https://developers.google.com/protocol-buffers/docs/proto3#default + https://protobuf.dev/programming-guides/proto3/#default * 5 _Also note that if a scalar message field is set to its default, the value will not be serialized on the wire._ from - https://developers.google.com/protocol-buffers/docs/proto3#default + https://protobuf.dev/programming-guides/proto3/#default * 6 _For enums, the default value is the first defined enum value, which must be 0._ from - https://developers.google.com/protocol-buffers/docs/proto3#default + https://protobuf.dev/programming-guides/proto3/#default * 7 _For message fields, the field is not set. Its exact value is language-dependent._ from - https://developers.google.com/protocol-buffers/docs/proto3#default + https://protobuf.dev/programming-guides/proto3/#default * Encoding rules and parts of the reasoning taken from [canonical-proto3 Aaron Craelius](https://github.com/regen-network/canonical-proto3) diff --git a/docs/architecture/adr-031-msg-service.md b/docs/architecture/adr-031-msg-service.md index 8aa78c82d25..8185509bd19 100644 --- a/docs/architecture/adr-031-msg-service.md +++ b/docs/architecture/adr-031-msg-service.md @@ -77,7 +77,7 @@ message MsgSubmitProposalResponse { ``` While this is most commonly used for gRPC, overloading protobuf `service` definitions like this does not violate -the intent of the [protobuf spec](https://developers.google.com/protocol-buffers/docs/proto3#services) which says: +the intent of the [protobuf spec](https://protobuf.dev/programming-guides/proto3/#services) which says: > If you don’t want to use gRPC, it’s also possible to use protocol buffers with your own RPC implementation. With this approach, we would get an auto-generated `MsgServer` interface: @@ -175,7 +175,7 @@ Separate handler definition is no longer needed with this approach. ## Consequences -This design changes how a module functionality is exposed and accessed. It deprecates the existing `Handler` interface and `AppModule.Route` in favor of [Protocol Buffer Services](https://developers.google.com/protocol-buffers/docs/proto3#services) and Service Routing described above. This dramatically simplifies the code. We don't need to create handlers and keepers any more. Use of Protocol Buffer auto-generated clients clearly separates the communication interfaces between the module and a modules user. The control logic (aka handlers and keepers) is not exposed any more. A module interface can be seen as a black box accessible through a client API. It's worth to note that the client interfaces are also generated by Protocol Buffers. +This design changes how a module functionality is exposed and accessed. It deprecates the existing `Handler` interface and `AppModule.Route` in favor of [Protocol Buffer Services](https://protobuf.dev/programming-guides/proto3/#services) and Service Routing described above. This dramatically simplifies the code. We don't need to create handlers and keepers any more. Use of Protocol Buffer auto-generated clients clearly separates the communication interfaces between the module and a modules user. The control logic (aka handlers and keepers) is not exposed any more. A module interface can be seen as a black box accessible through a client API. It's worth to note that the client interfaces are also generated by Protocol Buffers. This also allows us to change how we perform functional tests. Instead of mocking AppModules and Router, we will mock a client (server will stay hidden). More specifically: we will never mock `moduleA.MsgServer` in `moduleB`, but rather `moduleA.MsgClient`. One can think about it as working with external services (eg DBs, or online servers...). We assume that the transmission between clients and servers is correctly handled by generated Protocol Buffers. @@ -196,6 +196,6 @@ Finally, closing a module to client API opens desirable OCAP patterns discussed ## References * [Initial Github Issue \#7122](https://github.com/cosmos/cosmos-sdk/issues/7122) -* [proto 3 Language Guide: Defining Services](https://developers.google.com/protocol-buffers/docs/proto3#services) +* [proto 3 Language Guide: Defining Services](https://protobuf.dev/programming-guides/proto3/#services) * [ADR 020](./adr-020-protobuf-transaction-encoding.md) * [ADR 021](./adr-021-protobuf-query-encoding.md) diff --git a/docs/architecture/adr-043-nft-module.md b/docs/architecture/adr-043-nft-module.md index 5968eb1305a..40c21cd3c6d 100644 --- a/docs/architecture/adr-043-nft-module.md +++ b/docs/architecture/adr-043-nft-module.md @@ -17,9 +17,9 @@ This ADR defines the `x/nft` module which is a generic implementation of NFTs, r * `MsgNewClass` - Receive the user's request to create a class, and call the `NewClass` of the `x/nft` module. * `MsgUpdateClass` - Receive the user's request to update a class, and call the `UpdateClass` of the `x/nft` module. -* `MsgMintNFT` - Receive the user's request to mint a nft, and call the `MintNFT` of the `x/nft` module. -* `BurnNFT` - Receive the user's request to burn a nft, and call the `BurnNFT` of the `x/nft` module. -* `UpdateNFT` - Receive the user's request to update a nft, and call the `UpdateNFT` of the `x/nft` module. +* `MsgMintNFT` - Receive the user's request to mint an NFT, and call the `MintNFT` of the `x/nft` module. +* `BurnNFT` - Receive the user's request to burn an NFT, and call the `BurnNFT` of the `x/nft` module. +* `UpdateNFT` - Receive the user's request to update an NFT, and call the `UpdateNFT` of the `x/nft` module. ## Context @@ -48,7 +48,7 @@ We create a `x/nft` module, which contains the following functionality: * Expose external `Message` interface for users to transfer ownership of their NFTs. * Query NFTs and their supply information. -The proposed module is a base module for NFT app logic. It's goal it to provide a common layer for storage, basic transfer functionality and IBC. The module should not be used as a standalone. +The proposed module is a base module for NFT app logic. Its goal is to provide a common layer for storage, basic transfer functionality and IBC. The module should not be used as a standalone. Instead an app should create a specialized module to handle app specific logic (eg: NFT ID construction, royalty), user level minting and burning. Moreover an app specialized module should handle auxiliary data to support the app logic (eg indexes, ORM, business data). All data carried over IBC must be part of the `NFT` or `Class` type described below. The app specific NFT data should be encoded in `NFT.data` for cross-chain integrity. Other objects related to NFT, which are not important for integrity can be part of the app specific module. @@ -58,7 +58,7 @@ All data carried over IBC must be part of the `NFT` or `Class` type described be We propose two main types: * `Class` -- describes NFT class. We can think about it as a smart contract address. -* `NFT` -- object representing unique, non fungible asset. Each NFT is associated with a Class. +* `NFT` -- object representing unique, non-fungible asset. Each NFT is associated with a class. #### Class @@ -81,7 +81,7 @@ message Class { * `symbol` is the symbol usually shown on exchanges for the NFT class; _optional_ * `description` is a detailed description of the NFT class; _optional_ * `uri` is a URI for the class metadata stored off chain. It should be a JSON file that contains metadata about the NFT class and NFT data schema ([OpenSea example](https://docs.opensea.io/docs/contract-level-metadata)); _optional_ -* `uri_hash` is a hash of the document pointed by uri; _optional_ +* `uri_hash` is a hash of the document pointed by URI; _optional_ * `data` is app specific metadata of the class; _optional_ #### NFT @@ -107,7 +107,7 @@ message NFT { * `uri` is a URI for the NFT metadata stored off chain. Should point to a JSON file that contains metadata about this NFT (Ref: [ERC721 standard and OpenSea extension](https://docs.opensea.io/docs/metadata-standards)); _required_ * `uri_hash` is a hash of the document pointed by uri; _optional_ -* `data` is an app specific data of the NFT. CAN be used by composing modules to specify additional properties of the NFT; _optional_ +* `data` is an app specific data of the NFT. Can be used by composing modules to specify additional properties of the NFT; _optional_ This ADR doesn't specify values that `data` can take; however, best practices recommend upper-level NFT modules clearly specify their contents. Although the value of this field doesn't provide the additional context required to manage NFT records, which means that the field can technically be removed from the specification, the field's existence allows basic informational/UI functionality. diff --git a/docs/architecture/adr-044-protobuf-updates-guidelines.md b/docs/architecture/adr-044-protobuf-updates-guidelines.md index c2d41a1a37e..93e28d2d173 100644 --- a/docs/architecture/adr-044-protobuf-updates-guidelines.md +++ b/docs/architecture/adr-044-protobuf-updates-guidelines.md @@ -73,7 +73,7 @@ and the following ones are NOT valid: #### 2. Fields MAY be marked as `deprecated`, and nodes MAY implement a protocol-breaking change for handling these fields -Protobuf supports the [`deprecated` field option](https://developers.google.com/protocol-buffers/docs/proto#options), and this option MAY be used on any field, including `Msg` fields. If a node handles a Protobuf message with a non-empty deprecated field, the node MAY change its behavior upon processing it, even in a protocol-breaking way. When possible, the node MUST handle backwards compatibility without breaking the consensus (unless we increment the proto version). +Protobuf supports the [`deprecated` field option](https://protobuf.dev/programming-guides/proto2/), and this option MAY be used on any field, including `Msg` fields. If a node handles a Protobuf message with a non-empty deprecated field, the node MAY change its behavior upon processing it, even in a protocol-breaking way. When possible, the node MUST handle backwards compatibility without breaking the consensus (unless we increment the proto version). As an example, the Cosmos SDK v0.42 to v0.43 update contained two Protobuf-breaking changes, listed below. Instead of bumping the package versions from `v1beta1` to `v1`, the SDK team decided to follow this guideline, by reverting the breaking changes, marking those changes as deprecated, and modifying the node implementation when processing messages with deprecated fields. More specifically: diff --git a/docs/architecture/adr-054-semver-compatible-modules.md b/docs/architecture/adr-054-semver-compatible-modules.md index 5dc0a666f69..40dd6081063 100644 --- a/docs/architecture/adr-054-semver-compatible-modules.md +++ b/docs/architecture/adr-054-semver-compatible-modules.md @@ -37,7 +37,7 @@ In order to achieve this, we need to solve the following problems: 2. circular dependencies between modules need to be broken to actually release many modules in the SDK independently 3. pernicious minor version incompatibilities introduced through correctly - [evolving protobuf schemas](https://developers.google.com/protocol-buffers/docs/proto3#updating) + [evolving protobuf schemas](https://protobuf.dev/programming-guides/proto3/#updating) without correct [unknown field filtering](./adr-020-protobuf-transaction-encoding.md#unknown-field-filtering) Note that all the following discussion assumes that the proto file versioning and state machine versioning of a module @@ -320,7 +320,7 @@ generate its own version of `MsgDoSomething` as `bar/internal/foo/v1.MsgDoSometh inter-module router which would somehow convert it to the version which foo needs (ex. `foo/internal.MsgDoSomething`). Currently, two generated structs for the same protobuf type cannot exist in the same go binary without special -build flags (see https://developers.google.com/protocol-buffers/docs/reference/go/faq#fix-namespace-conflict). +build flags (see https://protobuf.dev/reference/go/faq/#fix-namespace-conflict). A relatively simple mitigation to this issue would be to set up the protobuf code to not register protobuf types globally if they are generated in an `internal/` package. This will require modules to register their types manually with the app-level level protobuf registry, this is similar to what modules already do with the `InterfaceRegistry` diff --git a/docs/build/abci/00-introduction.md b/docs/build/abci/00-introduction.md index 6b7cd8ec3c8..7d955d41d95 100644 --- a/docs/build/abci/00-introduction.md +++ b/docs/build/abci/00-introduction.md @@ -19,7 +19,7 @@ The 5 methods introduced during ABCI 2.0 (compared to ABCI v0) are: Based on validator voting power, CometBFT chooses a block proposer and calls `PrepareProposal` on the block proposer's application (Cosmos SDK). The selected block proposer is responsible for collecting outstanding transactions from the mempool, adhering to the application's specifications. The application can enforce custom transaction ordering and incorporate additional transactions, potentially generated from vote extensions in the previous block. -To perform this manipulation on the application side, a custom handler must be implemented. By default, the Cosmos SDK provides `PrepareProposalHandler`, used in conjunction with an application specific mempool. A custom handler can be written by application developer, if a noop handler provided, all transactions are considered valid. Please see [this](https://github.com/fatal-fruit/abci-workshop) tutorial for more information on custom handlers. +To perform this manipulation on the application side, a custom handler must be implemented. By default, the Cosmos SDK provides `PrepareProposalHandler`, used in conjunction with an application specific mempool. A custom handler can be written by application developer, if a noop handler provided, all transactions are considered valid. Please note that vote extensions will only be available on the following height in which vote extensions are enabled. More information about vote extensions can be found [here](https://docs.cosmos.network/main/build/abci/vote-extensions). diff --git a/docs/build/abci/01-prepare-proposal.md b/docs/build/abci/01-prepare-proposal.md index c77c9094d45..3d480664e5a 100644 --- a/docs/build/abci/01-prepare-proposal.md +++ b/docs/build/abci/01-prepare-proposal.md @@ -24,12 +24,12 @@ it would like the block constructed. The Cosmos SDK defines the `DefaultProposalHandler` type, which provides applications with `PrepareProposal` and `ProcessProposal` handlers. If you decide to implement your -own `PrepareProposal` handler, you must be sure to ensure that the transactions +own `PrepareProposal` handler, you must ensure that the transactions selected DO NOT exceed the maximum block gas (if set) and the maximum bytes provided by `req.MaxBytes`. ```go reference -https://github.com/cosmos/cosmos-sdk/blob/v0.50.0-alpha.0/baseapp/abci_utils.go +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/baseapp/abci_utils.go ``` This default implementation can be overridden by the application developer in @@ -38,7 +38,7 @@ favor of a custom implementation in [`app_di.go`](https://docs.cosmos.network/ma ```go prepareOpt := func(app *baseapp.BaseApp) { abciPropHandler := baseapp.NewDefaultProposalHandler(mempool, app) - app.SetPrepareProposal(abciPropHandler.PrepareProposalHandler())) + app.SetPrepareProposal(abciPropHandler.PrepareProposalHandler()) } baseAppOptions = append(baseAppOptions, prepareOpt) diff --git a/docs/build/abci/02-process-proposal.md b/docs/build/abci/02-process-proposal.md index 834ba3b90c4..7768890bdae 100644 --- a/docs/build/abci/02-process-proposal.md +++ b/docs/build/abci/02-process-proposal.md @@ -14,12 +14,12 @@ and `ProcessProposal` for the new proposal. Here is the implementation of the default implementation: ```go reference -https://github.com/cosmos/cosmos-sdk/blob/v0.50.0-alpha.0/baseapp/abci_utils.go#L153-L159 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/baseapp/abci_utils.go#L224-L231 ``` Like `PrepareProposal` this implementation is the default and can be modified by the application developer in [`app_di.go`](https://docs.cosmos.network/main/build/building-apps/app-go-di). If you decide to implement -your own `ProcessProposal` handler, you must be sure to ensure that the transactions +your own `ProcessProposal` handler, you must ensure that the transactions provided in the proposal DO NOT exceed the maximum block gas and `maxtxbytes` (if set). ```go diff --git a/docs/build/abci/03-vote-extensions.md b/docs/build/abci/03-vote-extensions.md index 59b00c89854..26cbfd1a296 100644 --- a/docs/build/abci/03-vote-extensions.md +++ b/docs/build/abci/03-vote-extensions.md @@ -8,7 +8,7 @@ defined in ABCI++. ## Extend Vote ABCI2.0 (colloquially called ABCI++) allows an application to extend a pre-commit vote with arbitrary data. This process does NOT have to be deterministic, and the data returned can be unique to the -validator process. The Cosmos SDK defines [`baseapp.ExtendVoteHandler`](https://github.com/cosmos/cosmos-sdk/blob/v0.50.1/types/abci.go#L26-L27): +validator process. The Cosmos SDK defines [`baseapp.ExtendVoteHandler`](https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/types/abci.go#L26-L27): ```go type ExtendVoteHandler func(Context, *abci.ExtendVoteRequest) (*abci.ExtendVoteResponse, error) @@ -35,7 +35,7 @@ Click [here](https://docs.cosmos.network/main/build/abci/vote-extensions) if you Similar to extending a vote, an application can also verify vote extensions from other validators when validating their pre-commits. For a given vote extension, -this process MUST be deterministic. The Cosmos SDK defines [`sdk.VerifyVoteExtensionHandler`](https://github.com/cosmos/cosmos-sdk/blob/v0.50.1/types/abci.go#L29-L31): +this process MUST be deterministic. The Cosmos SDK defines [`sdk.VerifyVoteExtensionHandler`](https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/types/abci.go#L29-L31): ```go type VerifyVoteExtensionHandler func(Context, *abci.VerifyVoteExtensionRequest) (*abci.VerifyVoteExtensionResponse, error) diff --git a/docs/build/building-modules/01-module-manager.md b/docs/build/building-modules/01-module-manager.md index d5e0aac04da..9742454320e 100644 --- a/docs/build/building-modules/01-module-manager.md +++ b/docs/build/building-modules/01-module-manager.md @@ -40,7 +40,6 @@ The above interfaces are mostly embedding smaller interfaces (extension interfac * [`appmodule.HasEndBlocker`](#hasendblocker): The extension interface that contains information about the `AppModule` and `EndBlock`. * [`appmodule.HasService` / `module.HasServices`](#hasservices): The extension interface for modules to register services. * [`module.HasABCIEndBlock`](#hasabciendblock): The extension interface that contains information about the `AppModule`, `EndBlock` and returns an updated validator set. -* (legacy) [`module.HasInvariants`](#hasinvariants): The extension interface for registering invariants. * (legacy) [`module.HasConsensusVersion`](#hasconsensusversion): The extension interface for declaring a module consensus version. The `AppModule` interface exists to define inter-dependent module methods. Many modules need to interact with other modules, typically through [`keeper`s](./06-keeper.md), which means there is a need for an interface where modules list their `keeper`s and other methods that require a reference to another module's object. `AppModule` interface extension, such as `HasBeginBlocker` and `HasEndBlocker`, also enables the module manager to set the order of execution between module's methods like `BeginBlock` and `EndBlock`, which is important in cases where the order of execution between modules matters in the context of the application. @@ -125,16 +124,6 @@ Previously the `module.AppModule` interface was containing all the methods that https://github.com/cosmos/cosmos-sdk/blob/28fa3b8/core/appmodule/v2/module.go#L14-L20 ``` -### `HasInvariants` - -This interface defines one method. It allows to checks if a module can register invariants. - -```go reference -https://github.com/cosmos/cosmos-sdk/blob/v0.50.0-alpha.0/types/module/module.go#L202-L205 -``` - -* `RegisterInvariants(sdk.InvariantRegistry)`: Registers the [`invariants`](./07-invariants.md) of the module. If an invariant deviates from its predicted value, the [`InvariantRegistry`](./07-invariants.md#registry) triggers appropriate logic (most often the chain will be halted). - ### `HasServices` This interface defines one method. It allows to checks if a module can register invariants. @@ -238,14 +227,13 @@ The module manager is used throughout the application whenever an action on a co * `SetOrderPrecommiters(moduleNames ...string)`: Sets the order in which the `Precommit()` function of each module will be called during commit of each block. This function is generally called from the application's main [constructor function](../../learn/beginner/00-app-anatomy.md#constructor-function). * `SetOrderPrepareCheckStaters(moduleNames ...string)`: Sets the order in which the `PrepareCheckState()` function of each module will be called during commit of each block. This function is generally called from the application's main [constructor function](../../learn/beginner/00-app-anatomy.md#constructor-function). * `SetOrderMigrations(moduleNames ...string)`: Sets the order of migrations to be run. If not set then migrations will be run with an order defined in `DefaultMigrationsOrder`. -* `RegisterInvariants(ir sdk.InvariantRegistry)`: Registers the [invariants](./07-invariants.md) of module implementing the `HasInvariants` interface. * `RegisterServices(cfg Configurator)`: Registers the services of modules implementing the `HasServices` interface. * `InitGenesis(ctx context.Context, genesisData map[string]json.RawMessage)`: Calls the [`InitGenesis`](./08-genesis.md#initgenesis) function of each module when the application is first started, in the order defined in `OrderInitGenesis`. Returns an `abci.InitChainResponse` to the underlying consensus engine, which can contain validator updates. * `ExportGenesis(ctx context.Context)`: Calls the [`ExportGenesis`](./08-genesis.md#exportgenesis) function of each module, in the order defined in `OrderExportGenesis`. The export constructs a genesis file from a previously existing state, and is mainly used when a hard-fork upgrade of the chain is required. * `ExportGenesisForModules(ctx context.Context, modulesToExport []string)`: Behaves the same as `ExportGenesis`, except takes a list of modules to export. -* `BeginBlock(ctx context.Context) error`: At the beginning of each block, this function is called from [`BaseApp`](../../learn/advanced/00-baseapp.md#beginblock) and, in turn, calls the [`BeginBlock`](./06-beginblock-endblock.md) function of each modules implementing the `appmodule.HasBeginBlocker` interface, in the order defined in `OrderBeginBlockers`. It creates a child [context](../../learn/advanced/02-context.md) with an event manager to aggregate [events](../../learn/advanced/08-events.md) emitted from each modules. -* `EndBlock(ctx context.Context) error`: At the end of each block, this function is called from [`BaseApp`](../../learn/advanced/00-baseapp.md#endblock) and, in turn, calls the [`EndBlock`](./06-beginblock-endblock.md) function of each modules implementing the `appmodule.HasEndBlocker` interface, in the order defined in `OrderEndBlockers`. It creates a child [context](../../learn/advanced/02-context.md) with an event manager to aggregate [events](../../learn/advanced/08-events.md) emitted from all modules. The function returns an `abci` which contains the aforementioned events, as well as validator set updates (if any). -* `EndBlock(context.Context) ([]abci.ValidatorUpdate, error)`: At the end of each block, this function is called from [`BaseApp`](../../learn/advanced/00-baseapp.md#endblock) and, in turn, calls the [`EndBlock`](./06-beginblock-endblock.md) function of each modules implementing the `module.HasABCIEndBlock` interface, in the order defined in `OrderEndBlockers`. It creates a child [context](../../learn/advanced/02-context.md) with an event manager to aggregate [events](../../learn/advanced/08-events.md) emitted from all modules. The function returns an `abci` which contains the aforementioned events, as well as validator set updates (if any). +* `BeginBlock(ctx context.Context) error`: At the beginning of each block, this function is called from [`BaseApp`](../../learn/advanced/00-baseapp.md#beginblock) and, in turn, calls the [`BeginBlock`](./06-preblock-beginblock-endblock.md) function of each modules implementing the `appmodule.HasBeginBlocker` interface, in the order defined in `OrderBeginBlockers`. It creates a child [context](../../learn/advanced/02-context.md) with an event manager to aggregate [events](../../learn/advanced/08-events.md) emitted from each modules. +* `EndBlock(ctx context.Context) error`: At the end of each block, this function is called from [`BaseApp`](../../learn/advanced/00-baseapp.md#endblock) and, in turn, calls the [`EndBlock`](./06-preblock-beginblock-endblock.md) function of each modules implementing the `appmodule.HasEndBlocker` interface, in the order defined in `OrderEndBlockers`. It creates a child [context](../../learn/advanced/02-context.md) with an event manager to aggregate [events](../../learn/advanced/08-events.md) emitted from all modules. The function returns an `abci` which contains the aforementioned events, as well as validator set updates (if any). +* `EndBlock(context.Context) ([]abci.ValidatorUpdate, error)`: At the end of each block, this function is called from [`BaseApp`](../../learn/advanced/00-baseapp.md#endblock) and, in turn, calls the [`EndBlock`](./06-preblock-beginblock-endblock.md) function of each modules implementing the `module.HasABCIEndBlock` interface, in the order defined in `OrderEndBlockers`. It creates a child [context](../../learn/advanced/02-context.md) with an event manager to aggregate [events](../../learn/advanced/08-events.md) emitted from all modules. The function returns an `abci` which contains the aforementioned events, as well as validator set updates (if any). * `Precommit(ctx context.Context)`: During [`Commit`](../../learn/advanced/00-baseapp.md#commit), this function is called from `BaseApp` immediately before the [`deliverState`](../../learn/advanced/00-baseapp.md#state-updates) is written to the underlying [`rootMultiStore`](../../learn/advanced/04-store.md#commitmultistore) and, in turn calls the `Precommit` function of each modules implementing the `HasPrecommit` interface, in the order defined in `OrderPrecommiters`. It creates a child [context](../../learn/advanced/02-context.md) where the underlying `CacheMultiStore` is that of the newly committed block's [`finalizeblockstate`](../../learn/advanced/00-baseapp.md#state-updates). * `PrepareCheckState(ctx context.Context)`: During [`Commit`](../../learn/advanced/00-baseapp.md#commit), this function is called from `BaseApp` immediately after the [`deliverState`](../../learn/advanced/00-baseapp.md#state-updates) is written to the underlying [`rootMultiStore`](../../learn/advanced/04-store.md#commitmultistore) and, in turn calls the `PrepareCheckState` function of each module implementing the `HasPrepareCheckState` interface, in the order defined in `OrderPrepareCheckStaters`. It creates a child [context](../../learn/advanced/02-context.md) where the underlying `CacheMultiStore` is that of the next block's [`checkState`](../../learn/advanced/00-baseapp.md#state-updates). Writes to this state will be present in the [`checkState`](../../learn/advanced/00-baseapp.md#state-updates) of the next block, and therefore this method can be used to prepare the `checkState` for the next block. * (Optional) `RegisterLegacyAminoCodec(cdc *codec.LegacyAmino)`: Registers the [`codec.LegacyAmino`s](../../learn/advanced/05-encoding.md#amino) of each of the application module. This function is usually called early on in the [application's construction](../../learn/beginner/00-app-anatomy.md#constructor). diff --git a/docs/build/building-modules/02-messages-and-queries.md b/docs/build/building-modules/02-messages-and-queries.md index 4dfeae41e4c..5ec1c7a707e 100644 --- a/docs/build/building-modules/02-messages-and-queries.md +++ b/docs/build/building-modules/02-messages-and-queries.md @@ -97,7 +97,7 @@ A `query` is a request for information made by end-users of applications through ### gRPC Queries -Queries should be defined using [Protobuf services](https://developers.google.com/protocol-buffers/docs/proto#services). A `Query` service should be created per module in `query.proto`. This service lists endpoints starting with `rpc`. +Queries should be defined using [Protobuf services](https://protobuf.dev/programming-guides/proto2/). A `Query` service should be created per module in `query.proto`. This service lists endpoints starting with `rpc`. Here's an example of such a `Query` service definition: diff --git a/docs/build/building-modules/06-beginblock-endblock.md b/docs/build/building-modules/06-preblock-beginblock-endblock.md similarity index 62% rename from docs/build/building-modules/06-beginblock-endblock.md rename to docs/build/building-modules/06-preblock-beginblock-endblock.md index 21c1a3303d3..a7a890eff19 100644 --- a/docs/build/building-modules/06-beginblock-endblock.md +++ b/docs/build/building-modules/06-preblock-beginblock-endblock.md @@ -2,10 +2,11 @@ sidebar_position: 1 --- -# BeginBlocker and EndBlocker +# PreBlocker, BeginBlocker and EndBlocker :::note Synopsis -`BeginBlocker` and `EndBlocker` are optional methods module developers can implement in their module. They will be triggered at the beginning and at the end of each block respectively, when the [`BeginBlock`](../../learn/advanced/00-baseapp.md#beginblock) and [`EndBlock`](../../learn/advanced/00-baseapp.md#endblock) ABCI messages are received from the underlying consensus engine. +`PreBlocker`, `BeginBlocker` and `EndBlocker` are optional methods module developers can implement in their module. +They will be triggered at the beginning and at the end of each block respectively, when the [`BeginBlock`](../../learn/advanced/00-baseapp.md#beginblock) and [`EndBlock`](../../learn/advanced/00-baseapp.md#endblock) inside within ABCI `FinalizeBlock` ::: :::note Pre-requisite Readings @@ -14,11 +15,22 @@ sidebar_position: 1 ::: +## PreBlocker + +There are two semantics around the new lifecycle method: + +* It runs before the `BeginBlocker` of all modules +* It can modify consensus parameters in storage, and signal the caller through the return value. + +:::warning +Modules are required to get the consensus params from the consensus module. Consensus params located in `sdk.Context` were deprecated and should be treated as unsafe. `sdk.Context` is deprecated due to it being a global state within the entire state machine, it has been replaced with `appmodule.Environment`. +::: + ## BeginBlocker and EndBlocker `BeginBlocker` and `EndBlocker` are a way for module developers to add automatic execution of logic to their module. This is a powerful tool that should be used carefully, as complex automatic functions can slow down or even halt the chain. -In 0.47.0, Prepare and Process Proposal were added that allow app developers to do arbitrary work at those phases, but they do not influence the work that will be done in BeginBlock. If an application required `BeginBlock` to execute prior to any sort of work is done then this is not possible today (0.50.0). +In 0.47.0, `PrepareProposal` and `ProcessProposal` were added that allow app developers to do arbitrary work at those phases, but they do not influence the work that will be done in `BeginBlock`, nor are they accessible from modules. When needed, `BeginBlocker` and `EndBlocker` are implemented as part of the [`HasBeginBlocker`, `HasABCIEndBlocker` and `EndBlocker` interfaces](./01-module-manager.md#appmodule). This means either can be left-out if not required. The `BeginBlock` and `EndBlock` methods of the interface implemented in `module.go` generally defer to `BeginBlocker` and `EndBlocker` methods respectively, which are usually implemented in `abci.go`. @@ -28,10 +40,18 @@ The actual implementation of `BeginBlocker` and `EndBlocker` in `abci.go` are ve * If needed, they use the `keeper` and `ctx` to trigger state-transitions. * If needed, they can emit [`events`](../../learn/advanced/08-events.md) via the `environments`'s `EventManager`. -A specific method (`UpdateValidators`) is available to return validator updates to the underlying consensus engine in the form of an [`[]appmodule.ValidatorUpdates`](https://github.com/cosmos/cosmos-sdk/blob/07151304e2ec6a185243d083f59a2d543253cb15/core/appmodule/v2/module.go#L87-L101). This is the preferred way to implement custom validator changes. +A specific method (`UpdateValidators`) is available to return validator updates to the underlying consensus engine in the form of an [`[]appmodule.ValidatorUpdates`](https://github.com/cosmos/cosmos-sdk/blob/07151304e2ec6a185243d083f59a2d543253cb15/core/appmodule/v2/module.go#L87-L101). This is the preferred way to implement custom validator changes (in v1). It is possible for developers to define the order of execution between the `BeginBlocker`/`EndBlocker` functions of each of their application's modules via the module's manager `SetOrderBeginBlocker`/`SetOrderEndBlocker` methods. For more on the module manager, click [here](./01-module-manager.md#manager). +### Implementation + +A module must implement those core interface to make use of the `PreBlocker`, `BeginBlocker` or `EndBlocker` capabilities: + +```go reference +https://github.com/cosmos/cosmos-sdk/blob/core/v1.0.0-alpha.4/core/appmodule/v2/module.go#L22-L48 +``` + See an example implementation of `BeginBlocker` from the `distribution` module: ```go reference @@ -49,6 +69,3 @@ and an example implementation of `EndBlocker` with validator updates from the `s ```go reference https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/staking/keeper/abci.go#L12-L17 ``` - - - diff --git a/docs/build/building-modules/07-invariants.md b/docs/build/building-modules/07-invariants.md deleted file mode 100644 index 2e8edfcada6..00000000000 --- a/docs/build/building-modules/07-invariants.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Invariants - - - -:::note Synopsis -An invariant is a property of the application that should always be true. In the context of the Cosmos SDK, an `Invariant` is a function that checks for a particular invariant. These functions are useful to detect bugs early on and act upon them to limit their potential consequences (e.g. by halting the chain). They are also useful in the development process of the application to detect bugs via simulations. -::: - -:::note Pre-requisite Readings - -* [Keepers](./06-keeper.md) - -::: - -## Implementing `Invariant`s - -An `Invariant` is a function that checks for a particular invariant within a module. Module `Invariant`s must follow the `Invariant` type: - -```go reference -https://github.com/cosmos/cosmos-sdk/blob/v0.50.0-alpha.0/types/invariant.go#L9 -``` - -The `string` return value is the invariant message, which can be used when printing logs, and the `bool` return value is the actual result of the invariant check. - -In practice, each module implements `Invariant`s in a `keeper/invariants.go` file within the module's folder. The standard is to implement one `Invariant` function per logical grouping of invariants with the following model: - -```go -// Example for an Invariant that checks balance-related invariants - -func BalanceInvariants(k Keeper) sdk.Invariant { - return func(ctx context.Context) (string, bool) { - // Implement checks for balance-related invariants - } -} -``` - -Additionally, module developers should generally implement an `AllInvariants` function that runs all the `Invariant`s functions of the module: - -```go -// AllInvariants runs all invariants of the module. -// In this example, the module implements two Invariants: BalanceInvariants and DepositsInvariants - -func AllInvariants(k Keeper) sdk.Invariant { - - return func(ctx context.Context) (string, bool) { - res, stop := BalanceInvariants(k)(ctx) - if stop { - return res, stop - } - - return DepositsInvariant(k)(ctx) - } -} -``` - -Finally, module developers need to implement the `RegisterInvariants` method as part of the [`AppModule` interface](./01-module-manager.md#appmodule). Indeed, the `RegisterInvariants` method of the module, implemented in the `module/module.go` file, typically only defers the call to a `RegisterInvariants` method implemented in the `keeper/invariants.go` file. The `RegisterInvariants` method registers a route for each `Invariant` function in the [`InvariantRegistry`](#invariant-registry): - -```go reference -https://github.com/cosmos/cosmos-sdk/blob/v0.50.0-alpha.0/x/staking/keeper/invariants.go#L12-L22 -``` - -For more, see an example of [`Invariant`s implementation from the `staking` module](https://github.com/cosmos/cosmos-sdk/blob/v0.50.0-alpha.0/x/staking/keeper/invariants.go). - -## Invariant Registry - -The `InvariantRegistry` is a registry where the `Invariant`s of all the modules of an application are registered. There is only one `InvariantRegistry` per **application**, meaning module developers need not implement their own `InvariantRegistry` when building a module. **All module developers need to do is to register their modules' invariants in the `InvariantRegistry`, as explained in the section above**. The rest of this section gives more information on the `InvariantRegistry` itself, and does not contain anything directly relevant to module developers. - -At its core, the `InvariantRegistry` is defined in the Cosmos SDK as an interface: - -```go reference -https://github.com/cosmos/cosmos-sdk/blob/v0.50.0-alpha.0/types/invariant.go#L14-L17 -``` - -Typically, this interface is implemented in the `keeper` of a specific module. The most used implementation of an `InvariantRegistry` can be found in the `crisis` module: - -```go reference -https://github.com/cosmos/cosmos-sdk/blob/v0.50.0-alpha.0/x/crisis/keeper/keeper.go#L48-L50 -``` - -The `InvariantRegistry` is therefore typically instantiated by instantiating the `keeper` of the `crisis` module in the [application's constructor function](../../learn/beginner/00-app-anatomy.md#constructor-function). - -`Invariant`s can be checked manually via [`message`s](./02-messages-and-queries.md), but most often they are checked automatically at the end of each block. Here is an example from the `crisis` module: - -```go reference -https://github.com/cosmos/cosmos-sdk/blob/v0.50.0-alpha.0/x/crisis/abci.go#L13-L23 -``` - -In both cases, if one of the `Invariant`s returns false, the `InvariantRegistry` can trigger special logic (e.g. have the application panic and print the `Invariant`s message in the log). diff --git a/docs/build/building-modules/15-depinject.md b/docs/build/building-modules/15-depinject.md index e9a5f4486eb..c07c9aa96d2 100644 --- a/docs/build/building-modules/15-depinject.md +++ b/docs/build/building-modules/15-depinject.md @@ -52,7 +52,7 @@ https://github.com/cosmos/cosmos-sdk/blob/v0.50.0-alpha.0/api/cosmos/group/modul ``` :::note -Pulsar is optional. The official [`protoc-gen-go`](https://developers.google.com/protocol-buffers/docs/reference/go-generated) can be used as well. +Pulsar is optional. The official [`protoc-gen-go`](https://protobuf.dev/reference/go/go-generated/) can be used as well. ::: ## Dependency Definition diff --git a/docs/build/building-modules/16-testing.md b/docs/build/building-modules/16-testing.md index 65ed52a8a14..6b9441bbee0 100644 --- a/docs/build/building-modules/16-testing.md +++ b/docs/build/building-modules/16-testing.md @@ -104,7 +104,7 @@ System tests are at the top of the [test pyramid](https://martinfowler.com/artic They test the whole application flow as black box, from the user perspective. They are located under [`/tests/systemtests`](https://github.com/cosmos/cosmos-sdk/tree/main/tests/systemtests). For that, the SDK is using the `simapp` binary, but you should use your own binary. -More details about system test can be found in [building-apps](https://docs.cosmos.network/main/build/building-apps/06-system-tests.md) +More details about system test can be found in [building-apps](https://docs.cosmos.network/main/build/building-apps/system-tests) ## Learn More diff --git a/docs/build/building-modules/17-preblock.md b/docs/build/building-modules/17-preblock.md deleted file mode 100644 index 81c81bd94b6..00000000000 --- a/docs/build/building-modules/17-preblock.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -sidebar_position: 1 ---- - -# PreBlocker - -:::note Synopsis -`PreBlocker` is optional method module developers can implement in their module. They will be triggered before [`BeginBlock`](../../learn/advanced/00-baseapp.md#beginblock). -::: - -:::note Pre-requisite Readings - -* [Module Manager](./01-module-manager.md) - -::: - -## PreBlocker - -There are two semantics around the new lifecycle method: - -* It runs before the `BeginBlocker` of all modules -* It can modify consensus parameters in storage, and signal the caller through the return value. - -Modules are required to get the consensus params from the consensus module. Consensus params located in `sdk.Context` were deprecated and should be treated as unsafe. `sdk.Context` is deprecated due to it being a global state within the entire state machine, it has been replaced with `appmodule.Environment`. diff --git a/docs/build/building-modules/18-define-hooks.md b/docs/build/building-modules/18-define-hooks.md new file mode 100644 index 00000000000..8b19f238b0e --- /dev/null +++ b/docs/build/building-modules/18-define-hooks.md @@ -0,0 +1,57 @@ +--- +sidebar_position: 1 +--- + +# Hooks + +Hooks are functions that are called before and/or after certain events in the module's lifecycle. + +## Defining Hooks + +1. Define the hook interface and a wrapper implementing `depinject.OnePerModuleType`: + + ```go reference + https://github.com/cosmos/cosmos-sdk/blob/71c603a2a5a103df00f216d78ec8b108ed64ae28/testutil/x/counter/types/expected_keepers.go#L5-L12 + ``` + +2. Add a `CounterHooks` field to the keeper: + + ```go reference + https://github.com/cosmos/cosmos-sdk/blob/71c603a2a5a103df00f216d78ec8b108ed64ae28/testutil/x/counter/keeper/keeper.go#L25 + + ``` + +3. Create a `depinject` invoker function + + ```go reference + https://github.com/cosmos/cosmos-sdk/blob/71c603a2a5a103df00f216d78ec8b108ed64ae28/testutil/x/counter/depinject.go#L53-L75 + ``` + +4. Inject the hooks during app initialization: + + ```go + appConfig = appconfig.Compose(&appv1alpha1.Config{ + Modules: []*appv1alpha1.ModuleConfig{ + // .... + { + Name: types.ModuleName, + Config: appconfig.WrapAny(&types.Module{}), + }, + } + }) + appConfig = depinject.Configs( + AppConfig(), + runtime.DefaultServiceBindings(), + depinject.Supply( + logger, + viper, + map[string]types.CounterHooksWrapper{ + "counter": types.CounterHooksWrapper{&types.Hooks{}}, + }, + )) + ``` + +## Examples in the SDK + +For examples of hooks implementation in the Cosmos SDK, refer to the [Epochs Hooks documentation](https://docs.cosmos.network/main/build/modules/epochs#hooks) and [Distribution Hooks Documentation](https://docs.cosmos.network/main/build/modules/distribution#hooks). + diff --git a/docs/build/packages/README.md b/docs/build/packages/README.md index ce70c52e2b5..cbb150f501b 100644 --- a/docs/build/packages/README.md +++ b/docs/build/packages/README.md @@ -14,26 +14,40 @@ For more information on SDK tooling, see the [Tooling](https://docs.cosmos.netwo ## Core -* [Core](https://pkg.go.dev/cosmossdk.io/core) - Core library defining SDK interfaces ([ADR-063](https://docs.cosmos.network/main/architecture/adr-063-core-module-api)) +* [Core](https://pkg.go.dev/cosmossdk.io/core) - Core library defining SDK modules and Server core interfaces ([ADR-063](https://docs.cosmos.network/main/architecture/adr-063-core-module-api)) * [API](https://pkg.go.dev/cosmossdk.io/api) - API library containing generated SDK Pulsar API * [Store](https://pkg.go.dev/cosmossdk.io/store) - Implementation of the Cosmos SDK store +* [Store/v2](https://pkg.go.dev/cosmossdk.io/store/v2) - Implementation of the Cosmos SDK store + +## V2 + +* [Server/v2/stf](https://pkg.go.dev/cosmossdk.io/server/v2/stf) - State Transition Function (STF) library for Cosmos SDK v2 +* [Server/v2/appmanager](https://pkg.go.dev/cosmossdk.io/server/v2/appmanager) - App coordinator for Cosmos SDK v2 +* [runtime/v2](https://pkg.go.dev/cosmossdk.io/runtime/v2) - Runtime library for Cosmos SDK v2 +* [Server/v2](https://pkg.go.dev/cosmossdk.io/server/v2) - Global server library for Cosmos SDK v2 +* [Server/v2/cometbft](https://pkg.go.dev/cosmossdk.io/server/v2/cometbft) - CometBFT Server implementation for Cosmos SDK v2 ## State Management * [Collections](./02-collections.md) - State management library * [ORM](./03-orm.md) - State management library +* [Schema](https://pkg.go.dev/cosmossdk.io/schema) - Logical representation of module state schemas +* [PostgreSQL indexer](https://pkg.go.dev/cosmossdk.io/indexer/postgres) - PostgreSQL indexer for Cosmos SDK modules -## Automation +## UX * [Depinject](./01-depinject.md) - Dependency injection framework * [Client/v2](https://pkg.go.dev/cosmossdk.io/client/v2) - Library powering [AutoCLI](https://docs.cosmos.network/main/core/autocli) ## Utilities +* [Core/Testing](https://pkg.go.dev/cosmossdk.io/core/testing) - Mocking library for SDK modules * [Log](https://pkg.go.dev/cosmossdk.io/log) - Logging library * [Errors](https://pkg.go.dev/cosmossdk.io/errors) - Error handling library +* [Errors/v2](https://pkg.go.dev/cosmossdk.io/errors/v2) - Error handling library * [Math](https://pkg.go.dev/cosmossdk.io/math) - Math library for SDK arithmetic operations ## Example +* [SimApp v2](https://pkg.go.dev/cosmossdk.io/simapp/v2) - SimApp/v2 is **the** sample Cosmos SDK v2 chain. This package should not be imported in your application. * [SimApp](https://pkg.go.dev/cosmossdk.io/simapp) - SimApp is **the** sample Cosmos SDK chain. This package should not be imported in your application. diff --git a/docs/build/tooling/00-protobuf.md b/docs/build/tooling/00-protobuf.md index d9a210a7857..849a7974eab 100644 --- a/docs/build/tooling/00-protobuf.md +++ b/docs/build/tooling/00-protobuf.md @@ -4,21 +4,17 @@ sidebar_position: 1 # Protocol Buffers -It is known that Cosmos SDK uses protocol buffers extensively, this document is meant to provide a guide on how it is used in the cosmos-sdk. +Cosmos SDK uses protocol buffers extensively, this document is meant to provide a guide on how it is used in the cosmos-sdk. -To generate the proto file, the Cosmos SDK uses a docker image, this image is provided to all to use as well. The latest version is `ghcr.io/cosmos/proto-builder:0.12.x` +To generate the proto file, the Cosmos SDK uses a docker image, this image is provided to all to use as well. The latest version is `ghcr.io/cosmos/proto-builder:0.15.x` Below is the example of the Cosmos SDK's commands for generating, linting, and formatting protobuf files that can be reused in any applications makefile. ```go reference -https://github.com/cosmos/cosmos-sdk/blob/v0.50.0-alpha.0/Makefile#L411-L432 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/scripts/build/protobuf.mk#L1-L10 ``` -The script used to generate the protobuf files can be found in the `scripts/` directory. - -```shell reference -https://github.com/cosmos/cosmos-sdk/blob/v0.50.0-alpha.0/scripts/protocgen.sh -``` +The [`protocgen.sh`](https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/scripts/protocgen.sh) script used to generate the protobuf files via buf can be found in the `scripts/` directory. ## Buf @@ -36,7 +32,9 @@ https://github.com/cosmos/cosmos-sdk/blob/main/buf.work.yaml#L6-L9 ### Proto Directory -Next is the `proto/` directory where all of our protobuf files live. In here there are many different buf files defined each serving a different purpose. +The `proto/` directory where all of global protobuf files live. +In here there are many different buf files defined each serving a different purpose. +Modules proto files are defined in their respective module directories (in the SDK `x/{moduleName}/proto`). ```bash ├── README.md @@ -50,8 +48,6 @@ Next is the `proto/` directory where all of our protobuf files live. In here the └── tendermint ``` -The above diagram all the files and directories within the Cosmos SDK `proto/` directory. - #### `buf.gen.gogo.yaml` `buf.gen.gogo.yaml` defines how the protobuf files should be generated for use with in the module. This file uses [gogoproto](https://github.com/gogo/protobuf), a separate generator from the google go-proto generator that makes working with various objects more ergonomic, and it has more performant encode and decode steps @@ -60,10 +56,6 @@ The above diagram all the files and directories within the Cosmos SDK `proto/` d https://github.com/cosmos/cosmos-sdk/blob/main/proto/buf.gen.gogo.yaml#L1-L9 ``` -:::tip -Example of how to define `gen` files can be found [here](https://docs.buf.build/tour/generate-go-code) -::: - #### `buf.gen.pulsar.yaml` `buf.gen.pulsar.yaml` defines how protobuf files should be generated using the [new golang apiv2 of protobuf](https://go.dev/blog/protobuf-apiv2). This generator is used instead of the google go-proto generator because it has some extra helpers for Cosmos SDK applications and will have more performant encode and decode than the google go-proto generator. You can follow the development of this generator [here](https://github.com/cosmos/cosmos-proto). @@ -72,10 +64,6 @@ Example of how to define `gen` files can be found [here](https://docs.buf.build/ https://github.com/cosmos/cosmos-sdk/blob/main/proto/buf.gen.pulsar.yaml#L1-L18 ``` -:::tip -Example of how to define `gen` files can be found [here](https://docs.buf.build/tour/generate-go-code) -::: - #### `buf.gen.swagger.yaml` `buf.gen.swagger.yaml` generates the swagger documentation for the query and messages of the chain. This will only define the REST API end points that were defined in the query and msg servers. You can find examples of this [here](https://github.com/cosmos/cosmos-sdk/blob/main/x/bank/proto/cosmos/bank/v1beta1/query.proto) @@ -84,10 +72,6 @@ Example of how to define `gen` files can be found [here](https://docs.buf.build/ https://github.com/cosmos/cosmos-sdk/blob/main/proto/buf.gen.swagger.yaml#L1-L6 ``` -:::tip -Example of how to define `gen` files can be found [here](https://docs.buf.build/tour/generate-go-code) -::: - #### `buf.lock` This is an autogenerated file based off the dependencies required by the `.gen` files. There is no need to copy the current one. If you depend on cosmos-sdk proto definitions a new entry for the Cosmos SDK will need to be provided. The dependency you will need to use is `buf.build/cosmos/cosmos-sdk`. @@ -100,14 +84,11 @@ https://github.com/cosmos/cosmos-sdk/blob/main/proto/buf.lock#L1-L16 `buf.yaml` defines the [name of your package](https://github.com/cosmos/cosmos-sdk/blob/main/proto/buf.yaml#L3), which [breakage checker](https://buf.build/docs/tutorials/getting-started-with-buf-cli#detect-breaking-changes) to use and how to [lint your protobuf files](https://buf.build/docs/tutorials/getting-started-with-buf-cli#lint-your-api). +It is advised to use a tagged version of the buf modules corresponding to the version of the Cosmos SDK being are used. + ```go reference https://github.com/cosmos/cosmos-sdk/blob/main/proto/buf.yaml#L1-L24 ``` We use a variety of linters for the Cosmos SDK protobuf files. The repo also checks this in ci. - A reference to the github actions can be found [here](https://github.com/cosmos/cosmos-sdk/blob/main/.github/workflows/proto.yml#L1-L32) - -```go reference -https://github.com/cosmos/cosmos-sdk/blob/main/.github/workflows/proto.yml#L1-L32 -``` diff --git a/docs/learn/advanced/00-baseapp.md b/docs/learn/advanced/00-baseapp.md index 20968f91bd0..46f6cce403f 100644 --- a/docs/learn/advanced/00-baseapp.md +++ b/docs/learn/advanced/00-baseapp.md @@ -394,9 +394,9 @@ The `AnteHandler` operates on a copy of the cached context, allowing it to perfo Key operations performed by the `AnteHandler` include: -- **Signature Verification**: Ensures that the transaction's signatures are valid. -- **Sequence Checking**: Verifies and increments the sequence numbers to prevent replay attacks. -- **Fee Deduction**: Deducts the transaction fees from the accounts involved, typically starting with the first signer. +* **Signature Verification**: Ensures that the transaction's signatures are valid. +* **Sequence Checking**: Verifies and increments the sequence numbers to prevent replay attacks. +* **Fee Deduction**: Deducts the transaction fees from the accounts involved, typically starting with the first signer. These operations are crucial for maintaining the security and integrity of transactions on the blockchain. @@ -460,7 +460,7 @@ https://github.com/cosmos/cosmos-sdk/blob/v0.50.0-alpha.0/baseapp/abci.go#L623 #### PreBlock -* Run the application's [`preBlocker()`](../beginner/00-app-anatomy.md#preblocker), which mainly runs the [`PreBlocker()`](../../build/building-modules/17-preblock.md#preblock) method of each of the modules. +* Run the application's [`preBlocker()`](../beginner/00-app-anatomy.md#preblocker), which mainly runs the [`PreBlocker()`](../../build/building-modules/06-preblock-beginblock-endblock.md#preblock) method of each of the modules. #### BeginBlock @@ -473,7 +473,7 @@ https://github.com/cosmos/cosmos-sdk/blob/v0.50.0-alpha.0/baseapp/abci.go#L623 This function also resets the [main gas meter](../beginner/04-gas-fees.md#main-gas-meter). * Initialize the [block gas meter](../beginner/04-gas-fees.md#block-gas-meter) with the `maxGas` limit. The `gas` consumed within the block cannot go above `maxGas`. This parameter is defined in the application's consensus parameters. -* Run the application's [`beginBlocker()`](../beginner/00-app-anatomy.md#beginblocker-and-endblocker), which mainly runs the [`BeginBlocker()`](../../build/building-modules/06-beginblock-endblock.md#beginblock) method of each of the modules. +* Run the application's [`beginBlocker()`](../beginner/00-app-anatomy.md#beginblocker-and-endblocker), which mainly runs the [`BeginBlocker()`](../../build/building-modules/06-preblock-beginblock-endblock.md#beginblock) method of each of the modules. * Set the [`VoteInfos`](https://docs.cometbft.com/v1.0/spec/abci/abci++_methods#voteinfo) of the application, i.e. the list of validators whose _precommit_ for the previous block was included by the proposer of the current block. This information is carried into the [`Context`](./02-context.md) so that it can be used during transaction execution and EndBlock. #### Transaction Execution diff --git a/docs/learn/advanced/05-encoding.md b/docs/learn/advanced/05-encoding.md index 4a3af677d61..13703f892ec 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://protobuf.dev/), specifically the +[gogoprotobuf](https://github.com/cosmos/gogoproto/) implementation, which is a subset of +[Proto3](https://protobuf.dev/programming-guides/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 @@ -55,7 +52,7 @@ Modules are encouraged to utilize Protobuf encoding for their respective types. ### Guidelines for protobuf message definitions -In addition to [following official Protocol Buffer guidelines](https://developers.google.com/protocol-buffers/docs/proto3#simple), we recommend using these annotations in .proto files when dealing with interfaces: +In addition to [following official Protocol Buffer guidelines](https://protobuf.dev/programming-guides/proto3/#simple), we recommend using these annotations in .proto files when dealing with interfaces: * use `cosmos_proto.accepts_interface` to annotate `Any` fields that accept interfaces * pass the same fully qualified name as `protoName` to `InterfaceRegistry.RegisterInterface` @@ -243,5 +240,5 @@ Protobuf types can be defined to encode: #### Naming and conventions -We encourage developers to follow industry guidelines: [Protocol Buffers style guide](https://developers.google.com/protocol-buffers/docs/style) +We encourage developers to follow industry guidelines: [Protocol Buffers style guide](https://protobuf.dev/programming-guides/style/) and [Buf](https://buf.build/docs/style-guide), see more details in [ADR 023](../../architecture/adr-023-protobuf-naming.md) diff --git a/docs/learn/advanced/17-core.md b/docs/learn/advanced/17-core.md new file mode 100644 index 00000000000..85f5b110cec --- /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`. diff --git a/docs/learn/beginner/00-app-anatomy.md b/docs/learn/beginner/00-app-anatomy.md index 9757e7e95af..deae8303498 100644 --- a/docs/learn/beginner/00-app-anatomy.md +++ b/docs/learn/beginner/00-app-anatomy.md @@ -87,7 +87,6 @@ Here are the main actions performed by this function: * Instantiate all the [`keeper`](#keeper) objects defined in the application's `type` using the `NewKeeper` function of each of the application's modules. Note that keepers must be instantiated in the correct order, as the `NewKeeper` of one module might require a reference to another module's `keeper`. * Instantiate the application's [module manager](../../build/building-modules/01-module-manager.md#manager) with the [`AppModule`](#application-module-interface) object of each of the application's modules. * With the module manager, initialize the application's [`Msg` services](../advanced/00-baseapp.md#msg-services), [gRPC `Query` services](../advanced/00-baseapp.md#grpc-query-services), [legacy `Msg` routes](../advanced/00-baseapp.md#routing), and [legacy query routes](../advanced/00-baseapp.md#query-routing). When a transaction is relayed to the application by CometBFT via the ABCI, it is routed to the appropriate module's [`Msg` service](#msg-services) using the routes defined here. Likewise, when a gRPC query request is received by the application, it is routed to the appropriate module's [`gRPC query service`](#grpc-query-services) using the gRPC routes defined here. The Cosmos SDK still supports legacy `Msg`s and legacy CometBFT queries, which are routed using the legacy `Msg` routes and the legacy query routes, respectively. -* With the module manager, register the [application's modules' invariants](../../build/building-modules/07-invariants.md). Invariants are variables (e.g. total supply of a token) that are evaluated at the end of each block. The process of checking invariants is done via a special module called the [`InvariantsRegistry`](../../build/building-modules/07-invariants.md#invariant-registry). The value of the invariant should be equal to a predicted value defined in the module. Should the value be different than the predicted one, special logic defined in the invariant registry is triggered (usually the chain is halted). This is useful to make sure that no critical bug goes unnoticed, producing long-lasting effects that are hard to fix. * With the module manager, set the order of execution between the `InitGenesis`, `PreBlocker`, `BeginBlocker`, and `EndBlocker` functions of each of the [application's modules](#application-module-interface). Note that not all modules implement these functions. * Set the remaining application parameters: * [`InitChainer`](#initchainer): used to initialize the application when it is first started. @@ -136,7 +135,7 @@ The new ctx must be passed to all the other lifecycle methods. The Cosmos SDK offers developers the possibility to implement automatic execution of code as part of their application. This is implemented through two functions called `BeginBlocker` and `EndBlocker`. They are called when the application receives the `FinalizeBlock` messages from the CometBFT consensus engine, which happens respectively at the beginning and at the end of each block. The application must set the `BeginBlocker` and `EndBlocker` in its [constructor](#constructor-function) via the [`SetBeginBlocker`](https://pkg.go.dev/github.com/cosmos/cosmos-sdk/baseapp#BaseApp.SetBeginBlocker) and [`SetEndBlocker`](https://pkg.go.dev/github.com/cosmos/cosmos-sdk/baseapp#BaseApp.SetEndBlocker) methods. -In general, the `BeginBlocker` and `EndBlocker` functions are mostly composed of the [`BeginBlock` and `EndBlock`](../../build/building-modules/06-beginblock-endblock.md) functions of each of the application's modules. This is done by calling the `BeginBlock` and `EndBlock` functions of the module manager, which in turn calls the `BeginBlock` and `EndBlock` functions of each of the modules it contains. Note that the order in which the modules' `BeginBlock` and `EndBlock` functions must be called has to be set in the module manager using the `SetOrderBeginBlockers` and `SetOrderEndBlockers` methods, respectively. This is done via the [module manager](../../build/building-modules/01-module-manager.md) in the [application's constructor](#constructor-function), and the `SetOrderBeginBlockers` and `SetOrderEndBlockers` methods have to be called before the `SetBeginBlocker` and `SetEndBlocker` functions. +In general, the `BeginBlocker` and `EndBlocker` functions are mostly composed of the [`BeginBlock` and `EndBlock`](../../build/building-modules/06-preblock-beginblock-endblock.md) functions of each of the application's modules. This is done by calling the `BeginBlock` and `EndBlock` functions of the module manager, which in turn calls the `BeginBlock` and `EndBlock` functions of each of the modules it contains. Note that the order in which the modules' `BeginBlock` and `EndBlock` functions must be called has to be set in the module manager using the `SetOrderBeginBlockers` and `SetOrderEndBlockers` methods, respectively. This is done via the [module manager](../../build/building-modules/01-module-manager.md) in the [application's constructor](#constructor-function), and the `SetOrderBeginBlockers` and `SetOrderEndBlockers` methods have to be called before the `SetBeginBlocker` and `SetEndBlocker` functions. As a sidenote, it is important to remember that application-specific blockchains are deterministic. Developers must be careful not to introduce non-determinism in `BeginBlocker` or `EndBlocker`, and must also be careful not to make them too computationally expensive, as [gas](./04-gas-fees.md) does not constrain the cost of `BeginBlocker` and `EndBlocker` execution. @@ -182,7 +181,7 @@ Modules must implement [interfaces](../../build/building-modules/01-module-manag ### `Msg` Services -Each application module defines two [Protobuf services](https://developers.google.com/protocol-buffers/docs/proto#services): one `Msg` service to handle messages, and one gRPC `Query` service to handle queries. If we consider the module as a state-machine, then a `Msg` service is a set of state transition RPC methods. +Each application module defines two [Protobuf services](https://protobuf.dev/programming-guides/proto2/): one `Msg` service to handle messages, and one gRPC `Query` service to handle queries. If we consider the module as a state-machine, then a `Msg` service is a set of state transition RPC methods. Each Protobuf `Msg` service method is 1:1 related to a Protobuf request type, which must implement `sdk.Msg` interface. Note that `sdk.Msg`s are bundled in [transactions](../advanced/01-transactions.md), and each transaction contains one or multiple messages. @@ -209,7 +208,7 @@ Each module should also implement the `RegisterServices` method as part of the [ gRPC `Query` services allow users to query the state using [gRPC](https://grpc.io). They are enabled by default, and can be configured under the `grpc.enable` and `grpc.address` fields inside [`app.toml`](../../user/run-node/01-run-node.md#configuring-the-node-using-apptoml-and-configtoml). -gRPC `Query` services are defined in the module's Protobuf definition files, specifically inside `query.proto`. The `query.proto` definition file exposes a single `Query` [Protobuf service](https://developers.google.com/protocol-buffers/docs/proto#services). Each gRPC query endpoint corresponds to a service method, starting with the `rpc` keyword, inside the `Query` service. +gRPC `Query` services are defined in the module's Protobuf definition files, specifically inside `query.proto`. The `query.proto` definition file exposes a single `Query` [Protobuf service](https://protobuf.dev/programming-guides/proto2/). Each gRPC query endpoint corresponds to a service method, starting with the `rpc` keyword, inside the `Query` service. Protobuf generates a `QueryServer` interface for each module, containing all the service methods. A module's [`keeper`](#keeper) then needs to implement this `QueryServer` interface, by providing the concrete implementation of each service method. This concrete implementation is the handler of the corresponding gRPC query endpoint. diff --git a/docs/learn/beginner/02-query-lifecycle.md b/docs/learn/beginner/02-query-lifecycle.md index 2c3600f7fe3..bdaa5176699 100644 --- a/docs/learn/beginner/02-query-lifecycle.md +++ b/docs/learn/beginner/02-query-lifecycle.md @@ -41,7 +41,7 @@ The CLI understands a specific set of commands, defined in a hierarchical struct ### gRPC -Another interface through which users can make queries is [gRPC](https://grpc.io) requests to a [gRPC server](../advanced/06-grpc_rest.md#grpc-server). The endpoints are defined as [Protocol Buffers](https://developers.google.com/protocol-buffers) service methods inside `.proto` files, written in Protobuf's own language-agnostic interface definition language (IDL). The Protobuf ecosystem developed tools for code-generation from `*.proto` files into various languages. These tools allow to build gRPC clients easily. +Another interface through which users can make queries is [gRPC](https://grpc.io) requests to a [gRPC server](../advanced/06-grpc_rest.md#grpc-server). The endpoints are defined as [Protocol Buffers](https://protobuf.dev/) service methods inside `.proto` files, written in Protobuf's own language-agnostic interface definition language (IDL). The Protobuf ecosystem developed tools for code-generation from `*.proto` files into various languages. These tools allow to build gRPC clients easily. One such tool is [grpcurl](https://github.com/fullstorydev/grpcurl), and a gRPC request for `MyQuery` using this client looks like: diff --git a/docs/learn/beginner/04-gas-fees.md b/docs/learn/beginner/04-gas-fees.md index 783a2228b82..63ac006a839 100644 --- a/docs/learn/beginner/04-gas-fees.md +++ b/docs/learn/beginner/04-gas-fees.md @@ -52,7 +52,7 @@ By default, the Cosmos SDK makes use of two different gas meters, the [main gas `ctx.GasMeter()` is the main gas meter of the application. The main gas meter is initialized in `FinalizeBlock` via `setFinalizeBlockState`, and then tracks gas consumption during execution sequences that lead to state-transitions, i.e. those originally triggered by [`FinalizeBlock`](../advanced/00-baseapp.md#finalizeblock). At the beginning of each transaction execution, the main gas meter **must be set to 0** in the [`AnteHandler`](#antehandler), so that it can track gas consumption per-transaction. -Gas consumption can be done manually, generally by the module developer in the [`BeginBlocker`, `EndBlocker`](../../build/building-modules/06-beginblock-endblock.md) or [`Msg` service](../../build/building-modules/03-msg-services.md), but most of the time it is done automatically whenever there is a read or write to the store. This automatic gas consumption logic is implemented in a special store called [`GasKv`](../advanced/04-store.md#gaskv-store). +Gas consumption can be done manually, generally by the module developer in the [`BeginBlocker`, `EndBlocker`](../../build/building-modules/06-preblock-beginblock-endblock.md) or [`Msg` service](../../build/building-modules/03-msg-services.md), but most of the time it is done automatically whenever there is a read or write to the store. This automatic gas consumption logic is implemented in a special store called [`GasKv`](../advanced/04-store.md#gaskv-store). ### Block Gas Meter diff --git a/docs/learn/intro/00-overview.md b/docs/learn/intro/00-overview.md index bb32d84a962..7b0a3da037b 100644 --- a/docs/learn/intro/00-overview.md +++ b/docs/learn/intro/00-overview.md @@ -9,13 +9,13 @@ sidebar_position: 1 The [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) is an open-source toolkit for building multi-asset public Proof-of-Stake (PoS) blockchains, like the Cosmos Hub, as well as permissioned Proof-of-Authority (PoA) blockchains. Blockchains built with the Cosmos SDK are generally referred to as **application-specific blockchains**. The goal of the Cosmos SDK is to allow developers to easily create custom blockchains from scratch that can natively interoperate with other blockchains. -We further this modular approach by allowing developers to plug and play with different consensus engines this can range from the [CometBFT](https://github.com/cometbft/cometbft) or [Rollkit](https://rollkit.dev/). +We further this modular approach by allowing developers to plug and play with different consensus engines this can range from [CometBFT](https://cometbft.com/) or [Rollkit](https://rollkit.dev/). SDK-based blockchains have the choice to use the predefined modules or to build their own modules. What this means is that developers can build a blockchain that is tailored to their specific use case, without having to worry about the low-level details of building a blockchain from scratch. Predefined modules include staking, governance, and token issuance, among others. What's more, the Cosmos SDK is a capabilities-based system that allows developers to better reason about the security of interactions between modules. For a deeper look at capabilities, jump to [Object-Capability Model](../advanced/10-ocap.md). -How you can look at this is if we imagine that the SDK is like a lego kit. You can choose to build the basic house from the instructions or you can choose to modify your house and add more floors, more doors, more windows. The choice is yours. +You can think of the SDK as a lego kit. You can choose to build the basic house from the instructions, or you can choose to modify your house and add more floors, more doors, more windows. The choice is yours. ## What are Application-Specific Blockchains @@ -27,17 +27,17 @@ Learn more about [application-specific blockchains](./01-why-app-specific.md). ## What is Modularity -Today there is a lot of talk around modularity and discussions between monolithic and modular. Originally the Cosmos SDK was built with a vision of modularity in mind. Modularity is derived from splitting a blockchain into customizable layers of execution, consensus, settlement and data availability, which is what the Cosmos SDK enables. This means that developers can plug and play, making their blockchain customisable by using different software for different layers. For example you can choose to build a vanilla chain and use the Cosmos SDK with CometBFT. CometBFT will be your consensus layer and the chain itself would be the settlement and execution layer. Another route could be to use the SDK with Rollkit and Celestia as your consensus and data availability layer. The benefit of modularity is that you can customize your chain to your specific use case. +Today, there is a lot of talk around modularity and discussions between monolithic and modular. Originally, the Cosmos SDK was built with a vision of modularity in mind. Modularity is derived from splitting a blockchain into customizable layers of execution, consensus, settlement and data availability, which is what the Cosmos SDK enables. This means that developers can plug and play, making their blockchain customizable by using different software for different layers. For example, you can choose to build a vanilla chain and use the Cosmos SDK with CometBFT. CometBFT will be your consensus layer and the chain itself would be the settlement and execution layer. Another route could be to use the SDK with Rollkit and [Celestia](https://celestia.org/) as your consensus and data availability layer. The benefit of modularity is that you can customize your chain to your specific use case. ## Why the Cosmos SDK The Cosmos SDK is the most advanced framework for building custom modular application-specific blockchains today. Here are a few reasons why you might want to consider building your decentralized application with the Cosmos SDK: -* It allows you to plug and play and customize your consensus layer. As above you can use Rollkit and Celestia as your consensus and data availability layer. This offers a lot of flexibility and customisation. -* Previously the default consensus engine available within the Cosmos SDK is [CometBFT](https://github.com/cometbft/cometbft). CometBFT is the most (and only) mature BFT consensus engine in existence. It is widely used across the industry and is considered the gold standard consensus engine for building Proof-of-Stake systems. +* It allows you to plug and play and customize your consensus layer. As mentioned above, you can use Rollkit and Celestia as your consensus and data availability layer. This offers a lot of flexibility and customization. +* Previously the default consensus engine available within the Cosmos SDK is [CometBFT](https://cometbft.com/). CometBFT is the most (and only) mature BFT consensus engine in existence. It is widely used across the industry and is considered the gold standard consensus engine for building Proof-of-Stake systems. * The Cosmos SDK is open-source and designed to make it easy to build blockchains out of composable [modules](../../build/modules). As the ecosystem of open-source Cosmos SDK modules grows, it will become increasingly easier to build complex decentralized platforms with it. * The Cosmos SDK is inspired by capabilities-based security, and informed by years of wrestling with blockchain state-machines. This makes the Cosmos SDK a very secure environment to build blockchains. -* Most importantly, the Cosmos SDK has already been used to build many application-specific blockchains that are already in production. Among others, we can cite [Cosmos Hub](https://hub.cosmos.network), [IRIS Hub](https://irisnet.org), [Binance Chain](https://docs.binance.org/), [Terra](https://terra.money/) or [Kava](https://www.kava.io/). [Many more](https://cosmos.network/ecosystem) are building on the Cosmos SDK. +* Most importantly, the Cosmos SDK has already been used to build many application-specific blockchains that are already in production. Among others, we can cite [Cosmos Hub](https://hub.cosmos.network), [Osmosis](https://osmosis.zone/), [Binance Chain](https://docs.binance.org/), [Terra](https://terra.money/) or [Dydx](https://dydx.exchange/). [Many more](https://cosmos.network/ecosystem) are building on the Cosmos SDK. ## Getting started with the Cosmos SDK diff --git a/docs/learn/intro/02-sdk-app-architecture.md b/docs/learn/intro/02-sdk-app-architecture.md index 6ace65e57ab..1f6d9df78d2 100644 --- a/docs/learn/intro/02-sdk-app-architecture.md +++ b/docs/learn/intro/02-sdk-app-architecture.md @@ -54,7 +54,7 @@ flowchart LR A -->|"For each T in B: apply(T)"| B ``` -In a blockchain context, the state machine is deterministic. This means that if a node is started at a given state and replays the same sequence of transactions, it will always end up with the same final state. +In a blockchain context, the state machine is [deterministic](https://en.wikipedia.org/wiki/Deterministic_system). This means that if a node is started at a given state and replays the same sequence of transactions, it will always end up with the same final state. The Cosmos SDK gives developers maximum flexibility to define the state of their application, transaction types and state transition functions. The process of building state machines with the Cosmos SDK will be described more in-depth in the following sections. But first, let us see how the state machine is replicated using various consensus engines, such as CometBFT. @@ -117,7 +117,7 @@ Note that **CometBFT only handles transaction bytes**. It has no knowledge of wh Here are the most important messages of the ABCI: -* `CheckTx`: When a transaction is received by CometBFT, it is passed to the application to check if a few basic requirements are met. `CheckTx` is used to protect the mempool of full-nodes against spam transactions. . A special handler called the [`AnteHandler`](../beginner/04-gas-fees.md#antehandler) is used to execute a series of validation steps such as checking for sufficient fees and validating the signatures. If the checks are valid, the transaction is added to the [mempool](https://docs.cometbft.com/v1.0/explanation/core/mempool) and relayed to peer nodes. Note that transactions are not processed (i.e. no modification of the state occurs) with `CheckTx` since they have not been included in a block yet. +* `CheckTx`: When a transaction is received by CometBFT, it is passed to the application to check if a few basic requirements are met. `CheckTx` is used to protect the mempool of full-nodes against spam transactions. A special handler called the [`AnteHandler`](../beginner/04-gas-fees.md#antehandler) is used to execute a series of validation steps such as checking for sufficient fees and validating the signatures. If the checks are valid, the transaction is added to the [mempool](https://docs.cometbft.com/v1.0/explanation/core/mempool) and relayed to peer nodes. Note that transactions are not processed (i.e. no modification of the state occurs) with `CheckTx` since they have not been included in a block yet. * `DeliverTx`: When a [valid block](https://docs.cometbft.com/v1.0/spec/core/data_structures#block) is received by CometBFT, each transaction in the block is passed to the application via `DeliverTx` in order to be processed. It is during this stage that the state transitions occur. The `AnteHandler` executes again, along with the actual [`Msg` service](../../build/building-modules/03-msg-services.md) RPC for each message in the transaction. * `BeginBlock`/`EndBlock`: These messages are executed at the beginning and the end of each block, whether the block contains transactions or not. It is useful to trigger automatic execution of logic. Proceed with caution though, as computationally expensive loops could slow down your blockchain, or even freeze it if the loop is infinite. @@ -136,4 +136,4 @@ If we use the example of Rollkit, a user initiates a transaction, which is then The Interoperability Layer enables communication and interaction between different blockchains. This layer facilitates cross-chain transactions and data sharing, allowing various blockchain networks to interoperate seamlessly. Interoperability is key for building a connected ecosystem of blockchains, enhancing their functionality and reach. -In this case we have separated the layers even further to really illustrate the components that make-up the blockchain architecture and it is important to note that the Cosmos SDK is designed to be interoperable with other blockchains. This is achieved through the use of the Inter-Blockchain Communication (IBC) protocol, which allows different blockchains to communicate and transfer assets between each other. +In this case we have separated the layers even further to really illustrate the components that make-up the blockchain architecture and it is important to note that the Cosmos SDK is designed to be interoperable with other blockchains. This is achieved through the use of the [Inter-Blockchain Communication (IBC) protocol](https://www.ibcprotocol.dev/), which allows different blockchains to communicate and transfer assets between each other. diff --git a/docs/learn/intro/03-sdk-design.md b/docs/learn/intro/03-sdk-design.md index e2edcd4efb0..9ae17b73bb1 100644 --- a/docs/learn/intro/03-sdk-design.md +++ b/docs/learn/intro/03-sdk-design.md @@ -22,7 +22,7 @@ Here is a simplified view of how transactions are handled by an application buil Here is an example of this from `simapp`, the Cosmos SDK demonstration app: ```go reference -https://github.com/cosmos/cosmos-sdk/blob/v0.50.0-alpha.0/simapp/app.go#L170-L212 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/simapp/app.go#L145-L186 ``` The goal of `baseapp` is to provide a secure interface between the store and the extensible state machine while defining as little about the state machine as possible (staying true to the ABCI). @@ -33,7 +33,7 @@ For more on `baseapp`, please click [here](../advanced/00-baseapp.md). The Cosmos SDK provides a [`multistore`](../advanced/04-store.md#multistore) for persisting state. The multistore allows developers to declare any number of [`KVStores`](../advanced/04-store.md#base-layer-kvstores). These `KVStores` only accept the `[]byte` type as value and therefore any custom structure needs to be marshalled using [a codec](../advanced/05-encoding.md) before being stored. -The multistore abstraction is used to divide the state in distinct compartments, each managed by its own module. For more on the multistore, click [here](../advanced/04-store.md#multistore) +The multistore abstraction is used to divide the state in distinct compartments, each managed by its own module. For more on the multistore, click [here](../advanced/04-store.md#multistore). ## Modules @@ -61,6 +61,6 @@ Cosmos SDK modules are defined in the `x/` folder of the Cosmos SDK. Some core m * `x/auth`: Used to manage accounts and signatures. * `x/bank`: Used to enable tokens and token transfers. -* `x/staking` + `x/slashing`: Used to build Proof-Of-Stake blockchains. +* `x/staking` + `x/slashing`: Used to build Proof-of-Stake blockchains. -In addition to the already existing modules in `x/`, that anyone can use in their app, the Cosmos SDK lets you build your own custom modules. You can check an [example of that in the tutorial](https://tutorials.cosmos.network/). +In addition to the already existing modules in `x/`, which anyone can use in their app, the Cosmos SDK lets you build your own custom modules. You can check an [example of that in the tutorial](https://tutorials.cosmos.network/). diff --git a/docs/learn/learn.md b/docs/learn/learn.md index b8f64821a90..3414fa1a203 100644 --- a/docs/learn/learn.md +++ b/docs/learn/learn.md @@ -3,8 +3,8 @@ sidebar_position: 0 --- # Learn -* [Introduction](./intro/00-overview.md) - Dive into the fundamentals of Cosmos SDK with an insightful introduction, -laying the groundwork for understanding blockchain development. In this section we provide a High-Level Overview of the SDK, then dive deeper into Core concepts such as Application-Specific Blockchains, Blockchain Architecture, and finally we begin to explore what are the main components of the SDK. +* [Introduction](./intro/00-overview.md) - Dive into the fundamentals of Cosmos SDK with an insightful introduction, +laying the groundwork for understanding blockchain development. In this section, we provide a High-Level Overview of the SDK, then dive deeper into Core concepts such as Application-Specific Blockchains, Blockchain Architecture, and finally, we begin to explore the main components of the SDK. * [Beginner](./beginner/00-app-anatomy.md) - Start your journey with beginner-friendly resources in the Cosmos SDK's "Learn" section, providing a gentle entry point for newcomers to blockchain development. Here we focus on a little more detail, covering the Anatomy of a Cosmos SDK Application, Transaction Lifecycles, Accounts and lastly, Gas and Fees. * [Advanced](./advanced/00-baseapp.md) - Level up your Cosmos SDK expertise with advanced topics, tailored for experienced diff --git a/go.mod b/go.mod index 316ed8448cf..b2dc62a8bc6 100644 --- a/go.mod +++ b/go.mod @@ -54,11 +54,11 @@ require ( github.com/stretchr/testify v1.9.0 github.com/tendermint/go-amino v0.16.0 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b - golang.org/x/crypto v0.27.0 + golang.org/x/crypto v0.28.0 golang.org/x/sync v0.8.0 google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 google.golang.org/grpc v1.67.1 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 gotest.tools/v3 v3.5.1 pgregory.net/rapid v1.1.0 sigs.k8s.io/yaml v1.4.0 @@ -163,9 +163,9 @@ require ( golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect diff --git a/go.sum b/go.sum index 5aff95150b0..39a05c2011f 100644 --- a/go.sum +++ b/go.sum @@ -512,8 +512,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -590,19 +590,19 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -660,8 +660,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/indexer/postgres/tests/go.mod b/indexer/postgres/tests/go.mod index b02fb01dd8a..f204ff8173a 100644 --- a/indexer/postgres/tests/go.mod +++ b/indexer/postgres/tests/go.mod @@ -26,10 +26,10 @@ require ( github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/tidwall/btree v1.7.0 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect pgregory.net/rapid v1.1.0 // indirect ) diff --git a/indexer/postgres/tests/go.sum b/indexer/postgres/tests/go.sum index 6fea319add7..176e5ddaa9a 100644 --- a/indexer/postgres/tests/go.sum +++ b/indexer/postgres/tests/go.sum @@ -40,14 +40,14 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofm github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/orm/go.mod b/orm/go.mod index 8bd1d5f47d9..03ab714a3d2 100644 --- a/orm/go.mod +++ b/orm/go.mod @@ -17,7 +17,7 @@ require ( github.com/stretchr/testify v1.9.0 golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 google.golang.org/grpc v1.67.1 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 gotest.tools/v3 v3.5.1 pgregory.net/rapid v1.1.0 ) diff --git a/orm/go.sum b/orm/go.sum index 0635f05e49b..fe975253538 100644 --- a/orm/go.sum +++ b/orm/go.sum @@ -251,8 +251,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/runtime/v2/app.go b/runtime/v2/app.go index b7104f9e477..d5999f3bd99 100644 --- a/runtime/v2/app.go +++ b/runtime/v2/app.go @@ -2,12 +2,9 @@ package runtime import ( "encoding/json" - "errors" - "slices" - - gogoproto "github.com/cosmos/gogoproto/proto" runtimev2 "cosmossdk.io/api/cosmos/app/runtime/v2" + appmodulev2 "cosmossdk.io/core/appmodule/v2" "cosmossdk.io/core/registry" "cosmossdk.io/core/transaction" "cosmossdk.io/log" @@ -37,15 +34,12 @@ type App[T transaction.Tx] struct { logger log.Logger config *runtimev2.Module - // modules configuration - storeKeys []string interfaceRegistrar registry.InterfaceRegistrar amino registry.AminoRegistrar moduleManager *MM[T] - // GRPCMethodsToMessageMap maps gRPC method name to a function that decodes the request - // bytes into a gogoproto.Message, which then can be passed to appmanager. - GRPCMethodsToMessageMap map[string]func() gogoproto.Message + // QueryHandlers defines the query handlers + QueryHandlers map[string]appmodulev2.Handler storeLoader StoreLoader } @@ -95,31 +89,10 @@ func (a *App[T]) Close() error { return nil } -// GetStoreKeys returns all the app store keys. -func (a *App[T]) GetStoreKeys() []string { - return a.storeKeys -} - -// UnsafeFindStoreKey fetches a registered StoreKey from the App in linear time. -// NOTE: This should only be used in testing. -func (a *App[T]) UnsafeFindStoreKey(storeKey string) (string, error) { - i := slices.IndexFunc(a.storeKeys, func(s string) bool { return s == storeKey }) - if i == -1 { - return "", errors.New("store key not found") - } - - return a.storeKeys[i], nil -} - -// GetStore returns the app store. -func (a *App[T]) GetStore() Store { - return a.db -} - func (a *App[T]) GetAppManager() *appmanager.AppManager[T] { return a.AppManager } -func (a *App[T]) GetGPRCMethodsToMessageMap() map[string]func() gogoproto.Message { - return a.GRPCMethodsToMessageMap +func (a *App[T]) GetQueryHandlers() map[string]appmodulev2.Handler { + return a.QueryHandlers } diff --git a/runtime/v2/builder.go b/runtime/v2/builder.go index b28ddbc741b..adb177d41eb 100644 --- a/runtime/v2/builder.go +++ b/runtime/v2/builder.go @@ -15,13 +15,15 @@ import ( "cosmossdk.io/server/v2/appmanager" "cosmossdk.io/server/v2/stf" "cosmossdk.io/server/v2/stf/branch" + "cosmossdk.io/store/v2/root" ) // AppBuilder is a type that is injected into a container by the runtime/v2 module // (as *AppBuilder) which can be used to create an app which is compatible with // the existing app.go initialization conventions. type AppBuilder[T transaction.Tx] struct { - app *App[T] + app *App[T] + storeBuilder root.Builder // the following fields are used to overwrite the default branch func(state store.ReaderMap) store.WriterMap @@ -62,14 +64,6 @@ func (a *AppBuilder[T]) RegisterModules(modules map[string]appmodulev2.AppModule return nil } -// RegisterStores registers the provided store keys. -// This method should only be used for registering extra stores -// which is necessary for modules that not registered using the app config. -// To be used in combination of RegisterModules. -func (a *AppBuilder[T]) RegisterStores(keys ...string) { - a.app.storeKeys = append(a.app.storeKeys, keys...) -} - // Build builds an *App instance. func (a *AppBuilder[T]) Build(opts ...AppBuilderOption[T]) (*App[T], error) { for _, opt := range opts { @@ -93,8 +87,9 @@ func (a *AppBuilder[T]) Build(opts ...AppBuilderOption[T]) (*App[T], error) { } } + a.app.db = a.storeBuilder.Get() if a.app.db == nil { - return nil, fmt.Errorf("app.db is not set, it is required to build the app") + return nil, fmt.Errorf("storeBuilder did not return a db") } if err := a.app.moduleManager.RegisterServices(a.app); err != nil { @@ -149,7 +144,7 @@ func (a *AppBuilder[T]) Build(opts ...AppBuilderOption[T]) (*App[T], error) { return nil, errors.New("cannot init genesis on non-zero state") } genesisCtx := services.NewGenesisContext(a.branch(zeroState)) - genesisState, err := genesisCtx.Run(ctx, func(ctx context.Context) error { + genesisState, err := genesisCtx.Mutate(ctx, func(ctx context.Context) error { err = a.app.moduleManager.InitGenesisJSON(ctx, genesisJSON, txHandler) if err != nil { return fmt.Errorf("failed to init genesis: %w", err) @@ -205,7 +200,11 @@ func AppBuilderWithBranch[T transaction.Tx](branch func(state store.ReaderMap) s // AppBuilderWithTxValidator sets the tx validator for the app. // It overrides all default tx validators defined by modules. -func AppBuilderWithTxValidator[T transaction.Tx](txValidators func(ctx context.Context, tx T) error) AppBuilderOption[T] { +func AppBuilderWithTxValidator[T transaction.Tx]( + txValidators func( + ctx context.Context, tx T, + ) error, +) AppBuilderOption[T] { return func(a *AppBuilder[T]) { a.txValidator = txValidators } @@ -213,7 +212,11 @@ func AppBuilderWithTxValidator[T transaction.Tx](txValidators func(ctx context.C // AppBuilderWithPostTxExec sets logic that will be executed after each transaction. // When not provided, a no-op function will be used. -func AppBuilderWithPostTxExec[T transaction.Tx](postTxExec func(ctx context.Context, tx T, success bool) error) AppBuilderOption[T] { +func AppBuilderWithPostTxExec[T transaction.Tx]( + postTxExec func( + ctx context.Context, tx T, success bool, + ) error, +) AppBuilderOption[T] { return func(a *AppBuilder[T]) { a.postTxExec = postTxExec } diff --git a/runtime/v2/go.mod b/runtime/v2/go.mod index 1bd24cd1e26..c28b2b52a05 100644 --- a/runtime/v2/go.mod +++ b/runtime/v2/go.mod @@ -22,7 +22,7 @@ require ( cosmossdk.io/x/tx v0.13.3 github.com/cosmos/gogoproto v1.7.0 google.golang.org/grpc v1.67.1 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 ) require ( @@ -74,12 +74,12 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tendermint/go-amino v0.16.0 // indirect github.com/tidwall/btree v1.7.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/runtime/v2/go.sum b/runtime/v2/go.sum index b273f05e2e3..2ee0283004b 100644 --- a/runtime/v2/go.sum +++ b/runtime/v2/go.sum @@ -243,8 +243,8 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -294,16 +294,16 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -329,8 +329,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/runtime/v2/manager.go b/runtime/v2/manager.go index 02df9ac750f..2d6515b8122 100644 --- a/runtime/v2/manager.go +++ b/runtime/v2/manager.go @@ -230,15 +230,17 @@ func (m *MM[T]) ExportGenesisForModules( channels[moduleName] = make(chan genesisResult) go func(moduleI ModuleI, ch chan genesisResult) { genesisCtx := services.NewGenesisContext(stateFactory()) - _, _ = genesisCtx.Run(ctx, func(ctx context.Context) error { + err := genesisCtx.Read(ctx, func(ctx context.Context) error { jm, err := moduleI.ExportGenesis(ctx) if err != nil { - ch <- genesisResult{nil, err} return err } ch <- genesisResult{jm, nil} return nil }) + if err != nil { + ch <- genesisResult{nil, err} + } }(moduleI, channels[moduleName]) } @@ -615,46 +617,46 @@ func (m *MM[T]) assertNoForgottenModules( } func registerServices[T transaction.Tx](s appmodulev2.AppModule, app *App[T], registry *protoregistry.Files) error { - c := &configurator{ - grpcQueryDecoders: map[string]func() gogoproto.Message{}, - stfQueryRouter: app.queryRouterBuilder, - stfMsgRouter: app.msgRouterBuilder, - registry: registry, - err: nil, - } - + // case module with services if services, ok := s.(hasServicesV1); ok { + c := &configurator{ + queryHandlers: map[string]appmodulev2.Handler{}, + stfQueryRouter: app.queryRouterBuilder, + stfMsgRouter: app.msgRouterBuilder, + registry: registry, + err: nil, + } if err := services.RegisterServices(c); err != nil { return fmt.Errorf("unable to register services: %w", err) } - } else { - // If module not implement RegisterServices, register msg & query handler. - if module, ok := s.(appmodulev2.HasMsgHandlers); ok { - wrapper := stfRouterWrapper{stfRouter: app.msgRouterBuilder} - module.RegisterMsgHandlers(&wrapper) - if wrapper.error != nil { - return fmt.Errorf("unable to register handlers: %w", wrapper.error) - } - } - - if module, ok := s.(appmodulev2.HasQueryHandlers); ok { - wrapper := stfRouterWrapper{stfRouter: app.msgRouterBuilder} - module.RegisterQueryHandlers(&wrapper) - for path, decoder := range wrapper.decoders { - app.GRPCMethodsToMessageMap[path] = decoder - } + if c.err != nil { + app.logger.Warn("error registering services", "error", c.err) } + // merge maps + for path, decoder := range c.queryHandlers { + app.QueryHandlers[path] = decoder + } } - if c.err != nil { - app.logger.Warn("error registering services", "error", c.err) + // if module implements register msg handlers + if module, ok := s.(appmodulev2.HasMsgHandlers); ok { + wrapper := stfRouterWrapper{stfRouter: app.msgRouterBuilder} + module.RegisterMsgHandlers(&wrapper) + if wrapper.error != nil { + return fmt.Errorf("unable to register handlers: %w", wrapper.error) + } } - // merge maps - for path, decoder := range c.grpcQueryDecoders { - app.GRPCMethodsToMessageMap[path] = decoder + // if module implements register query handlers + if module, ok := s.(appmodulev2.HasQueryHandlers); ok { + wrapper := stfRouterWrapper{stfRouter: app.queryRouterBuilder} + module.RegisterQueryHandlers(&wrapper) + + for path, handler := range wrapper.handlers { + app.QueryHandlers[path] = handler + } } return nil @@ -663,9 +665,7 @@ func registerServices[T transaction.Tx](s appmodulev2.AppModule, app *App[T], re var _ grpc.ServiceRegistrar = (*configurator)(nil) type configurator struct { - // grpcQueryDecoders is required because module expose queries through gRPC - // this provides a way to route to modules using gRPC. - grpcQueryDecoders map[string]func() gogoproto.Message + queryHandlers map[string]appmodulev2.Handler stfQueryRouter *stf.MsgRouterBuilder stfMsgRouter *stf.MsgRouterBuilder @@ -697,28 +697,31 @@ func (c *configurator) RegisterService(sd *grpc.ServiceDesc, ss interface{}) { func (c *configurator) registerQueryHandlers(sd *grpc.ServiceDesc, ss interface{}) error { for _, md := range sd.Methods { // TODO(tip): what if a query is not deterministic? - requestFullName, err := registerMethod(c.stfQueryRouter, sd, md, ss) + + handler, err := grpcHandlerToAppModuleHandler(sd, md, ss) if err != nil { - return fmt.Errorf("unable to register query handler %s.%s: %w", sd.ServiceName, md.MethodName, err) + return fmt.Errorf("unable to make a appmodulev2.HandlerFunc from gRPC handler (%s, %s): %w", sd.ServiceName, md.MethodName, err) } - // register gRPC query method. - typ := gogoproto.MessageType(requestFullName) - if typ == nil { - return fmt.Errorf("unable to find message in gogotype registry: %w", err) - } - decoderFunc := func() gogoproto.Message { - return reflect.New(typ.Elem()).Interface().(gogoproto.Message) + // register to stf query router. + err = c.stfQueryRouter.RegisterHandler(gogoproto.MessageName(handler.MakeMsg()), handler.Func) + if err != nil { + return fmt.Errorf("unable to register handler to stf router (%s, %s): %w", sd.ServiceName, md.MethodName, err) } - methodName := fmt.Sprintf("/%s/%s", sd.ServiceName, md.MethodName) - c.grpcQueryDecoders[methodName] = decoderFunc + + // register query handler using the same mapping used in stf + c.queryHandlers[gogoproto.MessageName(handler.MakeMsg())] = handler } return nil } func (c *configurator) registerMsgHandlers(sd *grpc.ServiceDesc, ss interface{}) error { for _, md := range sd.Methods { - _, err := registerMethod(c.stfMsgRouter, sd, md, ss) + handler, err := grpcHandlerToAppModuleHandler(sd, md, ss) + if err != nil { + return err + } + err = c.stfMsgRouter.RegisterHandler(gogoproto.MessageName(handler.MakeMsg()), handler.Func) if err != nil { return fmt.Errorf("unable to register msg handler %s.%s: %w", sd.ServiceName, md.MethodName, err) } @@ -726,32 +729,27 @@ func (c *configurator) registerMsgHandlers(sd *grpc.ServiceDesc, ss interface{}) return nil } -// requestFullNameFromMethodDesc returns the fully-qualified name of the request message of the provided service's method. -func requestFullNameFromMethodDesc(sd *grpc.ServiceDesc, method grpc.MethodDesc) (protoreflect.FullName, error) { - methodFullName := protoreflect.FullName(fmt.Sprintf("%s.%s", sd.ServiceName, method.MethodName)) - desc, err := gogoproto.HybridResolver.FindDescriptorByName(methodFullName) - if err != nil { - return "", fmt.Errorf("cannot find method descriptor %s", methodFullName) - } - methodDesc, ok := desc.(protoreflect.MethodDescriptor) - if !ok { - return "", fmt.Errorf("invalid method descriptor %s", methodFullName) - } - return methodDesc.Input().FullName(), nil -} - -func registerMethod( - stfRouter *stf.MsgRouterBuilder, +// grpcHandlerToAppModuleHandler converts a gRPC handler into an appmodulev2.HandlerFunc. +func grpcHandlerToAppModuleHandler( sd *grpc.ServiceDesc, md grpc.MethodDesc, ss interface{}, -) (string, error) { - requestName, err := requestFullNameFromMethodDesc(sd, md) +) (appmodulev2.Handler, error) { + requestName, responseName, err := requestFullNameFromMethodDesc(sd, md) if err != nil { - return "", err + return appmodulev2.Handler{}, err } - return string(requestName), stfRouter.RegisterHandler(string(requestName), func( + requestTyp := gogoproto.MessageType(string(requestName)) + if requestTyp == nil { + return appmodulev2.Handler{}, fmt.Errorf("no proto message found for %s", requestName) + } + responseTyp := gogoproto.MessageType(string(responseName)) + if responseTyp == nil { + return appmodulev2.Handler{}, fmt.Errorf("no proto message found for %s", responseName) + } + + handlerFunc := func( ctx context.Context, msg transaction.Msg, ) (resp transaction.Msg, err error) { @@ -760,7 +758,17 @@ func registerMethod( return nil, err } return res.(transaction.Msg), nil - }) + } + + return appmodulev2.Handler{ + Func: handlerFunc, + MakeMsg: func() transaction.Msg { + return reflect.New(requestTyp.Elem()).Interface().(transaction.Msg) + }, + MakeMsgResp: func() transaction.Msg { + return reflect.New(responseTyp.Elem()).Interface().(transaction.Msg) + }, + }, nil } func noopDecoder(_ interface{}) error { return nil } @@ -776,6 +784,22 @@ func messagePassingInterceptor(msg transaction.Msg) grpc.UnaryServerInterceptor } } +// requestFullNameFromMethodDesc returns the fully-qualified name of the request message and response of the provided service's method. +func requestFullNameFromMethodDesc(sd *grpc.ServiceDesc, method grpc.MethodDesc) ( + protoreflect.FullName, protoreflect.FullName, error, +) { + methodFullName := protoreflect.FullName(fmt.Sprintf("%s.%s", sd.ServiceName, method.MethodName)) + desc, err := gogoproto.HybridResolver.FindDescriptorByName(methodFullName) + if err != nil { + return "", "", fmt.Errorf("cannot find method descriptor %s", methodFullName) + } + methodDesc, ok := desc.(protoreflect.MethodDescriptor) + if !ok { + return "", "", fmt.Errorf("invalid method descriptor %s", methodFullName) + } + return methodDesc.Input().FullName(), methodDesc.Output().FullName(), nil +} + // defaultMigrationsOrder returns a default migrations order: ascending alphabetical by module name, // except x/auth which will run last, see: // https://github.com/cosmos/cosmos-sdk/issues/10591 @@ -815,7 +839,7 @@ type stfRouterWrapper struct { error error - decoders map[string]func() gogoproto.Message + handlers map[string]appmodulev2.Handler } func (s *stfRouterWrapper) RegisterHandler(handler appmodulev2.Handler) { @@ -831,7 +855,7 @@ func (s *stfRouterWrapper) RegisterHandler(handler appmodulev2.Handler) { // also make the decoder if s.error == nil { - s.decoders = map[string]func() gogoproto.Message{} + s.handlers = map[string]appmodulev2.Handler{} } - s.decoders[requestName] = handler.MakeMsg + s.handlers[requestName] = handler } diff --git a/runtime/v2/module.go b/runtime/v2/module.go index 8e8b7e0ce83..11cd7037fff 100644 --- a/runtime/v2/module.go +++ b/runtime/v2/module.go @@ -19,7 +19,6 @@ import ( "cosmossdk.io/core/event" "cosmossdk.io/core/header" "cosmossdk.io/core/registry" - "cosmossdk.io/core/server" "cosmossdk.io/core/store" "cosmossdk.io/core/transaction" "cosmossdk.io/depinject" @@ -27,7 +26,7 @@ import ( "cosmossdk.io/log" "cosmossdk.io/runtime/v2/services" "cosmossdk.io/server/v2/stf" - rootstore "cosmossdk.io/store/v2/root" + "cosmossdk.io/store/v2/root" ) var ( @@ -97,9 +96,9 @@ func init() { appconfig.Register(&runtimev2.Module{}, appconfig.Provide( ProvideAppBuilder[transaction.Tx], - ProvideEnvironment[transaction.Tx], ProvideModuleManager[transaction.Tx], - ProvideStoreBuilder, + ProvideEnvironment, + ProvideKVService, ), appconfig.Invoke(SetupAppBuilder), ) @@ -108,6 +107,7 @@ func init() { func ProvideAppBuilder[T transaction.Tx]( interfaceRegistrar registry.InterfaceRegistrar, amino registry.AminoRegistrar, + storeBuilder root.Builder, ) ( *AppBuilder[T], *stf.MsgRouterBuilder, @@ -127,15 +127,14 @@ func ProvideAppBuilder[T transaction.Tx]( msgRouterBuilder := stf.NewMsgRouterBuilder() app := &App[T]{ - storeKeys: nil, - interfaceRegistrar: interfaceRegistrar, - amino: amino, - msgRouterBuilder: msgRouterBuilder, - queryRouterBuilder: stf.NewMsgRouterBuilder(), // TODO dedicated query router - GRPCMethodsToMessageMap: map[string]func() proto.Message{}, - storeLoader: DefaultStoreLoader, + interfaceRegistrar: interfaceRegistrar, + amino: amino, + msgRouterBuilder: msgRouterBuilder, + queryRouterBuilder: stf.NewMsgRouterBuilder(), // TODO dedicated query router + QueryHandlers: map[string]appmodulev2.Handler{}, + storeLoader: DefaultStoreLoader, } - appBuilder := &AppBuilder[T]{app: app} + appBuilder := &AppBuilder[T]{app: app, storeBuilder: storeBuilder} return appBuilder, msgRouterBuilder, appModule[T]{app}, protoFiles, protoTypes } @@ -149,12 +148,7 @@ type AppInputs struct { InterfaceRegistrar registry.InterfaceRegistrar LegacyAmino registry.AminoRegistrar Logger log.Logger - // StoreBuilder is a builder for a store/v2 RootStore satisfying the Store interface - StoreBuilder *StoreBuilder - // StoreOptions are required as input for the StoreBuilder. If not provided, the default options are used. - StoreOptions *rootstore.Options `optional:"true"` - // DynamicConfig can be nil in client wiring, but is required in server wiring. - DynamicConfig server.DynamicConfig `optional:"true"` + StoreBuilder root.Builder } func SetupAppBuilder(inputs AppInputs) { @@ -164,24 +158,8 @@ func SetupAppBuilder(inputs AppInputs) { app.moduleManager = inputs.ModuleManager app.moduleManager.RegisterInterfaces(inputs.InterfaceRegistrar) app.moduleManager.RegisterLegacyAminoCodec(inputs.LegacyAmino) - - if inputs.DynamicConfig == nil { - return - } - storeOptions := rootstore.DefaultStoreOptions() - if inputs.StoreOptions != nil { - storeOptions = *inputs.StoreOptions - } - var err error - app.db, err = inputs.StoreBuilder.Build( - inputs.Logger, - app.storeKeys, - inputs.DynamicConfig, - storeOptions, - ) - if err != nil { - panic(err) - } + // STF requires some state to run + inputs.StoreBuilder.RegisterKey("stf") } func ProvideModuleManager[T transaction.Tx]( @@ -192,44 +170,47 @@ func ProvideModuleManager[T transaction.Tx]( return NewModuleManager[T](logger, config, modules) } -// ProvideEnvironment provides the environment for keeper modules, while maintaining backward compatibility and provide services directly as well. -func ProvideEnvironment[T transaction.Tx]( - logger log.Logger, +func ProvideKVService( config *runtimev2.Module, key depinject.ModuleKey, - appBuilder *AppBuilder[T], kvFactory store.KVStoreServiceFactory, - headerService header.Service, - eventService event.Service, -) ( - appmodulev2.Environment, - store.KVStoreService, - store.MemoryStoreService, -) { - var ( - kvService store.KVStoreService = failingStoreService{} - memKvService store.MemoryStoreService = failingStoreService{} - ) - + storeBuilder root.Builder, +) (store.KVStoreService, store.MemoryStoreService) { // skips modules that have no store - if !slices.Contains(config.SkipStoreKeys, key.Name()) { - var kvStoreKey string - storeKeyOverride := storeKeyOverride(config, key.Name()) - if storeKeyOverride != nil { - kvStoreKey = storeKeyOverride.KvStoreKey - } else { - kvStoreKey = key.Name() - } + if slices.Contains(config.SkipStoreKeys, key.Name()) { + return &failingStoreService{}, &failingStoreService{} + } + var kvStoreKey string + override := storeKeyOverride(config, key.Name()) + if override != nil { + kvStoreKey = override.KvStoreKey + } else { + kvStoreKey = key.Name() + } - registerStoreKey(appBuilder, kvStoreKey) - kvService = kvFactory([]byte(kvStoreKey)) + storeBuilder.RegisterKey(kvStoreKey) + return kvFactory([]byte(kvStoreKey)), stf.NewMemoryStoreService([]byte(fmt.Sprintf("memory:%s", kvStoreKey))) +} - memStoreKey := fmt.Sprintf("memory:%s", key.Name()) - registerStoreKey(appBuilder, memStoreKey) - memKvService = stf.NewMemoryStoreService([]byte(memStoreKey)) +func storeKeyOverride(config *runtimev2.Module, moduleName string) *runtimev2.StoreKeyConfig { + for _, cfg := range config.OverrideStoreKeys { + if cfg.ModuleName == moduleName { + return cfg + } } + return nil +} - env := appmodulev2.Environment{ +// ProvideEnvironment provides the environment for keeper modules, while maintaining backward compatibility and provide services directly as well. +func ProvideEnvironment( + logger log.Logger, + key depinject.ModuleKey, + kvService store.KVStoreService, + memKvService store.MemoryStoreService, + headerService header.Service, + eventService event.Service, +) appmodulev2.Environment { + return appmodulev2.Environment{ Logger: logger, BranchService: stf.BranchService{}, EventService: eventService, @@ -241,28 +222,13 @@ func ProvideEnvironment[T transaction.Tx]( KVStoreService: kvService, MemStoreService: memKvService, } - - return env, kvService, memKvService -} - -func registerStoreKey[T transaction.Tx](builder *AppBuilder[T], key string) { - builder.app.storeKeys = append(builder.app.storeKeys, key) -} - -func storeKeyOverride(config *runtimev2.Module, moduleName string) *runtimev2.StoreKeyConfig { - for _, cfg := range config.OverrideStoreKeys { - if cfg.ModuleName == moduleName { - return cfg - } - } - - return nil } // DefaultServiceBindings provides default services for the following service interfaces: // - store.KVStoreServiceFactory // - header.Service // - comet.Service +// - event.Service // // They are all required. For most use cases these default services bindings should be sufficient. // Power users (or tests) may wish to provide their own services bindings, in which case they must diff --git a/runtime/v2/services/genesis.go b/runtime/v2/services/genesis.go index 79ebd92852f..5d09e5c78f7 100644 --- a/runtime/v2/services/genesis.go +++ b/runtime/v2/services/genesis.go @@ -11,6 +11,7 @@ import ( var ( _ store.KVStoreService = (*GenesisKVStoreService)(nil) _ header.Service = (*GenesisHeaderService)(nil) + _ store.KVStore = (*readonlyKVStore)(nil) ) type genesisContextKeyType struct{} @@ -21,28 +22,41 @@ var genesisContextKey = genesisContextKeyType{} // it backs the store.KVStoreService and header.Service interface implementations // defined in this file. type genesisContext struct { - state store.WriterMap + state store.ReaderMap } // NewGenesisContext creates a new genesis context. -func NewGenesisContext(state store.WriterMap) genesisContext { +func NewGenesisContext(state store.ReaderMap) genesisContext { return genesisContext{ state: state, } } -// Run runs the provided function within the genesis context and returns an +// Mutate runs the provided function within the genesis context and returns an // updated store.WriterMap containing the state modifications made during InitGenesis. -func (g *genesisContext) Run( +func (g genesisContext) Mutate( ctx context.Context, fn func(ctx context.Context) error, ) (store.WriterMap, error) { + writerMap, ok := g.state.(store.WriterMap) + if !ok { + return nil, fmt.Errorf("mutate requires a store.WriterMap, got a %T", g.state) + } ctx = context.WithValue(ctx, genesisContextKey, g) err := fn(ctx) if err != nil { return nil, err } - return g.state, nil + return writerMap, nil +} + +// Read runs the provided function within the genesis context. +func (g genesisContext) Read( + ctx context.Context, + fn func(ctx context.Context) error, +) error { + ctx = context.WithValue(ctx, genesisContextKey, g) + return fn(ctx) } // GenesisKVStoreService is a store.KVStoreService implementation that is used during @@ -71,15 +85,24 @@ func (g *GenesisKVStoreService) OpenKVStore(ctx context.Context) store.KVStore { if v == nil { return g.executionService.OpenKVStore(ctx) } - genCtx, ok := v.(*genesisContext) + genCtx, ok := v.(genesisContext) if !ok { panic(fmt.Errorf("unexpected genesis context type: %T", v)) } - state, err := genCtx.state.GetWriter(g.actor) + writerMap, ok := genCtx.state.(store.WriterMap) + if ok { + state, err := writerMap.GetWriter(g.actor) + if err != nil { + panic(err) + } + return state + + } + state, err := genCtx.state.GetReader(g.actor) if err != nil { panic(err) } - return state + return readonlyKVStore{state} } // GenesisHeaderService is a header.Service implementation that is used during @@ -105,3 +128,15 @@ func NewGenesisHeaderService(executionService header.Service) *GenesisHeaderServ executionService: executionService, } } + +type readonlyKVStore struct { + store.Reader +} + +func (r readonlyKVStore) Set(key, value []byte) error { + panic("tried to call Set on a readonly store") +} + +func (r readonlyKVStore) Delete(key []byte) error { + panic("tried to call Delete on a readonly store") +} diff --git a/runtime/v2/store.go b/runtime/v2/store.go index 6c45dd5e72d..7f7b2135c80 100644 --- a/runtime/v2/store.go +++ b/runtime/v2/store.go @@ -3,18 +3,35 @@ package runtime import ( "errors" "fmt" - "path/filepath" + "sync" - "cosmossdk.io/core/server" "cosmossdk.io/core/store" - "cosmossdk.io/log" "cosmossdk.io/server/v2/stf" storev2 "cosmossdk.io/store/v2" - "cosmossdk.io/store/v2/db" "cosmossdk.io/store/v2/proof" "cosmossdk.io/store/v2/root" ) +var ( + storeBuilderSingleton root.Builder + storeBuilderSingletonOnce sync.Once +) + +// ProvideSingletonScopedStoreBuilder returns a store builder that is a singleton +// in the scope of the process lifetime. +func ProvideSingletonScopedStoreBuilder() root.Builder { + storeBuilderSingletonOnce.Do(func() { + storeBuilderSingleton = root.NewBuilder() + }) + return storeBuilderSingleton +} + +// ResetSingletonScopedStoreBuilder resets the singleton store builder. Applications +// should not ever need to call this, but it may be useful in tests. +func ResetSingletonScopedStoreBuilder() { + storeBuilderSingletonOnce = sync.Once{} +} + // NewKVStoreService creates a new KVStoreService. // This wrapper is kept for backwards compatibility. // When migrating from runtime to runtime/v2, use runtimev2.NewKVStoreService(storeKey.Name()) instead of runtime.NewKVStoreService(storeKey). @@ -64,58 +81,6 @@ type Store interface { LastCommitID() (proof.CommitID, error) } -// StoreBuilder is a builder for a store/v2 RootStore satisfying the Store interface. -type StoreBuilder struct { - store Store -} - -// Build creates a new store/v2 RootStore. -func (sb *StoreBuilder) Build( - logger log.Logger, - storeKeys []string, - config server.DynamicConfig, - options root.Options, -) (Store, error) { - if sb.store != nil { - return sb.store, nil - } - home := config.GetString(flagHome) - scRawDb, err := db.NewDB( - db.DBType(config.GetString("store.app-db-backend")), - "application", - filepath.Join(home, "data"), - nil, - ) - if err != nil { - return nil, fmt.Errorf("failed to create SCRawDB: %w", err) - } - - factoryOptions := &root.FactoryOptions{ - Logger: logger, - RootDir: home, - Options: options, - // STF needs to store a bit of state - StoreKeys: append(storeKeys, "stf"), - SCRawDB: scRawDb, - } - - rs, err := root.CreateRootStore(factoryOptions) - if err != nil { - return nil, fmt.Errorf("failed to create root store: %w", err) - } - sb.store = rs - return sb.store, nil -} - -// Get returns the Store. Build must be called before calling Get or the result will be nil. -func (sb *StoreBuilder) Get() Store { - return sb.store -} - -func ProvideStoreBuilder() *StoreBuilder { - return &StoreBuilder{} -} - // StoreLoader allows for custom loading of the store, this is useful when upgrading the store from a previous version type StoreLoader func(store Store) error diff --git a/server/cmt_cmds.go b/server/cmt_cmds.go index e4c84364dd5..d511cc10569 100644 --- a/server/cmt_cmds.go +++ b/server/cmt_cmds.go @@ -368,7 +368,7 @@ func BootstrapStateCmd[T types.Application](appCreator types.AppCreator[T]) *cob Use: "bootstrap-state", Short: "Bootstrap CometBFT state at an arbitrary block height using a light client", Args: cobra.NoArgs, - Example: fmt.Sprintf("%s bootstrap-state --height 1000000", version.AppName), + Example: "bootstrap-state --height 1000000", RunE: func(cmd *cobra.Command, args []string) error { serverCtx := GetServerContextFromCmd(cmd) logger := log.NewLogger(cmd.OutOrStdout()) diff --git a/server/types/app.go b/server/types/app.go index ca0a55ca1a0..c1a67b2ceec 100644 --- a/server/types/app.go +++ b/server/types/app.go @@ -6,7 +6,6 @@ import ( cmtproto "github.com/cometbft/cometbft/api/cometbft/types/v1" cmtcrypto "github.com/cometbft/cometbft/crypto" - cmttypes "github.com/cometbft/cometbft/types" "github.com/cosmos/gogoproto/grpc" "cosmossdk.io/core/server" @@ -18,6 +17,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/server/api" "github.com/cosmos/cosmos-sdk/server/config" + sdk "github.com/cosmos/cosmos-sdk/types" ) type ( @@ -76,7 +76,7 @@ type ( // AppState is the application state as JSON. AppState json.RawMessage // Validators is the exported validator set. - Validators []cmttypes.GenesisValidator + Validators []sdk.GenesisValidator // Height is the app's latest block height. Height int64 // ConsensusParams are the exported consensus params for ABCI. diff --git a/server/v2/api/grpc/server.go b/server/v2/api/grpc/server.go index 170343fd512..7b70363f6f1 100644 --- a/server/v2/api/grpc/server.go +++ b/server/v2/api/grpc/server.go @@ -9,14 +9,18 @@ import ( "net" "slices" "strconv" + "strings" + "sync" - "github.com/cosmos/gogoproto/proto" + gogoproto "github.com/cosmos/gogoproto/proto" "github.com/spf13/pflag" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" + "google.golang.org/protobuf/reflect/protoreflect" + appmodulev2 "cosmossdk.io/core/appmodule/v2" "cosmossdk.io/core/transaction" "cosmossdk.io/log" serverv2 "cosmossdk.io/server/v2" @@ -53,7 +57,7 @@ func (s *Server[T]) Init(appI serverv2.AppI[T], cfg map[string]any, logger log.L return fmt.Errorf("failed to unmarshal config: %w", err) } } - methodsMap := appI.GetGPRCMethodsToMessageMap() + methodsMap := appI.GetQueryHandlers() grpcSrv := grpc.NewServer( grpc.ForceServerCodec(newProtoCodec(appI.InterfaceRegistry()).GRPCCodec()), @@ -80,21 +84,42 @@ func (s *Server[T]) StartCmdFlags() *pflag.FlagSet { return flags } -func makeUnknownServiceHandler(messageMap map[string]func() proto.Message, querier interface { - Query(ctx context.Context, version uint64, msg proto.Message) (proto.Message, error) +func makeUnknownServiceHandler(handlers map[string]appmodulev2.Handler, querier interface { + Query(ctx context.Context, version uint64, msg gogoproto.Message) (gogoproto.Message, error) }, ) grpc.StreamHandler { + getRegistry := sync.OnceValues(gogoproto.MergedRegistry) + return func(srv any, stream grpc.ServerStream) error { method, ok := grpc.MethodFromServerStream(stream) if !ok { return status.Error(codes.InvalidArgument, "unable to get method") } - makeMsg, exists := messageMap[method] + // if this fails we cannot serve queries anymore... + registry, err := getRegistry() + if err != nil { + return fmt.Errorf("failed to get registry: %w", err) + } + + method = strings.TrimPrefix(method, "/") + fullName := protoreflect.FullName(strings.ReplaceAll(method, "/", ".")) + // get descriptor from the invoke method + desc, err := registry.FindDescriptorByName(fullName) + if err != nil { + return fmt.Errorf("failed to find descriptor %s: %w", method, err) + } + md, ok := desc.(protoreflect.MethodDescriptor) + if !ok { + return fmt.Errorf("%s is not a method", method) + } + // find handler + handler, exists := handlers[string(md.Input().FullName())] if !exists { return status.Errorf(codes.Unimplemented, "gRPC method %s is not handled", method) } + for { - req := makeMsg() + req := handler.MakeMsg() err := stream.RecvMsg(req) if err != nil { if errors.Is(err, io.EOF) { diff --git a/server/v2/cometbft/abci.go b/server/v2/cometbft/abci.go index fd24f2cbe09..cbcf387168a 100644 --- a/server/v2/cometbft/abci.go +++ b/server/v2/cometbft/abci.go @@ -5,13 +5,18 @@ import ( "crypto/sha256" "errors" "fmt" + "strings" + "sync" "sync/atomic" abci "github.com/cometbft/cometbft/abci/types" abciproto "github.com/cometbft/cometbft/api/cometbft/abci/v1" gogoproto "github.com/cosmos/gogoproto/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/protoregistry" "cosmossdk.io/collections" + appmodulev2 "cosmossdk.io/core/appmodule/v2" "cosmossdk.io/core/comet" corecontext "cosmossdk.io/core/context" "cosmossdk.io/core/event" @@ -59,11 +64,13 @@ type Consensus[T transaction.Tx] struct { processProposalHandler handlers.ProcessHandler[T] verifyVoteExt handlers.VerifyVoteExtensionhandler extendVote handlers.ExtendVoteHandler + checkTxHandler handlers.CheckTxHandler[T] addrPeerFilter types.PeerFilter // filter peers by address and port idPeerFilter types.PeerFilter // filter peers by node ID - grpcMethodsMap map[string]func() transaction.Msg // maps gRPC method to message creator func + queryHandlersMap map[string]appmodulev2.Handler + getProtoRegistry func() (*protoregistry.Files, error) } func NewConsensus[T transaction.Tx]( @@ -72,7 +79,7 @@ func NewConsensus[T transaction.Tx]( app *appmanager.AppManager[T], mp mempool.Mempool[T], indexedEvents map[string]struct{}, - gRPCMethodsMap map[string]func() transaction.Msg, + queryHandlersMap map[string]appmodulev2.Handler, store types.Store, cfg Config, txCodec transaction.Codec[T], @@ -81,7 +88,6 @@ func NewConsensus[T transaction.Tx]( return &Consensus[T]{ appName: appName, version: getCometBFTServerVersion(), - grpcMethodsMap: gRPCMethodsMap, app: app, cfg: cfg, store: store, @@ -98,6 +104,8 @@ func NewConsensus[T transaction.Tx]( chainID: chainId, indexedEvents: indexedEvents, initialHeight: 0, + queryHandlersMap: queryHandlersMap, + getProtoRegistry: sync.OnceValues(func() (*protoregistry.Files, error) { return gogoproto.MergedRegistry() }), } } @@ -129,31 +137,35 @@ func (c *Consensus[T]) CheckTx(ctx context.Context, req *abciproto.CheckTxReques return nil, err } - resp, err := c.app.ValidateTx(ctx, decodedTx) - // we do not want to return a cometbft error, but a check tx response with the error - if err != nil && !errors.Is(err, resp.Error) { - return nil, err - } + if c.checkTxHandler == nil { + resp, err := c.app.ValidateTx(ctx, decodedTx) + // we do not want to return a cometbft error, but a check tx response with the error + if err != nil && !errors.Is(err, resp.Error) { + return nil, err + } - events, err := intoABCIEvents(resp.Events, c.indexedEvents) - if err != nil { - return nil, err - } + events, err := intoABCIEvents(resp.Events, c.indexedEvents) + if err != nil { + return nil, err + } - cometResp := &abciproto.CheckTxResponse{ - Code: 0, - GasWanted: uint64ToInt64(resp.GasWanted), - GasUsed: uint64ToInt64(resp.GasUsed), - Events: events, - } - if resp.Error != nil { - space, code, log := errorsmod.ABCIInfo(resp.Error, c.cfg.AppTomlConfig.Trace) - cometResp.Code = code - cometResp.Codespace = space - cometResp.Log = log + cometResp := &abciproto.CheckTxResponse{ + Code: 0, + GasWanted: uint64ToInt64(resp.GasWanted), + GasUsed: uint64ToInt64(resp.GasUsed), + Events: events, + } + if resp.Error != nil { + space, code, log := errorsmod.ABCIInfo(resp.Error, c.cfg.AppTomlConfig.Trace) + cometResp.Code = code + cometResp.Codespace = space + cometResp.Log = log + } + + return cometResp, nil } - return cometResp, nil + return c.checkTxHandler(c.app.ValidateTx) } // Info implements types.Application. @@ -198,23 +210,9 @@ func (c *Consensus[T]) Info(ctx context.Context, _ *abciproto.InfoRequest) (*abc // Query implements types.Application. // It is called by cometbft to query application state. func (c *Consensus[T]) Query(ctx context.Context, req *abciproto.QueryRequest) (resp *abciproto.QueryResponse, err error) { - // check if it's a gRPC method - makeGRPCRequest, isGRPC := c.grpcMethodsMap[req.Path] + resp, isGRPC, err := c.maybeRunGRPCQuery(ctx, req) if isGRPC { - protoRequest := makeGRPCRequest() - err = gogoproto.Unmarshal(req.Data, protoRequest) // TODO: use codec - if err != nil { - return nil, fmt.Errorf("unable to decode gRPC request with path %s from ABCI.Query: %w", req.Path, err) - } - res, err := c.app.Query(ctx, uint64(req.Height), protoRequest) - if err != nil { - resp := QueryResult(err, c.cfg.AppTomlConfig.Trace) - resp.Height = req.Height - return resp, err - - } - - return queryResponse(res, req.Height) + return resp, err } // this error most probably means that we can't handle it with a proto message, so @@ -245,6 +243,50 @@ func (c *Consensus[T]) Query(ctx context.Context, req *abciproto.QueryRequest) ( return resp, nil } +func (c *Consensus[T]) maybeRunGRPCQuery(ctx context.Context, req *abci.QueryRequest) (resp *abciproto.QueryResponse, isGRPC bool, err error) { + // if this fails then we cannot serve queries anymore + registry, err := c.getProtoRegistry() + if err != nil { + return nil, false, err + } + + path := strings.TrimPrefix(req.Path, "/") + pathFullName := protoreflect.FullName(strings.ReplaceAll(path, "/", ".")) + + // in order to check if it's a gRPC query we ensure that there's a descriptor + // for the path, if such descriptor exists, and it is a method descriptor + // then we assume this is a gRPC query. + desc, err := registry.FindDescriptorByName(pathFullName) + if err != nil { + return nil, false, err + } + + md, isGRPC := desc.(protoreflect.MethodDescriptor) + if !isGRPC { + return nil, false, nil + } + + handler, found := c.queryHandlersMap[string(md.Input().FullName())] + if !found { + return nil, true, fmt.Errorf("no query handler found for %s", req.Path) + } + protoRequest := handler.MakeMsg() + err = gogoproto.Unmarshal(req.Data, protoRequest) // TODO: use codec + if err != nil { + return nil, true, fmt.Errorf("unable to decode gRPC request with path %s from ABCI.Query: %w", req.Path, err) + } + res, err := c.app.Query(ctx, uint64(req.Height), protoRequest) + if err != nil { + resp := QueryResult(err, c.cfg.AppTomlConfig.Trace) + resp.Height = req.Height + return resp, true, err + + } + + resp, err = queryResponse(res, req.Height) + return resp, isGRPC, err +} + // InitChain implements types.Application. func (c *Consensus[T]) InitChain(ctx context.Context, req *abciproto.InitChainRequest) (*abciproto.InitChainResponse, error) { c.logger.Info("InitChain", "initialHeight", req.InitialHeight, "chainID", req.ChainId) diff --git a/server/v2/cometbft/go.mod b/server/v2/cometbft/go.mod index f807f2c2d07..c95f22af817 100644 --- a/server/v2/cometbft/go.mod +++ b/server/v2/cometbft/go.mod @@ -36,6 +36,7 @@ require ( github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.9.0 + google.golang.org/protobuf v1.35.1 sigs.k8s.io/yaml v1.4.0 ) @@ -166,20 +167,19 @@ require ( go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect google.golang.org/grpc v1.67.1 // indirect - google.golang.org/protobuf v1.34.2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.1 // indirect diff --git a/server/v2/cometbft/go.sum b/server/v2/cometbft/go.sum index ea29392600d..4343a0df72c 100644 --- a/server/v2/cometbft/go.sum +++ b/server/v2/cometbft/go.sum @@ -516,8 +516,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -596,19 +596,19 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -667,8 +667,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/server/v2/cometbft/handlers/handlers.go b/server/v2/cometbft/handlers/handlers.go index 9008490f6a4..015594f469f 100644 --- a/server/v2/cometbft/handlers/handlers.go +++ b/server/v2/cometbft/handlers/handlers.go @@ -5,6 +5,7 @@ import ( abci "github.com/cometbft/cometbft/api/cometbft/abci/v1" + "cosmossdk.io/core/server" "cosmossdk.io/core/store" "cosmossdk.io/core/transaction" ) @@ -27,4 +28,7 @@ type ( // It takes a context, a store reader map, and a request to extend a vote. // It returns a response to extend the vote and an error if any. ExtendVoteHandler func(context.Context, store.ReaderMap, *abci.ExtendVoteRequest) (*abci.ExtendVoteResponse, error) + + // CheckTxHandler is a function type that handles the execution of a transaction. + CheckTxHandler[T transaction.Tx] func(func(ctx context.Context, tx T) (server.TxResult, error)) (*abci.CheckTxResponse, error) ) diff --git a/server/v2/cometbft/options.go b/server/v2/cometbft/options.go index 0f5091da70a..d5aa4872fff 100644 --- a/server/v2/cometbft/options.go +++ b/server/v2/cometbft/options.go @@ -18,6 +18,7 @@ type keyGenF = func() (cmtcrypto.PrivKey, error) type ServerOptions[T transaction.Tx] struct { PrepareProposalHandler handlers.PrepareHandler[T] ProcessProposalHandler handlers.ProcessHandler[T] + CheckTxHandler handlers.CheckTxHandler[T] VerifyVoteExtensionHandler handlers.VerifyVoteExtensionhandler ExtendVoteHandler handlers.ExtendVoteHandler KeygenF keyGenF @@ -35,6 +36,7 @@ func DefaultServerOptions[T transaction.Tx]() ServerOptions[T] { return ServerOptions[T]{ PrepareProposalHandler: handlers.NoOpPrepareProposal[T](), ProcessProposalHandler: handlers.NoOpProcessProposal[T](), + CheckTxHandler: nil, VerifyVoteExtensionHandler: handlers.NoOpVerifyVoteExtensionHandler(), ExtendVoteHandler: handlers.NoOpExtendVote(), Mempool: func(cfg map[string]any) mempool.Mempool[T] { return mempool.NoOpMempool[T]{} }, diff --git a/server/v2/cometbft/server.go b/server/v2/cometbft/server.go index 21b6d59ddc7..7fa1a94d8ee 100644 --- a/server/v2/cometbft/server.go +++ b/server/v2/cometbft/server.go @@ -8,6 +8,9 @@ import ( "os" "path/filepath" + "cosmossdk.io/server/v2/store" + "cosmossdk.io/store/v2/root" + abciserver "github.com/cometbft/cometbft/abci/server" cmtcmd "github.com/cometbft/cometbft/cmd/cometbft/commands" cmtcfg "github.com/cometbft/cometbft/config" @@ -23,7 +26,6 @@ import ( serverv2 "cosmossdk.io/server/v2" cometlog "cosmossdk.io/server/v2/cometbft/log" "cosmossdk.io/server/v2/cometbft/mempool" - "cosmossdk.io/server/v2/cometbft/types" "cosmossdk.io/store/v2/snapshots" genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" @@ -43,14 +45,21 @@ type CometBFTServer[T transaction.Tx] struct { initTxCodec transaction.Codec[T] logger log.Logger + storeBuilder root.Builder serverOptions ServerOptions[T] config Config cfgOptions []CfgOption } -func New[T transaction.Tx](txCodec transaction.Codec[T], serverOptions ServerOptions[T], cfgOptions ...CfgOption) *CometBFTServer[T] { +func New[T transaction.Tx]( + txCodec transaction.Codec[T], + storeBuilder root.Builder, + serverOptions ServerOptions[T], + cfgOptions ...CfgOption, +) *CometBFTServer[T] { return &CometBFTServer[T]{ initTxCodec: txCodec, + storeBuilder: storeBuilder, serverOptions: serverOptions, cfgOptions: cfgOptions, } @@ -97,29 +106,38 @@ func (s *CometBFTServer[T]) Init(appI serverv2.AppI[T], cfg map[string]any, logg indexEvents[e] = struct{}{} } + storeCfg, err := store.UnmarshalConfig(cfg) + if err != nil { + return err + } + rs, err := s.storeBuilder.Build(logger, storeCfg) + if err != nil { + return err + } + s.logger = logger.With(log.ModuleKey, s.Name()) - store := appI.GetStore().(types.Store) consensus := NewConsensus( s.logger, appI.Name(), appI.GetAppManager(), s.serverOptions.Mempool(cfg), indexEvents, - appI.GetGPRCMethodsToMessageMap(), - store, + appI.GetQueryHandlers(), + rs, s.config, s.initTxCodec, chainID, ) consensus.prepareProposalHandler = s.serverOptions.PrepareProposalHandler consensus.processProposalHandler = s.serverOptions.ProcessProposalHandler + consensus.checkTxHandler = s.serverOptions.CheckTxHandler consensus.verifyVoteExt = s.serverOptions.VerifyVoteExtensionHandler consensus.extendVote = s.serverOptions.ExtendVoteHandler consensus.addrPeerFilter = s.serverOptions.AddrPeerFilter consensus.idPeerFilter = s.serverOptions.IdPeerFilter - ss := store.GetStateStorage().(snapshots.StorageSnapshotter) - sc := store.GetStateCommitment().(snapshots.CommitSnapshotter) + ss := rs.GetStateStorage().(snapshots.StorageSnapshotter) + sc := rs.GetStateCommitment().(snapshots.CommitSnapshotter) snapshotStore, err := GetSnapshotStore(s.config.ConfigTomlConfig.RootDir) if err != nil { diff --git a/server/v2/cometbft/types/store.go b/server/v2/cometbft/types/store.go index ff06163af5a..3e603ff18ce 100644 --- a/server/v2/cometbft/types/store.go +++ b/server/v2/cometbft/types/store.go @@ -7,6 +7,8 @@ import ( ) type Store interface { + storev2.Backend + // GetLatestVersion returns the latest version that consensus has been made on GetLatestVersion() (uint64, error) // StateLatest returns a readonly view over the latest @@ -30,10 +32,4 @@ type Store interface { // LastCommitID returns a CommitID pertaining to the last commitment. LastCommitID() (proof.CommitID, error) - - // GetStateStorage returns the SS backend. - GetStateStorage() storev2.VersionedDatabase - - // GetStateCommitment returns the SC backend. - GetStateCommitment() storev2.Committer } diff --git a/server/v2/commands.go b/server/v2/commands.go index da8e0ec6e53..0357fe180d9 100644 --- a/server/v2/commands.go +++ b/server/v2/commands.go @@ -33,7 +33,7 @@ func Execute(rootCmd *cobra.Command, envPrefix, defaultHome string) error { } // AddCommands add the server commands to the root command -// It configure the config handling and the logger handling +// It configures the config handling and the logger handling func AddCommands[T transaction.Tx]( rootCmd *cobra.Command, newApp AppCreator[T], diff --git a/server/v2/config_test.go b/server/v2/config_test.go index f69e2ed5676..3ddd7893a00 100644 --- a/server/v2/config_test.go +++ b/server/v2/config_test.go @@ -10,6 +10,7 @@ import ( serverv2 "cosmossdk.io/server/v2" grpc "cosmossdk.io/server/v2/api/grpc" store "cosmossdk.io/server/v2/store" + "cosmossdk.io/store/v2/root" ) func TestReadConfig(t *testing.T) { @@ -21,7 +22,7 @@ func TestReadConfig(t *testing.T) { require.NoError(t, err) require.Equal(t, v.GetString(grpc.FlagAddress), grpc.DefaultConfig().Address) - require.Equal(t, v.GetString(store.FlagAppDBBackend), store.DefaultConfig().AppDBBackend) + require.Equal(t, v.GetString(store.FlagAppDBBackend), root.DefaultConfig().AppDBBackend) } func TestUnmarshalSubConfig(t *testing.T) { @@ -40,8 +41,8 @@ func TestUnmarshalSubConfig(t *testing.T) { require.True(t, grpc.DefaultConfig().Enable) require.False(t, grpcConfig.Enable) - storeConfig := store.Config{} + storeConfig := root.Config{} err = serverv2.UnmarshalSubConfig(cfg, "store", &storeConfig) require.NoError(t, err) - require.Equal(t, *store.DefaultConfig(), storeConfig) + require.Equal(t, *root.DefaultConfig(), storeConfig) } diff --git a/server/v2/go.mod b/server/v2/go.mod index 005ea3372e5..1fca8cbb441 100644 --- a/server/v2/go.mod +++ b/server/v2/go.mod @@ -13,7 +13,7 @@ replace ( require ( cosmossdk.io/api v0.7.6 - cosmossdk.io/core v1.0.0-alpha.3 + cosmossdk.io/core v1.0.0-alpha.4 cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29 cosmossdk.io/log v1.4.1 cosmossdk.io/server/v2/appmanager v0.0.0-00010101000000-000000000000 @@ -37,7 +37,7 @@ require ( github.com/stretchr/testify v1.9.0 golang.org/x/sync v0.8.0 google.golang.org/grpc v1.67.1 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 ) require ( @@ -97,12 +97,12 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tidwall/btree v1.7.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect diff --git a/server/v2/go.sum b/server/v2/go.sum index 1a556c25791..26e67ab39a4 100644 --- a/server/v2/go.sum +++ b/server/v2/go.sum @@ -1,7 +1,7 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cosmossdk.io/core v1.0.0-alpha.3 h1:pnxaYAas7llXgVz1lM7X6De74nWrhNKnB3yMKe4OUUA= -cosmossdk.io/core v1.0.0-alpha.3/go.mod h1:3u9cWq1FAVtiiCrDPpo4LhR+9V6k/ycSG4/Y/tREWCY= +cosmossdk.io/core v1.0.0-alpha.4 h1:9iuroT9ejDYETCsGkzkvs/wAY/5UFl7nCIINFRxyMJY= +cosmossdk.io/core v1.0.0-alpha.4/go.mod h1:3u9cWq1FAVtiiCrDPpo4LhR+9V6k/ycSG4/Y/tREWCY= cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29 h1:NxxUo0GMJUbIuVg0R70e3cbn9eFTEuMr7ev1AFvypdY= cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29/go.mod h1:8s2tPeJtSiQuoyPmr2Ag7meikonISO4Fv4MoO8+ORrs= cosmossdk.io/errors/v2 v2.0.0-20240731132947-df72853b3ca5 h1:IQNdY2kB+k+1OM2DvqFG1+UgeU1JzZrWtwuWzI3ZfwA= @@ -336,8 +336,8 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -411,8 +411,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -420,8 +420,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -475,8 +475,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/server/v2/server.go b/server/v2/server.go index 7ee91e64998..a628fbdf100 100644 --- a/server/v2/server.go +++ b/server/v2/server.go @@ -61,6 +61,7 @@ const ( var _ ServerComponent[transaction.Tx] = (*Server[transaction.Tx])(nil) +// Server is the top-level server component which contains all other server components. type Server[T transaction.Tx] struct { logger log.Logger components []ServerComponent[T] diff --git a/server/v2/server_test.go b/server/v2/server_test.go index e84bcd59889..c216824fb65 100644 --- a/server/v2/server_test.go +++ b/server/v2/server_test.go @@ -11,6 +11,7 @@ import ( "github.com/spf13/viper" "github.com/stretchr/testify/require" + appmodulev2 "cosmossdk.io/core/appmodule/v2" coreserver "cosmossdk.io/core/server" "cosmossdk.io/core/transaction" "cosmossdk.io/log" @@ -18,6 +19,8 @@ import ( grpc "cosmossdk.io/server/v2/api/grpc" "cosmossdk.io/server/v2/appmanager" "cosmossdk.io/server/v2/store" + storev2 "cosmossdk.io/store/v2" + "cosmossdk.io/store/v2/root" ) type mockInterfaceRegistry struct{} @@ -35,8 +38,8 @@ type mockApp[T transaction.Tx] struct { serverv2.AppI[T] } -func (*mockApp[T]) GetGPRCMethodsToMessageMap() map[string]func() gogoproto.Message { - return map[string]func() gogoproto.Message{} +func (*mockApp[T]) GetQueryHandlers() map[string]appmodulev2.Handler { + return map[string]appmodulev2.Handler{} } func (*mockApp[T]) GetAppManager() *appmanager.AppManager[T] { @@ -47,6 +50,18 @@ func (*mockApp[T]) InterfaceRegistry() coreserver.InterfaceRegistry { return &mockInterfaceRegistry{} } +var _ root.Builder = &mockStoreBuilder{} + +type mockStoreBuilder struct{} + +func (m mockStoreBuilder) Build(logger log.Logger, config *root.Config) (storev2.RootStore, error) { + return nil, nil +} + +func (m mockStoreBuilder) RegisterKey(string) {} + +func (m mockStoreBuilder) Get() storev2.RootStore { return nil } + func TestServer(t *testing.T) { currentDir, err := os.Getwd() require.NoError(t, err) @@ -63,7 +78,7 @@ func TestServer(t *testing.T) { err = grpcServer.Init(&mockApp[transaction.Tx]{}, cfg, logger) require.NoError(t, err) - storeServer := store.New[transaction.Tx](nil /* nil appCreator as not using CLI commands */) + storeServer := store.New[transaction.Tx](&mockStoreBuilder{}) err = storeServer.Init(&mockApp[transaction.Tx]{}, cfg, logger) require.NoError(t, err) diff --git a/server/v2/stf/branch/mergeiter_test.go b/server/v2/stf/branch/mergeiter_test.go new file mode 100644 index 00000000000..1a9e13bbe65 --- /dev/null +++ b/server/v2/stf/branch/mergeiter_test.go @@ -0,0 +1,130 @@ +package branch + +import ( + "reflect" + "testing" + + corestore "cosmossdk.io/core/store" +) + +func TestMergedIterator_Next(t *testing.T) { + specs := map[string]struct { + setup func() corestore.Iterator + exp [][2]string + }{ + "both iterators are empty": { + setup: func() corestore.Iterator { + parent := newMemState() + cache := newMemState() + return mergeIterators(must(parent.Iterator(nil, nil)), must(cache.Iterator(nil, nil)), true) + }, + }, + "parent iterator has one item, cache is empty": { + setup: func() corestore.Iterator { + parent := newMemState() + if err := parent.Set([]byte("k1"), []byte("1")); err != nil { + t.Fatal(err) + } + cache := newMemState() + return mergeIterators(must(parent.Iterator(nil, nil)), must(cache.Iterator(nil, nil)), true) + }, + exp: [][2]string{{"k1", "1"}}, + }, + "cache has one item, parent is empty": { + setup: func() corestore.Iterator { + parent := newMemState() + cache := newMemState() + if err := cache.Set([]byte("k1"), []byte("1")); err != nil { + t.Fatal(err) + } + return mergeIterators(must(parent.Iterator(nil, nil)), must(cache.Iterator(nil, nil)), true) + }, + exp: [][2]string{{"k1", "1"}}, + }, + "both iterators have same key, cache preferred": { + setup: func() corestore.Iterator { + parent := newMemState() + if err := parent.Set([]byte("k1"), []byte("parent-val")); err != nil { + t.Fatal(err) + } + cache := newMemState() + if err := cache.Set([]byte("k1"), []byte("cache-val")); err != nil { + t.Fatal(err) + } + return mergeIterators(must(parent.Iterator(nil, nil)), must(cache.Iterator(nil, nil)), true) + }, + exp: [][2]string{{"k1", "cache-val"}}, + }, + "both iterators have same key, but cache value is nil": { + setup: func() corestore.Iterator { + parent := newMemState() + if err := parent.Set([]byte("k1"), []byte("1")); err != nil { + t.Fatal(err) + } + cache := newMemState() + if err := cache.Set([]byte("k1"), nil); err != nil { + t.Fatal(err) + } + return mergeIterators(must(parent.Iterator(nil, nil)), must(cache.Iterator(nil, nil)), true) + }, + }, + "parent and cache are ascending": { + setup: func() corestore.Iterator { + parent := newMemState() + if err := parent.Set([]byte("k2"), []byte("v2")); err != nil { + t.Fatal(err) + } + if err := parent.Set([]byte("k3"), []byte("v3")); err != nil { + t.Fatal(err) + } + cache := newMemState() + if err := cache.Set([]byte("k1"), []byte("v1")); err != nil { + t.Fatal(err) + } + if err := cache.Set([]byte("k4"), []byte("v4")); err != nil { + t.Fatal(err) + } + return mergeIterators(must(parent.Iterator(nil, nil)), must(cache.Iterator(nil, nil)), true) + }, + exp: [][2]string{{"k1", "v1"}, {"k2", "v2"}, {"k3", "v3"}, {"k4", "v4"}}, + }, + "parent and cache are descending": { + setup: func() corestore.Iterator { + parent := newMemState() + if err := parent.Set([]byte("k3"), []byte("v3")); err != nil { + t.Fatal(err) + } + if err := parent.Set([]byte("k2"), []byte("v2")); err != nil { + t.Fatal(err) + } + cache := newMemState() + if err := cache.Set([]byte("k4"), []byte("v4")); err != nil { + t.Fatal(err) + } + if err := cache.Set([]byte("k1"), []byte("v1")); err != nil { + t.Fatal(err) + } + return mergeIterators(must(parent.ReverseIterator(nil, nil)), must(cache.ReverseIterator(nil, nil)), false) + }, + exp: [][2]string{{"k4", "v4"}, {"k3", "v3"}, {"k2", "v2"}, {"k1", "v1"}}, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + var got [][2]string + for iter := spec.setup(); iter.Valid(); iter.Next() { + got = append(got, [2]string{string(iter.Key()), string(iter.Value())}) + } + if !reflect.DeepEqual(spec.exp, got) { + t.Errorf("expected: %#v, got: %#v", spec.exp, got) + } + }) + } +} + +func must[T any](r T, err error) T { + if err != nil { + panic(err) + } + return r +} diff --git a/server/v2/stf/go.mod b/server/v2/stf/go.mod index 06f5ca471a1..7f55feb3f3f 100644 --- a/server/v2/stf/go.mod +++ b/server/v2/stf/go.mod @@ -11,5 +11,5 @@ require ( require ( github.com/google/go-cmp v0.6.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/protobuf v1.35.1 // indirect ) diff --git a/server/v2/stf/go.sum b/server/v2/stf/go.sum index 95ab31efba8..29d26cc868d 100644 --- a/server/v2/stf/go.sum +++ b/server/v2/stf/go.sum @@ -10,5 +10,5 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI= github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= diff --git a/server/v2/store/config.go b/server/v2/store/config.go deleted file mode 100644 index 457bfa9383c..00000000000 --- a/server/v2/store/config.go +++ /dev/null @@ -1,17 +0,0 @@ -package store - -import ( - "cosmossdk.io/store/v2/root" -) - -func DefaultConfig() *Config { - return &Config{ - AppDBBackend: "goleveldb", - Options: root.DefaultStoreOptions(), - } -} - -type Config struct { - AppDBBackend string `mapstructure:"app-db-backend" toml:"app-db-backend" comment:"The type of database for application and snapshots databases."` - Options root.Options `mapstructure:"options" toml:"options"` -} diff --git a/server/v2/store/server.go b/server/v2/store/server.go index c50a53dc6e2..a793a1912db 100644 --- a/server/v2/store/server.go +++ b/server/v2/store/server.go @@ -9,6 +9,8 @@ import ( "cosmossdk.io/core/transaction" "cosmossdk.io/log" serverv2 "cosmossdk.io/server/v2" + storev2 "cosmossdk.io/store/v2" + "cosmossdk.io/store/v2/root" ) var ( @@ -21,24 +23,26 @@ const ServerName = "store" // Server manages store config and contains prune & snapshot commands type Server[T transaction.Tx] struct { - config *Config - // saving appCreator for only RestoreSnapshotCmd - appCreator serverv2.AppCreator[T] + config *root.Config + builder root.Builder + backend storev2.Backend } -func New[T transaction.Tx](appCreator serverv2.AppCreator[T]) *Server[T] { - return &Server[T]{appCreator: appCreator} +func New[T transaction.Tx](builder root.Builder) *Server[T] { + return &Server[T]{builder: builder} } -func (s *Server[T]) Init(appI serverv2.AppI[T], cfg map[string]any, logger log.Logger) error { - serverCfg := s.Config().(*Config) - if len(cfg) > 0 { - if err := serverv2.UnmarshalSubConfig(cfg, s.Name(), &serverCfg); err != nil { - return fmt.Errorf("failed to unmarshal config: %w", err) - } +func (s *Server[T]) Init(_ serverv2.AppI[T], cfg map[string]any, log log.Logger) error { + var err error + s.config, err = UnmarshalConfig(cfg) + if err != nil { + return fmt.Errorf("failed to unmarshal config: %w", err) + } + s.backend, err = s.builder.Build(log, s.config) + if err != nil { + return fmt.Errorf("failed to create store backend: %w", err) } - s.config = serverCfg return nil } @@ -46,11 +50,11 @@ func (s *Server[T]) Name() string { return ServerName } -func (s *Server[T]) Start(ctx context.Context) error { +func (s *Server[T]) Start(context.Context) error { return nil } -func (s *Server[T]) Stop(ctx context.Context) error { +func (s *Server[T]) Stop(context.Context) error { return nil } @@ -63,15 +67,34 @@ func (s *Server[T]) CLICommands() serverv2.CLIConfig { s.ListSnapshotsCmd(), s.DumpArchiveCmd(), s.LoadArchiveCmd(), - s.RestoreSnapshotCmd(s.appCreator), + s.RestoreSnapshotCmd(s.backend), }, } } func (s *Server[T]) Config() any { if s.config == nil || s.config.AppDBBackend == "" { - return DefaultConfig() + return root.DefaultConfig() } return s.config } + +// UnmarshalConfig unmarshals the store config from the given map. +// If the config is not found in the map, the default config is returned. +// If the home directory is found in the map, it sets the home directory in the config. +// An empty home directory *is* permitted at this stage, but attempting to build +// the store with an empty home directory will fail. +func UnmarshalConfig(cfg map[string]any) (*root.Config, error) { + config := &root.Config{ + Options: root.DefaultStoreOptions(), + } + if err := serverv2.UnmarshalSubConfig(cfg, ServerName, config); err != nil { + return nil, fmt.Errorf("failed to unmarshal config: %w", err) + } + home := cfg[serverv2.FlagHome] + if home != nil { + config.Home = home.(string) + } + return config, nil +} diff --git a/server/v2/store/snapshot.go b/server/v2/store/snapshot.go index b804e34b71c..e7958068e56 100644 --- a/server/v2/store/snapshot.go +++ b/server/v2/store/snapshot.go @@ -76,7 +76,7 @@ func (s *Server[T]) ExportSnapshotCmd() *cobra.Command { } // RestoreSnapshotCmd returns a command to restore a snapshot -func (s *Server[T]) RestoreSnapshotCmd(newApp serverv2.AppCreator[T]) *cobra.Command { +func (s *Server[T]) RestoreSnapshotCmd(rootStore storev2.Backend) *cobra.Command { cmd := &cobra.Command{ Use: "restore ", Short: "Restore app state from local snapshot", @@ -95,8 +95,6 @@ func (s *Server[T]) RestoreSnapshotCmd(newApp serverv2.AppCreator[T]) *cobra.Com } logger := log.NewLogger(cmd.OutOrStdout()) - app := newApp(logger, v) - rootStore := app.GetStore().(storev2.RootStore) sm, err := createSnapshotsManager(cmd, v, logger, rootStore) if err != nil { @@ -350,7 +348,9 @@ func (s *Server[T]) LoadArchiveCmd() *cobra.Command { } } -func createSnapshotsManager(cmd *cobra.Command, v *viper.Viper, logger log.Logger, store storev2.RootStore) (*snapshots.Manager, error) { +func createSnapshotsManager( + cmd *cobra.Command, v *viper.Viper, logger log.Logger, store storev2.Backend, +) (*snapshots.Manager, error) { home := v.GetString(serverv2.FlagHome) snapshotStore, err := snapshots.NewStore(filepath.Join(home, "data", "snapshots")) if err != nil { @@ -371,7 +371,11 @@ func createSnapshotsManager(cmd *cobra.Command, v *viper.Viper, logger log.Logge } } - sm := snapshots.NewManager(snapshotStore, snapshots.NewSnapshotOptions(interval, uint32(keepRecent)), store.GetStateCommitment().(snapshots.CommitSnapshotter), store.GetStateStorage().(snapshots.StorageSnapshotter), nil, logger) + sm := snapshots.NewManager( + snapshotStore, snapshots.NewSnapshotOptions(interval, uint32(keepRecent)), + store.GetStateCommitment().(snapshots.CommitSnapshotter), + store.GetStateStorage().(snapshots.StorageSnapshotter), + nil, logger) return sm, nil } diff --git a/server/v2/streaming/plugin.md b/server/v2/streaming/plugin.md index 9eedff9ea2a..7a1d15bd430 100644 --- a/server/v2/streaming/plugin.md +++ b/server/v2/streaming/plugin.md @@ -29,7 +29,7 @@ To generate the stubs the local client implementation can call, run the followin make proto-gen ``` -For other languages you'll need to [download](https://github.com/cosmos/cosmos-sdk/blob/main/third_party/proto/README.md) +For other languages you'll need to [download](https://github.com/cosmos/cosmos-sdk/blob/main/proto/README.md#generate) the CosmosSDK protos into your project and compile. For language specific compilation instructions visit [https://github.com/grpc](https://github.com/grpc) and look in the `examples` folder of your language of choice `https://github.com/grpc/grpc-{language}/tree/master/examples` and [https://grpc.io](https://grpc.io) diff --git a/server/v2/types.go b/server/v2/types.go index afa82969131..a2838592217 100644 --- a/server/v2/types.go +++ b/server/v2/types.go @@ -1,9 +1,9 @@ package serverv2 import ( - gogoproto "github.com/cosmos/gogoproto/proto" "github.com/spf13/viper" + appmodulev2 "cosmossdk.io/core/appmodule/v2" "cosmossdk.io/core/server" "cosmossdk.io/core/transaction" "cosmossdk.io/log" @@ -16,6 +16,5 @@ type AppI[T transaction.Tx] interface { Name() string InterfaceRegistry() server.InterfaceRegistry GetAppManager() *appmanager.AppManager[T] - GetGPRCMethodsToMessageMap() map[string]func() gogoproto.Message - GetStore() any + GetQueryHandlers() map[string]appmodulev2.Handler } diff --git a/simapp/export.go b/simapp/export.go index 05346741b99..28d15f507a9 100644 --- a/simapp/export.go +++ b/simapp/export.go @@ -5,7 +5,6 @@ import ( "fmt" cmtproto "github.com/cometbft/cometbft/api/cometbft/types/v1" - cmttypes "github.com/cometbft/cometbft/types" "cosmossdk.io/collections" storetypes "cosmossdk.io/store/types" @@ -13,7 +12,6 @@ import ( "cosmossdk.io/x/staking" stakingtypes "cosmossdk.io/x/staking/types" - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" servertypes "github.com/cosmos/cosmos-sdk/server/types" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -43,25 +41,10 @@ func (app *SimApp) ExportAppStateAndValidators(forZeroHeight bool, jailAllowedAd } validators, err := staking.WriteValidators(ctx, app.StakingKeeper) - cmtValidators := []cmttypes.GenesisValidator{} - for _, val := range validators { - cmtPk, err := cryptocodec.ToCmtPubKeyInterface(val.PubKey) - if err != nil { - return servertypes.ExportedApp{}, err - } - cmtVal := cmttypes.GenesisValidator{ - Address: val.Address.Bytes(), - PubKey: cmtPk, - Power: val.Power, - Name: val.Name, - } - - cmtValidators = append(cmtValidators, cmtVal) - } return servertypes.ExportedApp{ AppState: appState, - Validators: cmtValidators, + Validators: validators, Height: height, ConsensusParams: app.BaseApp.GetConsensusParams(ctx), }, err diff --git a/simapp/go.mod b/simapp/go.mod index c2eeafc74d9..7adefe7f1f5 100644 --- a/simapp/go.mod +++ b/simapp/go.mod @@ -43,7 +43,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 ) require ( @@ -209,15 +209,15 @@ require ( go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.6.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/api v0.192.0 // indirect diff --git a/simapp/go.sum b/simapp/go.sum index 84be5993cd6..640946116d4 100644 --- a/simapp/go.sum +++ b/simapp/go.sum @@ -862,8 +862,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1083,13 +1083,13 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1100,8 +1100,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1393,8 +1393,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/simapp/upgrades.go b/simapp/upgrades.go index a4a475490ca..fc754250b6c 100644 --- a/simapp/upgrades.go +++ b/simapp/upgrades.go @@ -15,12 +15,12 @@ import ( ) // UpgradeName defines the on-chain upgrade name for the sample SimApp upgrade -// from v0.50.x to v0.51.x +// from v0.52.x to v0.54.x // // NOTE: This upgrade defines a reference implementation of what an upgrade // could look like when an application is migrating from Cosmos SDK version -// v0.50.x to v0.51.x. -const UpgradeName = "v050-to-v051" +// v0.52.x to v0.54.x. +const UpgradeName = "v052-to-v054" func (app SimApp) RegisterUpgradeHandlers() { app.UpgradeKeeper.SetUpgradeHandler( diff --git a/simapp/v2/app_di.go b/simapp/v2/app_di.go index 43ca45f99ae..29f62defcde 100644 --- a/simapp/v2/app_di.go +++ b/simapp/v2/app_di.go @@ -12,10 +12,13 @@ import ( "cosmossdk.io/depinject" "cosmossdk.io/log" "cosmossdk.io/runtime/v2" + serverstore "cosmossdk.io/server/v2/store" + "cosmossdk.io/store/v2" "cosmossdk.io/store/v2/root" basedepinject "cosmossdk.io/x/accounts/defaults/base/depinject" lockupdepinject "cosmossdk.io/x/accounts/defaults/lockup/depinject" multisigdepinject "cosmossdk.io/x/accounts/defaults/multisig/depinject" + stakingkeeper "cosmossdk.io/x/staking/keeper" upgradekeeper "cosmossdk.io/x/upgrade/keeper" "github.com/cosmos/cosmos-sdk/client" @@ -37,10 +40,12 @@ type SimApp[T transaction.Tx] struct { appCodec codec.Codec txConfig client.TxConfig interfaceRegistry codectypes.InterfaceRegistry + store store.RootStore // required keepers during wiring // others keepers are all in the app UpgradeKeeper *upgradekeeper.Keeper + StakingKeeper *stakingkeeper.Keeper } func init() { @@ -55,6 +60,18 @@ func init() { func AppConfig() depinject.Config { return depinject.Configs( appConfig, // Alternatively use appconfig.LoadYAML(AppConfigYAML) + runtime.DefaultServiceBindings(), + depinject.Provide( + codec.ProvideInterfaceRegistry, + codec.ProvideAddressCodec, + codec.ProvideProtoCodec, + codec.ProvideLegacyAmino, + runtime.ProvideSingletonScopedStoreBuilder, + ), + depinject.Invoke( + std.RegisterInterfaces, + std.RegisterLegacyAminoCodec, + ), ) } @@ -64,14 +81,14 @@ func NewSimApp[T transaction.Tx]( viper *viper.Viper, ) *SimApp[T] { var ( - app = &SimApp[T]{} - appBuilder *runtime.AppBuilder[T] - err error + app = &SimApp[T]{} + appBuilder *runtime.AppBuilder[T] + storeBuilder root.Builder + err error // merge the AppConfig and other configuration in one config appConfig = depinject.Configs( AppConfig(), - runtime.DefaultServiceBindings(), depinject.Supply( logger, viper, @@ -117,10 +134,6 @@ func NewSimApp[T transaction.Tx]( // interface. ), depinject.Provide( - codec.ProvideInterfaceRegistry, - codec.ProvideAddressCodec, - codec.ProvideProtoCodec, - codec.ProvideLegacyAmino, // inject desired account types: multisigdepinject.ProvideAccount, basedepinject.ProvideAccount, @@ -141,37 +154,33 @@ func NewSimApp[T transaction.Tx]( // } // }) ), - depinject.Invoke( - std.RegisterInterfaces, - std.RegisterLegacyAminoCodec, - ), ) ) - // the subsection of config that contains the store options (in app.toml [store.options] header) - // is unmarshaled into a store.Options struct and passed to the store builder. - // future work may move this specification and retrieval into store/v2. - // If these options are not specified then default values will be used. - if sub := viper.Sub("store.options"); sub != nil { - storeOptions := &root.Options{} - err := sub.Unmarshal(storeOptions) - if err != nil { - panic(err) - } - appConfig = depinject.Configs(appConfig, depinject.Supply(storeOptions)) - } - if err := depinject.Inject(appConfig, + &storeBuilder, &appBuilder, &app.appCodec, &app.legacyAmino, &app.txConfig, &app.interfaceRegistry, &app.UpgradeKeeper, + &app.StakingKeeper, ); err != nil { panic(err) } + // store/v2 follows a slightly more eager config life cycle than server components + storeConfig, err := serverstore.UnmarshalConfig(viper.AllSettings()) + if err != nil { + panic(err) + } + + app.store, err = storeBuilder.Build(logger, storeConfig) + if err != nil { + panic(err) + } + app.App, err = appBuilder.Build() if err != nil { panic(err) @@ -210,7 +219,6 @@ func (app *SimApp[T]) TxConfig() client.TxConfig { return app.txConfig } -// GetStore gets the app store. -func (app *SimApp[T]) GetStore() any { - return app.App.GetStore() +func (app *SimApp[T]) GetStore() store.RootStore { + return app.store } diff --git a/simapp/v2/app_test.go b/simapp/v2/app_test.go index f634d2d6766..8c705565956 100644 --- a/simapp/v2/app_test.go +++ b/simapp/v2/app_test.go @@ -18,8 +18,8 @@ import ( "cosmossdk.io/core/transaction" "cosmossdk.io/log" sdkmath "cosmossdk.io/math" + "cosmossdk.io/runtime/v2" serverv2 "cosmossdk.io/server/v2" - comettypes "cosmossdk.io/server/v2/cometbft/types" serverv2store "cosmossdk.io/server/v2/store" "cosmossdk.io/store/v2/db" banktypes "cosmossdk.io/x/bank/types" @@ -40,6 +40,7 @@ func NewTestApp(t *testing.T) (*SimApp[transaction.Tx], context.Context) { vp.Set(serverv2store.FlagAppDBBackend, string(db.DBTypeGoLevelDB)) vp.Set(serverv2.FlagHome, t.TempDir()) + runtime.ResetSingletonScopedStoreBuilder() app := NewSimApp[transaction.Tx](logger, vp) genesis := app.ModuleManager().DefaultGenesis() @@ -73,7 +74,7 @@ func NewTestApp(t *testing.T) (*SimApp[transaction.Tx], context.Context) { genesisBytes, err := json.Marshal(genesis) require.NoError(t, err) - st := app.GetStore().(comettypes.Store) + st := app.GetStore() ci, err := st.LastCommitID() require.NoError(t, err) @@ -109,7 +110,7 @@ func MoveNextBlock(t *testing.T, app *SimApp[transaction.Tx], ctx context.Contex bz := sha256.Sum256([]byte{}) - st := app.GetStore().(comettypes.Store) + st := app.GetStore() ci, err := st.LastCommitID() require.NoError(t, err) diff --git a/simapp/v2/export.go b/simapp/v2/export.go index 5a1757b1653..50f4a898bb3 100644 --- a/simapp/v2/export.go +++ b/simapp/v2/export.go @@ -3,27 +3,46 @@ package simapp import ( "context" + "cosmossdk.io/runtime/v2/services" + "cosmossdk.io/x/staking" + v2 "github.com/cosmos/cosmos-sdk/x/genutil/v2" ) // ExportAppStateAndValidators exports the state of the application for a genesis // file. -func (app *SimApp[T]) ExportAppStateAndValidators(jailAllowedAddrs []string) (v2.ExportedApp, error) { - // as if they could withdraw from the start of the next block +// This is a demonstation of how to export a genesis file. Export may need extended at +// the user discretion for cleaning the genesis state at the end provided with jailAllowedAddrs +func (app *SimApp[T]) ExportAppStateAndValidators( + jailAllowedAddrs []string, +) (v2.ExportedApp, error) { ctx := context.Background() + var exportedApp v2.ExportedApp latestHeight, err := app.LoadLatestHeight() if err != nil { - return v2.ExportedApp{}, err + return exportedApp, err } genesis, err := app.ExportGenesis(ctx, latestHeight) if err != nil { - return v2.ExportedApp{}, err + return exportedApp, err + } + + readerMap, err := app.GetStore().StateAt(latestHeight) + if err != nil { + return exportedApp, err + } + genesisCtx := services.NewGenesisContext(readerMap) + err = genesisCtx.Read(ctx, func(ctx context.Context) error { + exportedApp.Validators, err = staking.WriteValidators(ctx, app.StakingKeeper) + return err + }) + if err != nil { + return exportedApp, err } - return v2.ExportedApp{ - AppState: genesis, - Height: int64(latestHeight), - }, nil + exportedApp.AppState = genesis + exportedApp.Height = int64(latestHeight) + return exportedApp, nil } diff --git a/simapp/v2/go.mod b/simapp/v2/go.mod index 484d0de1e4e..448a98a8928 100644 --- a/simapp/v2/go.mod +++ b/simapp/v2/go.mod @@ -39,7 +39,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 ) require ( @@ -216,15 +216,15 @@ require ( go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.6.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/api v0.192.0 // indirect diff --git a/simapp/v2/go.sum b/simapp/v2/go.sum index b0eca7fdef2..f29f693d653 100644 --- a/simapp/v2/go.sum +++ b/simapp/v2/go.sum @@ -866,8 +866,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1087,13 +1087,13 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1104,8 +1104,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1397,8 +1397,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/simapp/v2/simdv2/cmd/commands.go b/simapp/v2/simdv2/cmd/commands.go index addc7eeb768..6cc233cc9b1 100644 --- a/simapp/v2/simdv2/cmd/commands.go +++ b/simapp/v2/simdv2/cmd/commands.go @@ -17,8 +17,9 @@ import ( "cosmossdk.io/server/v2/api/grpc" "cosmossdk.io/server/v2/api/telemetry" "cosmossdk.io/server/v2/cometbft" - "cosmossdk.io/server/v2/store" + serverstore "cosmossdk.io/server/v2/store" "cosmossdk.io/simapp/v2" + "cosmossdk.io/store/v2/root" confixcmd "cosmossdk.io/tools/confix/cmd" "github.com/cosmos/cosmos-sdk/client" @@ -43,6 +44,7 @@ func newApp[T transaction.Tx](logger log.Logger, viper *viper.Viper) serverv2.Ap func initRootCmd[T transaction.Tx]( rootCmd *cobra.Command, txConfig client.TxConfig, + storeBuilder root.Builder, moduleManager *runtimev2.MM[T], ) { cfg := sdk.GetConfig() @@ -52,7 +54,7 @@ func initRootCmd[T transaction.Tx]( genutilcli.InitCmd(moduleManager), debug.Cmd(), confixcmd.ConfigCommand(), - NewTestnetCmd(moduleManager), + NewTestnetCmd(storeBuilder, moduleManager), ) logger, err := serverv2.NewLogger(viper.New(), rootCmd.OutOrStdout()) @@ -77,11 +79,12 @@ func initRootCmd[T transaction.Tx]( initServerConfig(), cometbft.New( &genericTxDecoder[T]{txConfig}, + storeBuilder, initCometOptions[T](), initCometConfig(), ), grpc.New[T](), - store.New[T](newApp), + serverstore.New[T](storeBuilder), telemetry.New[T](), ); err != nil { panic(err) diff --git a/simapp/v2/simdv2/cmd/root_di.go b/simapp/v2/simdv2/cmd/root_di.go index 1ad834b53a5..30291b4ecdb 100644 --- a/simapp/v2/simdv2/cmd/root_di.go +++ b/simapp/v2/simdv2/cmd/root_di.go @@ -14,13 +14,13 @@ import ( "cosmossdk.io/log" "cosmossdk.io/runtime/v2" "cosmossdk.io/simapp/v2" + "cosmossdk.io/store/v2/root" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/config" nodeservice "github.com/cosmos/cosmos-sdk/client/grpc/node" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/std" "github.com/cosmos/cosmos-sdk/x/auth/tx" authtxconfig "github.com/cosmos/cosmos-sdk/x/auth/tx/config" "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -32,25 +32,16 @@ func NewRootCmd[T transaction.Tx]() *cobra.Command { autoCliOpts autocli.AppOptions moduleManager *runtime.MM[T] clientCtx client.Context + storeBuilder root.Builder ) if err := depinject.Inject( depinject.Configs( simapp.AppConfig(), - runtime.DefaultServiceBindings(), + depinject.Provide(ProvideClientContext), depinject.Supply(log.NewNopLogger()), - depinject.Provide( - codec.ProvideInterfaceRegistry, - codec.ProvideAddressCodec, - codec.ProvideProtoCodec, - codec.ProvideLegacyAmino, - ProvideClientContext, - ), - depinject.Invoke( - std.RegisterInterfaces, - std.RegisterLegacyAminoCodec, - ), ), + &storeBuilder, &autoCliOpts, &moduleManager, &clientCtx, @@ -83,7 +74,7 @@ func NewRootCmd[T transaction.Tx]() *cobra.Command { }, } - initRootCmd(rootCmd, clientCtx.TxConfig, moduleManager) + initRootCmd(rootCmd, clientCtx.TxConfig, storeBuilder, moduleManager) nodeCmds := nodeservice.NewNodeCommands() autoCliOpts.ModuleOptions = make(map[string]*autocliv1.ModuleOptions) diff --git a/simapp/v2/simdv2/cmd/testnet.go b/simapp/v2/simdv2/cmd/testnet.go index ae01310f915..4e24f708365 100644 --- a/simapp/v2/simdv2/cmd/testnet.go +++ b/simapp/v2/simdv2/cmd/testnet.go @@ -23,6 +23,7 @@ import ( "cosmossdk.io/server/v2/api/grpc" "cosmossdk.io/server/v2/cometbft" "cosmossdk.io/server/v2/store" + "cosmossdk.io/store/v2/root" banktypes "cosmossdk.io/x/bank/types" bankv2types "cosmossdk.io/x/bank/v2/types" stakingtypes "cosmossdk.io/x/staking/types" @@ -87,7 +88,7 @@ func addTestnetFlagsToCmd(cmd *cobra.Command) { // NewTestnetCmd creates a root testnet command with subcommands to run an in-process testnet or initialize // validator configuration files for running a multi-validator testnet in a separate process -func NewTestnetCmd[T transaction.Tx](mm *runtimev2.MM[T]) *cobra.Command { +func NewTestnetCmd[T transaction.Tx](sb root.Builder, mm *runtimev2.MM[T]) *cobra.Command { testnetCmd := &cobra.Command{ Use: "testnet", Short: "subcommands for starting or configuring local testnets", @@ -96,13 +97,13 @@ func NewTestnetCmd[T transaction.Tx](mm *runtimev2.MM[T]) *cobra.Command { RunE: client.ValidateCmd, } - testnetCmd.AddCommand(testnetInitFilesCmd(mm)) + testnetCmd.AddCommand(testnetInitFilesCmd(sb, mm)) return testnetCmd } // testnetInitFilesCmd returns a cmd to initialize all files for CometBFT testnet and application -func testnetInitFilesCmd[T transaction.Tx](mm *runtimev2.MM[T]) *cobra.Command { +func testnetInitFilesCmd[T transaction.Tx](sb root.Builder, mm *runtimev2.MM[T]) *cobra.Command { cmd := &cobra.Command{ Use: "init-files", Short: "Initialize config directories & files for a multi-validator testnet running locally via separate processes (e.g. Docker Compose or similar)", @@ -143,7 +144,7 @@ Example: return err } - return initTestnetFiles(clientCtx, cmd, config, mm, args) + return initTestnetFiles(clientCtx, sb, cmd, config, mm, args) }, } @@ -165,6 +166,7 @@ const nodeDirPerm = 0o755 // initTestnetFiles initializes testnet files for a testnet to be run in a separate process func initTestnetFiles[T transaction.Tx]( clientCtx client.Context, + sb root.Builder, cmd *cobra.Command, nodeConfig *cmtconfig.Config, mm *runtimev2.MM[T], @@ -339,12 +341,13 @@ func initTestnetFiles[T transaction.Tx]( // Write server config cometServer := cometbft.New[T]( &genericTxDecoder[T]{clientCtx.TxConfig}, + sb, cometbft.ServerOptions[T]{}, cometbft.OverwriteDefaultConfigTomlConfig(nodeConfig), ) - storeServer := store.New[T](newApp) + storeServer := store.New[T](sb) grpcServer := grpc.New[T](grpc.OverwriteDefaultConfig(grpcConfig)) - server := serverv2.NewServer(log.NewNopLogger(), serverCfg, cometServer, grpcServer, storeServer) + server := serverv2.NewServer[T](log.NewNopLogger(), serverCfg, cometServer, grpcServer, storeServer) err = server.WriteConfig(filepath.Join(nodeDir, "config")) if err != nil { return err diff --git a/simapp/v2/upgrades.go b/simapp/v2/upgrades.go index 48d557eedb4..2145196fa86 100644 --- a/simapp/v2/upgrades.go +++ b/simapp/v2/upgrades.go @@ -14,12 +14,12 @@ import ( ) // UpgradeName defines the on-chain upgrade name for the sample SimApp upgrade -// from v0.50.x to v0.51.x +// from v0.52.x to v2 // // NOTE: This upgrade defines a reference implementation of what an upgrade // could look like when an application is migrating from Cosmos SDK version -// v0.50.x to v0.51.x. -const UpgradeName = "v050-to-v051" +// v0.52.x to v2. +const UpgradeName = "v052-to-v2" func (app *SimApp[T]) RegisterUpgradeHandlers() { app.UpgradeKeeper.SetUpgradeHandler( diff --git a/store/go.mod b/store/go.mod index a1d3ad87b43..260e335619d 100644 --- a/store/go.mod +++ b/store/go.mod @@ -3,7 +3,7 @@ module cosmossdk.io/store go 1.23 require ( - cosmossdk.io/core v1.0.0-alpha.3 + cosmossdk.io/core v1.0.0-alpha.4 cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29 cosmossdk.io/errors v1.0.1 cosmossdk.io/log v1.4.1 @@ -22,7 +22,7 @@ require ( github.com/tidwall/btree v1.7.0 go.uber.org/mock v0.4.0 google.golang.org/grpc v1.67.1 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 gotest.tools/v3 v3.5.1 ) @@ -48,11 +48,11 @@ require ( github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/rs/zerolog v1.33.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/net v0.29.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/store/go.sum b/store/go.sum index f00b75056cd..43b213989e0 100644 --- a/store/go.sum +++ b/store/go.sum @@ -1,5 +1,5 @@ -cosmossdk.io/core v1.0.0-alpha.3 h1:pnxaYAas7llXgVz1lM7X6De74nWrhNKnB3yMKe4OUUA= -cosmossdk.io/core v1.0.0-alpha.3/go.mod h1:3u9cWq1FAVtiiCrDPpo4LhR+9V6k/ycSG4/Y/tREWCY= +cosmossdk.io/core v1.0.0-alpha.4 h1:9iuroT9ejDYETCsGkzkvs/wAY/5UFl7nCIINFRxyMJY= +cosmossdk.io/core v1.0.0-alpha.4/go.mod h1:3u9cWq1FAVtiiCrDPpo4LhR+9V6k/ycSG4/Y/tREWCY= cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29 h1:NxxUo0GMJUbIuVg0R70e3cbn9eFTEuMr7ev1AFvypdY= cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29/go.mod h1:8s2tPeJtSiQuoyPmr2Ag7meikonISO4Fv4MoO8+ORrs= cosmossdk.io/errors v1.0.1 h1:bzu+Kcr0kS/1DuPBtUFdWjzLqyUuCiyHjyJB6srBV/0= @@ -207,8 +207,8 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -260,16 +260,16 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -292,8 +292,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/store/prefix/store.go b/store/prefix/store.go index 26b8b0344a7..d8efb5a6240 100644 --- a/store/prefix/store.go +++ b/store/prefix/store.go @@ -12,7 +12,7 @@ import ( var _ types.KVStore = Store{} -// Store is similar with cometbft/cometbft/libs/db/prefix_db +// Store is similar with cometbft/cometbft-db/blob/v1.0.1/prefixdb.go // both gives access only to the limited subset of the store // for convenience or safety type Store struct { @@ -81,7 +81,7 @@ func (s Store) Delete(key []byte) { } // Iterator implements KVStore -// Check https://github.com/cometbft/cometbft/blob/master/libs/db/prefix_db.go#L106 +// Check https://github.com/cometbft/cometbft-db/blob/v1.0.1/prefixdb.go#L109 func (s Store) Iterator(start, end []byte) types.Iterator { newstart := cloneAppend(s.prefix, start) @@ -98,7 +98,7 @@ func (s Store) Iterator(start, end []byte) types.Iterator { } // ReverseIterator implements KVStore -// Check https://github.com/cometbft/cometbft/blob/master/libs/db/prefix_db.go#L129 +// Check https://github.com/cometbft/cometbft-db/blob/v1.0.1/prefixdb.go#L132 func (s Store) ReverseIterator(start, end []byte) types.Iterator { newstart := cloneAppend(s.prefix, start) @@ -192,7 +192,7 @@ func (pi *prefixIterator) Error() error { return nil } -// copied from github.com/cometbft/cometbft/libs/db/prefix_db.go +// copied from github.com/cometbft/cometbft-db/blob/v1.0.1/prefixdb.go func stripPrefix(key, prefix []byte) []byte { if len(key) < len(prefix) || !bytes.Equal(key[:len(prefix)], prefix) { panic("should not happen") diff --git a/store/prefix/store_test.go b/store/prefix/store_test.go index b586c37aade..71846120a1c 100644 --- a/store/prefix/store_test.go +++ b/store/prefix/store_test.go @@ -237,7 +237,7 @@ func TestPrefixStoreReverseIteratorEdgeCase(t *testing.T) { iter.Close() } -// Tests below are ported from https://github.com/cometbft/cometbft/blob/master/libs/db/prefix_db_test.go +// Tests below are ported from https://github.com/cometbft/cometbft-db/blob/v1.0.1/prefixdb_test.go func mockStoreWithStuff() types.KVStore { db := coretesting.NewMemDB() diff --git a/store/streaming/abci/README.md b/store/streaming/abci/README.md index 08aaf12e8a3..1ee13d65976 100644 --- a/store/streaming/abci/README.md +++ b/store/streaming/abci/README.md @@ -27,7 +27,7 @@ To generate the stubs the local client implementation can call, run the followin make proto-gen ``` -For other languages you'll need to [download](https://github.com/cosmos/cosmos-sdk/blob/main/third_party/proto/README.md) +For other languages you'll need to [download](https://github.com/cosmos/cosmos-sdk/blob/main/proto/README.md#generate) the CosmosSDK protos into your project and compile. For language specific compilation instructions visit [https://github.com/grpc](https://github.com/grpc) and look in the `examples` folder of your language of choice `https://github.com/grpc/grpc-{language}/tree/master/examples` and [https://grpc.io](https://grpc.io) diff --git a/store/v2/README.md b/store/v2/README.md index 213f55d05ae..eb8495c9adc 100644 --- a/store/v2/README.md +++ b/store/v2/README.md @@ -1,7 +1,7 @@ # Store The `store` package contains the implementation of store/v2, which is the SDK's -abstraction around managing historical and committed state. See [ADR-065](../docs/architecture/adr-065-store-v2.md) +abstraction around managing historical and committed state. See [ADR-065](../../docs/architecture/adr-065-store-v2.md) and [Store v2 Design](https://docs.google.com/document/d/1l6uXIjTPHOOWM5N4sUUmUfCZvePoa5SNfIEtmgvgQSU/edit#heading=h.nz8dqy6wa4g1) for a high-level overview of the design and rationale. ## Usage @@ -42,21 +42,26 @@ sequenceDiagram end ``` -`Prune store keys` does not remove the data from the SC and SS instantly. It only +`PruneStoreKeys` does not remove the data from the SC and SS instantly. It only marks the store keys as pruned. The actual data removal is done by the pruning process of the underlying SS and SC. ## Migration - +The migration from store/v1 to store/v2 is supported by the `MigrationManager` in +the `migration` package. See [Migration Manager](./migration/README.md) for more details. ## Pruning The `root.Store` is NOT responsible for pruning. Rather, pruning is the responsibility of the underlying SS and SC layers. This means pruning can be implementation specific, -such as being synchronous or asynchronous. +such as being synchronous or asynchronous. See [Pruning Manager](./pruning/README.md) for more details. +## State Sync + +The `root.Store` is NOT responsible for state sync. See [Snapshots Manager](./snapshots/README.md) +for more details. ## Test Coverage diff --git a/store/v2/commitment/README.md b/store/v2/commitment/README.md index c9bcf111b4e..bf730ccb374 100644 --- a/store/v2/commitment/README.md +++ b/store/v2/commitment/README.md @@ -28,7 +28,11 @@ See this [section](https://docs.google.com/document/d/1l6uXIjTPHOOWM5N4sUUmUfCZv ## Pruning - +Pruning is the process of efficiently managing and removing outdated data from the +State Commitment (SC). To facilitate this, the SC backend must implement the `Pruner` +interface, allowing the `PruningManager` to execute data pruning operations according +to the specified `PruningOption`. Optionally, the SC backend can implement the +`PausablePruner` interface to pause pruning during a commit. ## State Sync diff --git a/store/v2/go.mod b/store/v2/go.mod index 1a0d84d80a8..953ecfbfa03 100644 --- a/store/v2/go.mod +++ b/store/v2/go.mod @@ -57,10 +57,10 @@ require ( github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/rs/zerolog v1.33.0 // indirect github.com/tidwall/btree v1.7.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/text v0.18.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/store/v2/go.sum b/store/v2/go.sum index 8211266d41a..6daeacc737e 100644 --- a/store/v2/go.sum +++ b/store/v2/go.sum @@ -228,8 +228,8 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -278,16 +278,16 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -306,8 +306,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/store/v2/migration/README.md b/store/v2/migration/README.md new file mode 100644 index 00000000000..9db8c9874a8 --- /dev/null +++ b/store/v2/migration/README.md @@ -0,0 +1,111 @@ +# Migration Manager + +The `migration` package contains the `migration.Manager`, which is responsible +for migrating data from `store/v1` to `store/v2`. To ensure a smooth transition, +the process is designed to **lazily** migrate data in the background without blocking +`root.Store` operations. + +## Overview + +The migration process involves several steps: + +1. **Create a snapshot** of the current state while `Commit` operations continue to + function with `store/v1`. +2. **Restore the snapshot** into the new StateStorage (SS) and StateCommitment (SC). +3. **Sync recent state changes** from `store/v1` to the new SS and SC. +4. After syncing, the `Commit` operation will be switched to the new `store/v2`. + +Taking a snapshot is a lightweight operation. The snapshot is not stored on disk but +consumed by the `Restore` process, which replays state changes to the new SS and SC. + +> **Note:** After migration, `store/v2` does **not** support historical queries. +If historical data access is required, a full state migration to `store/v2` is necessary. + +## Usage + +You can create a new `migration.Manager` by calling the following function: + +```go +func NewManager( + db corestore.KVStoreWithBatch, + sm *snapshots.Manager, + ss *storage.StorageStore, + sc *commitment.CommitStore, + logger log.Logger +) *Manager +``` + +* `sc` (Commitment Store) can be `nil`. In that case, the Manager will migrate only + the state storage. +* The migration process is lazy, meaning data is migrated in the background while + `root.Store` remains fully operational. + +To initiate the migration process, call the `Start` method: + +```go +func (m *Manager) Start(ctx context.Context) error +``` + +> **Note:** It should be called by the RootStore, running in the background. + +## Migration Flow + +```mermaid +sequenceDiagram + autonumber + + participant A as RootStore + participant B as MigrationManager + participant C as SnapshotsManager + participant D as StateCommitment + participant E as StateStorage + + A->>B: Start + loop Old Data Migration + B->>C: Create Snapshot + C->>B: Stream Snapshot + B->>D: State Sync (Restore) + B->>E: Write Changeset (Restore) + end + + loop New Commit Data Sync + A->>B: Commit(Changeset) + B->>B: Store Changeset + B->>D: Commit Changeset + B->>E: Write Changeset + end + + B->>A: Switch to new store/v2 +``` + +## Key Considerations + +### Laziness and Background Operation + +The migration is performed lazily, meaning it occurs in the background without +interrupting the current operations on root.Store. This allows the chain to continue +running while data is gradually migrated to `store/v2`. State synchronization ensures +that any new state changes during the migration are also applied to `store/v2`. + +However, note that there may be a performance impact depending on the size of the data +being migrated, and it’s essential to monitor the migration process in production +environments. + +### Handling Failures and Rollbacks + +It is important to consider how the migration manager handles errors or system failures +during the migration process: + +* If the migration fails, there is no impact on the existing `store/v1` operations, + but need to restart the migration process from the scratch. +* In the event of a critical failure after migration, a rollback may not be possible, + and it is needed to keep the `store/v1` backup for a certain period. + +### Impact on Historical Queries + +After the migration, the new `store/v2` does not support historical queries. +This limitation should be clearly understood before starting the migration process, +especially if the node relies on historical data for any operations. + +If historical queries are required, users must fully migrate all historical data to `store/v2`. +Alternatively, keeping store/v1 accessible for historical queries could be an option. \ No newline at end of file diff --git a/store/v2/root/builder.go b/store/v2/root/builder.go new file mode 100644 index 00000000000..885c41d2448 --- /dev/null +++ b/store/v2/root/builder.go @@ -0,0 +1,95 @@ +package root + +import ( + "fmt" + "path/filepath" + + "cosmossdk.io/log" + "cosmossdk.io/store/v2" + "cosmossdk.io/store/v2/db" +) + +// Builder is the interface for a store/v2 RootStore builder. +// RootStores built by the Cosmos SDK typically involve a 2 phase initialization: +// 1. Namespace registration +// 2. Configuration and loading +// +// The Builder interface is used to facilitate this pattern. Namespaces (store keys) are registered +// by calling RegisterKey before Build is called. Build is then called with a Config +// object and a RootStore is returned. Calls to Get may return the `RootStore` if Build +// was successful, but that's left up to the implementation. +type Builder interface { + // Build creates a new store/v2 RootStore from the given Config. + Build(log.Logger, *Config) (store.RootStore, error) + // RegisterKey registers a store key (namespace) to be used when building the RootStore. + RegisterKey(string) + // Get returns the Store. Build should be called before calling Get or the result will be nil. + Get() store.RootStore +} + +var _ Builder = (*builder)(nil) + +// builder is the default builder for a store/v2 RootStore satisfying the Store interface. +// Tangibly it combines store key registration and a top-level Config to create a RootStore by calling +// the CreateRootStore factory function. +type builder struct { + // input + storeKeys map[string]struct{} + + // output + store store.RootStore +} + +func NewBuilder() Builder { + return &builder{storeKeys: make(map[string]struct{})} +} + +// Build creates a new store/v2 RootStore. +func (sb *builder) Build( + logger log.Logger, + config *Config, +) (store.RootStore, error) { + if sb.store != nil { + return sb.store, nil + } + if config.Home == "" { + return nil, fmt.Errorf("home directory is required") + } + scRawDb, err := db.NewDB( + db.DBType(config.AppDBBackend), + "application", + filepath.Join(config.Home, "data"), + nil, + ) + if err != nil { + return nil, fmt.Errorf("failed to create SCRawDB: %w", err) + } + + var storeKeys []string + for key := range sb.storeKeys { + storeKeys = append(storeKeys, key) + } + + factoryOptions := &FactoryOptions{ + Logger: logger, + RootDir: config.Home, + Options: config.Options, + StoreKeys: storeKeys, + SCRawDB: scRawDb, + } + + rs, err := CreateRootStore(factoryOptions) + if err != nil { + return nil, fmt.Errorf("failed to create root store: %w", err) + } + sb.store = rs + return sb.store, nil +} + +func (sb *builder) Get() store.RootStore { + return sb.store +} + +func (sb *builder) RegisterKey(key string) { + sb.storeKeys[key] = struct{}{} +} diff --git a/store/v2/root/config.go b/store/v2/root/config.go new file mode 100644 index 00000000000..28f46a3879b --- /dev/null +++ b/store/v2/root/config.go @@ -0,0 +1,14 @@ +package root + +func DefaultConfig() *Config { + return &Config{ + AppDBBackend: "goleveldb", + Options: DefaultStoreOptions(), + } +} + +type Config struct { + Home string `toml:"-"` // this field is omitted in the TOML file + AppDBBackend string `mapstructure:"app-db-backend" toml:"app-db-backend" comment:"The type of database for application and snapshots databases."` + Options Options `mapstructure:"options" toml:"options"` +} diff --git a/store/v2/root/factory.go b/store/v2/root/factory.go index e6f86917ab0..9e1f76a36ce 100644 --- a/store/v2/root/factory.go +++ b/store/v2/root/factory.go @@ -33,7 +33,7 @@ const ( SCTypeIavlV2 SCType = "iavl-v2" ) -// app.toml config options +// Options are the options for creating a root store. type Options struct { SSType SSType `mapstructure:"ss-type" toml:"ss-type" comment:"SState storage database type. Currently we support: \"sqlite\", \"pebble\" and \"rocksdb\""` SCType SCType `mapstructure:"sc-type" toml:"sc-type" comment:"State commitment database type. Currently we support: \"iavl\" and \"iavl-v2\""` diff --git a/store/v2/storage/README.md b/store/v2/storage/README.md index 48606d6c19c..5467bff24a0 100644 --- a/store/v2/storage/README.md +++ b/store/v2/storage/README.md @@ -70,13 +70,10 @@ Iterate/backend_rocksdb_versiondb_opts-10 778ms ± 0% ## Pruning -Pruning is an implementation and responsibility of the underlying SS backend. -Specifically, the `StorageStore` accepts `store.PruningOption` which defines the -pruning configuration. During `ApplyChangeset`, the `StorageStore` will check if -pruning should occur based on the current height being committed. If so, it will -delegate a `Prune` call on the underlying SS backend, which can be defined specific -to the implementation, e.g. asynchronous or synchronous. - +Pruning is the process of efficiently managing and removing outdated or redundant +data from the State Storage (SS). To facilitate this, the SS backend must implement +the `Pruner` interface, allowing the `PruningManager` to execute data pruning operations +according to the specified `PruningOption`. ## State Sync diff --git a/store/v2/store.go b/store/v2/store.go index fee3ad39dbf..1adf44f0b89 100644 --- a/store/v2/store.go +++ b/store/v2/store.go @@ -12,6 +12,9 @@ import ( // RootStore defines an abstraction layer containing a State Storage (SS) engine // and one or more State Commitment (SC) engines. type RootStore interface { + Pruner + Backend + // StateLatest returns a read-only version of the RootStore at the latest // height, alongside the associated version. StateLatest() (uint64, corestore.ReaderMap, error) @@ -21,12 +24,6 @@ type RootStore interface { // an error must be returned. StateAt(version uint64) (corestore.ReaderMap, error) - // GetStateStorage returns the SS backend. - GetStateStorage() VersionedDatabase - - // GetStateCommitment returns the SC backend. - GetStateCommitment() Committer - // Query performs a query on the RootStore for a given store key, version (height), // and key tuple. Queries should be routed to the underlying SS engine. Query(storeKey []byte, version uint64, key []byte, prove bool) (QueryResult, error) @@ -67,11 +64,18 @@ type RootStore interface { // SetMetrics sets the telemetry handler on the RootStore. SetMetrics(m metrics.Metrics) - Prune(version uint64) error - io.Closer } +// Backend defines the interface for the RootStore backends. +type Backend interface { + // GetStateStorage returns the SS backend. + GetStateStorage() VersionedDatabase + + // GetStateCommitment returns the SC backend. + GetStateCommitment() Committer +} + // UpgradeableStore defines the interface for upgrading store keys. type UpgradeableStore interface { // LoadVersionAndUpgrade behaves identically to LoadVersion except it also diff --git a/tests/e2e/distribution/cli_test.go b/tests/e2e/distribution/cli_test.go deleted file mode 100644 index ad8e877d961..00000000000 --- a/tests/e2e/distribution/cli_test.go +++ /dev/null @@ -1,18 +0,0 @@ -//go:build e2e -// +build e2e - -package distribution - -import ( - "testing" - - "github.com/stretchr/testify/suite" -) - -func TestWithdrawAllSuite(t *testing.T) { - suite.Run(t, new(WithdrawAllTestSuite)) -} - -func TestGRPCQueryTestSuite(t *testing.T) { - suite.Run(t, new(GRPCQueryTestSuite)) -} diff --git a/tests/e2e/distribution/grpc_query_suite.go b/tests/e2e/distribution/grpc_query_suite.go deleted file mode 100644 index 991567abfb4..00000000000 --- a/tests/e2e/distribution/grpc_query_suite.go +++ /dev/null @@ -1,460 +0,0 @@ -package distribution - -import ( - "fmt" - - "github.com/cosmos/gogoproto/proto" - "github.com/stretchr/testify/suite" - - "cosmossdk.io/simapp" - "cosmossdk.io/x/distribution/types" - - sdktestutil "github.com/cosmos/cosmos-sdk/testutil" - "github.com/cosmos/cosmos-sdk/testutil/network" - sdk "github.com/cosmos/cosmos-sdk/types" - grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" - "github.com/cosmos/cosmos-sdk/types/query" -) - -type GRPCQueryTestSuite struct { - suite.Suite - - cfg network.Config - network network.NetworkI -} - -func (s *GRPCQueryTestSuite) SetupSuite() { - s.T().Log("setting up e2e test suite") - - cfg := network.DefaultConfig(simapp.NewTestNetworkFixture) - cfg.NumValidators = 1 - s.cfg = cfg - - var err error - s.network, err = network.New(s.T(), s.T().TempDir(), cfg) - s.Require().NoError(err) - - s.Require().NoError(s.network.WaitForNextBlock()) -} - -// TearDownSuite cleans up the current test network after _each_ test. -func (s *GRPCQueryTestSuite) TearDownSuite() { - s.T().Log("tearing down e2e test suite1") - s.network.Cleanup() -} - -func (s *GRPCQueryTestSuite) TestQueryParamsGRPC() { - val := s.network.GetValidators()[0] - baseURL := val.GetAPIAddress() - - testCases := []struct { - name string - url string - respType proto.Message - expected proto.Message - }{ - { - "gRPC request params", - fmt.Sprintf("%s/cosmos/distribution/v1beta1/params", baseURL), - &types.QueryParamsResponse{}, - &types.QueryParamsResponse{ - Params: types.DefaultParams(), - }, - }, - } - - for _, tc := range testCases { - - resp, err := sdktestutil.GetRequest(tc.url) - s.Run(tc.name, func() { - s.Require().NoError(err) - s.Require().NoError(val.GetClientCtx().Codec.UnmarshalJSON(resp, tc.respType)) - s.Require().Equal(tc.expected, tc.respType) - }) - } -} - -func (s *GRPCQueryTestSuite) TestQueryValidatorDistributionInfoGRPC() { - val := s.network.GetValidators()[0] - baseURL := val.GetAPIAddress() - - testCases := []struct { - name string - url string - expErr bool - respType proto.Message - }{ - { - "gRPC request with wrong validator address", - fmt.Sprintf("%s/cosmos/distribution/v1beta1/validators/%s", baseURL, "wrongAddress"), - true, - &types.QueryValidatorDistributionInfoResponse{}, - }, - { - "gRPC request with valid validator address ", - fmt.Sprintf("%s/cosmos/distribution/v1beta1/validators/%s", baseURL, val.GetValAddress().String()), - false, - &types.QueryValidatorDistributionInfoResponse{}, - }, - } - - for _, tc := range testCases { - - resp, err := sdktestutil.GetRequest(tc.url) - s.Run(tc.name, func() { - if tc.expErr { - s.Require().Error(val.GetClientCtx().Codec.UnmarshalJSON(resp, tc.respType)) - } else { - s.Require().NoError(err) - s.Require().NoError(val.GetClientCtx().Codec.UnmarshalJSON(resp, tc.respType)) - } - }) - } -} - -func (s *GRPCQueryTestSuite) TestQueryOutstandingRewardsGRPC() { - val := s.network.GetValidators()[0] - baseURL := val.GetAPIAddress() - - rewards, err := sdk.ParseDecCoins("46.06stake") - s.Require().NoError(err) - - testCases := []struct { - name string - url string - headers map[string]string - expErr bool - respType proto.Message - expected proto.Message - }{ - { - "gRPC request params with wrong validator address", - fmt.Sprintf("%s/cosmos/distribution/v1beta1/validators/%s/outstanding_rewards", baseURL, "wrongAddress"), - map[string]string{}, - true, - &types.QueryValidatorOutstandingRewardsResponse{}, - &types.QueryValidatorOutstandingRewardsResponse{}, - }, - { - "gRPC request params valid address", - fmt.Sprintf("%s/cosmos/distribution/v1beta1/validators/%s/outstanding_rewards", baseURL, val.GetValAddress().String()), - map[string]string{ - grpctypes.GRPCBlockHeightHeader: "2", - }, - false, - &types.QueryValidatorOutstandingRewardsResponse{}, - &types.QueryValidatorOutstandingRewardsResponse{ - Rewards: types.ValidatorOutstandingRewards{ - Rewards: rewards, - }, - }, - }, - } - - for _, tc := range testCases { - - resp, err := sdktestutil.GetRequestWithHeaders(tc.url, tc.headers) - s.Run(tc.name, func() { - if tc.expErr { - s.Require().Error(val.GetClientCtx().Codec.UnmarshalJSON(resp, tc.respType)) - } else { - s.Require().NoError(err) - s.Require().NoError(val.GetClientCtx().Codec.UnmarshalJSON(resp, tc.respType)) - s.Require().Equal(tc.expected.String(), tc.respType.String()) - } - }) - } -} - -func (s *GRPCQueryTestSuite) TestQueryValidatorCommissionGRPC() { - val := s.network.GetValidators()[0] - baseURL := val.GetAPIAddress() - - commission, err := sdk.ParseDecCoins("23.03stake") - s.Require().NoError(err) - - testCases := []struct { - name string - url string - headers map[string]string - expErr bool - respType proto.Message - expected proto.Message - }{ - { - "gRPC request params with wrong validator address", - fmt.Sprintf("%s/cosmos/distribution/v1beta1/validators/%s/commission", baseURL, "wrongAddress"), - map[string]string{}, - true, - &types.QueryValidatorCommissionResponse{}, - &types.QueryValidatorCommissionResponse{}, - }, - { - "gRPC request params valid address", - fmt.Sprintf("%s/cosmos/distribution/v1beta1/validators/%s/commission", baseURL, val.GetValAddress().String()), - map[string]string{ - grpctypes.GRPCBlockHeightHeader: "2", - }, - false, - &types.QueryValidatorCommissionResponse{}, - &types.QueryValidatorCommissionResponse{ - Commission: types.ValidatorAccumulatedCommission{ - Commission: commission, - }, - }, - }, - } - - for _, tc := range testCases { - - resp, err := sdktestutil.GetRequestWithHeaders(tc.url, tc.headers) - s.Run(tc.name, func() { - if tc.expErr { - s.Require().Error(val.GetClientCtx().Codec.UnmarshalJSON(resp, tc.respType)) - } else { - s.Require().NoError(err) - s.Require().NoError(val.GetClientCtx().Codec.UnmarshalJSON(resp, tc.respType)) - s.Require().Equal(tc.expected.String(), tc.respType.String()) - } - }) - } -} - -func (s *GRPCQueryTestSuite) TestQuerySlashesGRPC() { - val := s.network.GetValidators()[0] - baseURL := val.GetAPIAddress() - - testCases := []struct { - name string - url string - expErr bool - respType proto.Message - expected proto.Message - }{ - { - "invalid validator address", - fmt.Sprintf("%s/cosmos/distribution/v1beta1/validators/%s/slashes", baseURL, ""), - true, - &types.QueryValidatorSlashesResponse{}, - nil, - }, - { - "invalid start height", - fmt.Sprintf("%s/cosmos/distribution/v1beta1/validators/%s/slashes?starting_height=%s&ending_height=%s", baseURL, val.GetValAddress().String(), "-1", "3"), - true, - &types.QueryValidatorSlashesResponse{}, - nil, - }, - { - "invalid start height", - fmt.Sprintf("%s/cosmos/distribution/v1beta1/validators/%s/slashes?starting_height=%s&ending_height=%s", baseURL, val.GetValAddress().String(), "1", "-3"), - true, - &types.QueryValidatorSlashesResponse{}, - nil, - }, - { - "valid request get slashes", - fmt.Sprintf("%s/cosmos/distribution/v1beta1/validators/%s/slashes?starting_height=%s&ending_height=%s", baseURL, val.GetValAddress().String(), "1", "3"), - false, - &types.QueryValidatorSlashesResponse{}, - &types.QueryValidatorSlashesResponse{ - Pagination: &query.PageResponse{}, - }, - }, - } - - for _, tc := range testCases { - - resp, err := sdktestutil.GetRequest(tc.url) - - s.Run(tc.name, func() { - if tc.expErr { - s.Require().Error(val.GetClientCtx().Codec.UnmarshalJSON(resp, tc.respType)) - } else { - s.Require().NoError(err) - s.Require().NoError(val.GetClientCtx().Codec.UnmarshalJSON(resp, tc.respType)) - s.Require().Equal(tc.expected.String(), tc.respType.String()) - } - }) - } -} - -func (s *GRPCQueryTestSuite) TestQueryDelegatorRewardsGRPC() { - val := s.network.GetValidators()[0] - baseURL := val.GetAPIAddress() - - rewards, err := sdk.ParseDecCoins("23.03stake") - s.Require().NoError(err) - - testCases := []struct { - name string - url string - headers map[string]string - expErr bool - respType proto.Message - expected proto.Message - }{ - { - "wrong delegator address", - fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/rewards", baseURL, "wrongDelegatorAddress"), - map[string]string{}, - true, - &types.QueryDelegationTotalRewardsResponse{}, - nil, - }, - { - "valid request", - fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/rewards", baseURL, val.GetAddress().String()), - map[string]string{ - grpctypes.GRPCBlockHeightHeader: "2", - }, - false, - &types.QueryDelegationTotalRewardsResponse{}, - &types.QueryDelegationTotalRewardsResponse{ - Rewards: []types.DelegationDelegatorReward{ - types.NewDelegationDelegatorReward(val.GetValAddress().String(), rewards), - }, - Total: rewards, - }, - }, - { - "wrong validator address(specific validator rewards)", - fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/rewards/%s", baseURL, val.GetAddress().String(), "wrongValAddress"), - map[string]string{}, - true, - &types.QueryDelegationTotalRewardsResponse{}, - nil, - }, - { - "valid request(specific validator rewards)", - fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/rewards/%s", baseURL, val.GetAddress().String(), val.GetValAddress().String()), - map[string]string{ - grpctypes.GRPCBlockHeightHeader: "2", - }, - false, - &types.QueryDelegationRewardsResponse{}, - &types.QueryDelegationRewardsResponse{ - Rewards: rewards, - }, - }, - } - - for _, tc := range testCases { - - resp, err := sdktestutil.GetRequestWithHeaders(tc.url, tc.headers) - - s.Run(tc.name, func() { - if tc.expErr { - s.Require().Error(val.GetClientCtx().Codec.UnmarshalJSON(resp, tc.respType)) - } else { - s.Require().NoError(err) - s.Require().NoError(val.GetClientCtx().Codec.UnmarshalJSON(resp, tc.respType)) - s.Require().Equal(tc.expected.String(), tc.respType.String()) - } - }) - } -} - -func (s *GRPCQueryTestSuite) TestQueryDelegatorValidatorsGRPC() { - val := s.network.GetValidators()[0] - baseURL := val.GetAPIAddress() - - testCases := []struct { - name string - url string - expErr bool - respType proto.Message - expected proto.Message - }{ - { - "empty delegator address", - fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/validators", baseURL, ""), - true, - &types.QueryDelegatorValidatorsResponse{}, - nil, - }, - { - "wrong delegator address", - fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/validators", baseURL, "wrongDelegatorAddress"), - true, - &types.QueryDelegatorValidatorsResponse{}, - nil, - }, - { - "valid request", - fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/validators", baseURL, val.GetAddress().String()), - false, - &types.QueryDelegatorValidatorsResponse{}, - &types.QueryDelegatorValidatorsResponse{ - Validators: []string{val.GetValAddress().String()}, - }, - }, - } - - for _, tc := range testCases { - - resp, err := sdktestutil.GetRequest(tc.url) - - s.Run(tc.name, func() { - if tc.expErr { - s.Require().Error(val.GetClientCtx().Codec.UnmarshalJSON(resp, tc.respType)) - } else { - s.Require().NoError(err) - s.Require().NoError(val.GetClientCtx().Codec.UnmarshalJSON(resp, tc.respType)) - s.Require().Equal(tc.expected.String(), tc.respType.String()) - } - }) - } -} - -func (s *GRPCQueryTestSuite) TestQueryWithdrawAddressGRPC() { - val := s.network.GetValidators()[0] - baseURL := val.GetAPIAddress() - - testCases := []struct { - name string - url string - expErr bool - respType proto.Message - expected proto.Message - }{ - { - "empty delegator address", - fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/withdraw_address", baseURL, ""), - true, - &types.QueryDelegatorWithdrawAddressResponse{}, - nil, - }, - { - "wrong delegator address", - fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/withdraw_address", baseURL, "wrongDelegatorAddress"), - true, - &types.QueryDelegatorWithdrawAddressResponse{}, - nil, - }, - { - "valid request", - fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/withdraw_address", baseURL, val.GetAddress().String()), - false, - &types.QueryDelegatorWithdrawAddressResponse{}, - &types.QueryDelegatorWithdrawAddressResponse{ - WithdrawAddress: val.GetAddress().String(), - }, - }, - } - - for _, tc := range testCases { - - resp, err := sdktestutil.GetRequest(tc.url) - - s.Run(tc.name, func() { - if tc.expErr { - s.Require().Error(val.GetClientCtx().Codec.UnmarshalJSON(resp, tc.respType)) - } else { - s.Require().NoError(err) - s.Require().NoError(val.GetClientCtx().Codec.UnmarshalJSON(resp, tc.respType)) - s.Require().Equal(tc.expected.String(), tc.respType.String()) - } - }) - } -} diff --git a/tests/e2e/distribution/withdraw_all_suite.go b/tests/e2e/distribution/withdraw_all_suite.go deleted file mode 100644 index 9dd1888c046..00000000000 --- a/tests/e2e/distribution/withdraw_all_suite.go +++ /dev/null @@ -1,139 +0,0 @@ -package distribution - -import ( - "fmt" - "strings" - - "github.com/stretchr/testify/suite" - - "cosmossdk.io/math" - "cosmossdk.io/simapp" - banktypes "cosmossdk.io/x/bank/types" - "cosmossdk.io/x/distribution/client/cli" - stakingtypes "cosmossdk.io/x/staking/types" - - "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/crypto/hd" - "github.com/cosmos/cosmos-sdk/crypto/keyring" - clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" - "github.com/cosmos/cosmos-sdk/testutil/network" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -type WithdrawAllTestSuite struct { - suite.Suite - - cfg network.Config - network network.NetworkI -} - -func (s *WithdrawAllTestSuite) SetupSuite() { - cfg := network.DefaultConfig(simapp.NewTestNetworkFixture) - cfg.NumValidators = 2 - s.cfg = cfg - - s.T().Log("setting up e2e test suite") - network, err := network.New(s.T(), s.T().TempDir(), s.cfg) - s.Require().NoError(err) - s.network = network - - s.Require().NoError(s.network.WaitForNextBlock()) -} - -// TearDownSuite cleans up the current test network after _each_ test. -func (s *WithdrawAllTestSuite) TearDownSuite() { - s.T().Log("tearing down e2e test suite") - s.network.Cleanup() -} - -// This test requires multiple validators, if I add this test to `E2ETestSuite` by increasing -// `NumValidators` the existing tests are leading to non-determnism so created new suite for this test. -func (s *WithdrawAllTestSuite) TestNewWithdrawAllRewardsGenerateOnly() { - require := s.Require() - val := s.network.GetValidators()[0] - val1 := s.network.GetValidators()[1] - clientCtx := val.GetClientCtx() - - info, _, err := val.GetClientCtx().Keyring.NewMnemonic("newAccount", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1) - require.NoError(err) - - pubkey, err := info.GetPubKey() - require.NoError(err) - - newAddr := sdk.AccAddress(pubkey.Address()) - - msgSend := &banktypes.MsgSend{ - FromAddress: val.GetAddress().String(), - ToAddress: newAddr.String(), - Amount: sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, math.NewInt(2000))), - } - _, err = clitestutil.SubmitTestTx( - val.GetClientCtx(), - msgSend, - val.GetAddress(), - clitestutil.TestTxConfig{}, - ) - - require.NoError(err) - require.NoError(s.network.WaitForNextBlock()) - - // delegate 500 tokens to validator1 - msg := &stakingtypes.MsgDelegate{ - DelegatorAddress: newAddr.String(), - ValidatorAddress: val.GetValAddress().String(), - Amount: sdk.NewCoin("stake", math.NewInt(500)), - } - - _, err = clitestutil.SubmitTestTx(val.GetClientCtx(), msg, newAddr, clitestutil.TestTxConfig{}) - require.NoError(err) - require.NoError(s.network.WaitForNextBlock()) - - // delegate 500 tokens to validator2 - msg2 := &stakingtypes.MsgDelegate{ - DelegatorAddress: newAddr.String(), - ValidatorAddress: val1.GetValAddress().String(), - Amount: sdk.NewCoin("stake", math.NewInt(500)), - } - - _, err = clitestutil.SubmitTestTx(val.GetClientCtx(), msg2, newAddr, clitestutil.TestTxConfig{}) - require.NoError(err) - require.NoError(s.network.WaitForNextBlock()) - - err = s.network.RetryForBlocks(func() error { - args := []string{ - fmt.Sprintf("--%s=%s", flags.FlagFrom, newAddr.String()), - fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), - fmt.Sprintf("--%s=true", flags.FlagGenerateOnly), - fmt.Sprintf("--%s=1", cli.FlagMaxMessagesPerTx), - fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), - fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, math.NewInt(10))).String()), - } - cmd := cli.NewWithdrawAllRewardsCmd() - out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, args) - if err != nil { - return err - } - - // expect 2 transactions in the generated file when --max-msgs in a tx set 1. - txLen := len(strings.Split(strings.Trim(out.String(), "\n"), "\n")) - if txLen != 2 { - return fmt.Errorf("expected 2 transactions in the generated file, got %d", txLen) - } - return nil - }, 3) - require.NoError(err) - - args := []string{ - fmt.Sprintf("--%s=%s", flags.FlagFrom, newAddr.String()), - fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), - fmt.Sprintf("--%s=true", flags.FlagGenerateOnly), - fmt.Sprintf("--%s=2", cli.FlagMaxMessagesPerTx), - fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), - fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, math.NewInt(10))).String()), - } - cmd := cli.NewWithdrawAllRewardsCmd() - out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, args) - require.NoError(err) - // expect 1 transaction in the generated file when --max-msgs in a tx set 2, since there are only delegations. - s.Require().Equal(1, len(strings.Split(strings.Trim(out.String(), "\n"), "\n"))) -} diff --git a/tests/e2e/genutil/export_test.go b/tests/e2e/genutil/export_test.go index 0a98ef6b4e8..798885c0900 100644 --- a/tests/e2e/genutil/export_test.go +++ b/tests/e2e/genutil/export_test.go @@ -31,6 +31,7 @@ import ( "github.com/cosmos/cosmos-sdk/server/types" simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" gentestutil "github.com/cosmos/cosmos-sdk/testutil/x/genutil" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/genutil" genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" @@ -184,7 +185,7 @@ func setupApp(t *testing.T, tempDir string) (*simapp.SimApp, context.Context, ge ChainID: "theChainId", AppState: stateBytes, Consensus: &genutiltypes.ConsensusGenesis{ - Validators: nil, + Validators: []sdk.GenesisValidator{}, }, } diff --git a/tests/go.mod b/tests/go.mod index bbc2a3c3e48..7cebd970e65 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -26,7 +26,7 @@ require ( github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 google.golang.org/grpc v1.67.1 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 gotest.tools/v3 v3.5.1 pgregory.net/rapid v1.1.0 ) @@ -206,15 +206,15 @@ require ( go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.6.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/api v0.192.0 // indirect diff --git a/tests/go.sum b/tests/go.sum index b2cfe1aa475..f4a8e65b27b 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -851,8 +851,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1071,13 +1071,13 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1088,8 +1088,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1382,8 +1382,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/tests/integration/rapidgen/rapidgen.go b/tests/integration/rapidgen/rapidgen.go index e33e794b00f..0060ca1976c 100644 --- a/tests/integration/rapidgen/rapidgen.go +++ b/tests/integration/rapidgen/rapidgen.go @@ -222,7 +222,6 @@ var ( NonsignableTypes = []GeneratedType{ GenType(&authtypes.Params{}, &authapi.Params{}, GenOpts), GenType(&authtypes.BaseAccount{}, &authapi.BaseAccount{}, GenOpts.WithAnyTypes(&ed25519.PubKey{})), - GenType(&authtypes.ModuleAccount{}, &authapi.ModuleAccount{}, GenOpts.WithAnyTypes(&ed25519.PubKey{})), GenType(&authtypes.ModuleCredential{}, &authapi.ModuleCredential{}, GenOpts), GenType(&authztypes.GenericAuthorization{}, &authzapi.GenericAuthorization{}, GenOpts), @@ -260,7 +259,9 @@ var ( GenType(&slashingtypes.Params{}, &slashingapi.Params{}, GenOpts.WithDisallowNil()), - GenType(&stakingtypes.StakeAuthorization{}, &stakingapi.StakeAuthorization{}, GenOpts), + // JSON ordering of one of fields to be fixed in https://github.com/cosmos/cosmos-sdk/pull/21782 + // TODO uncomment once merged + // GenType(&stakingtypes.StakeAuthorization{}, &stakingapi.StakeAuthorization{}, GenOpts), GenType(&upgradetypes.CancelSoftwareUpgradeProposal{}, &upgradeapi.CancelSoftwareUpgradeProposal{}, GenOpts), //nolint:staticcheck // testing legacy code path GenType(&upgradetypes.SoftwareUpgradeProposal{}, &upgradeapi.SoftwareUpgradeProposal{}, GenOpts.WithDisallowNil()), //nolint:staticcheck // testing legacy code path diff --git a/tests/integration/tx/aminojson/aminojson_test.go b/tests/integration/tx/aminojson/aminojson_test.go index c17e845fe59..69ab768ff4c 100644 --- a/tests/integration/tx/aminojson/aminojson_test.go +++ b/tests/integration/tx/aminojson/aminojson_test.go @@ -1,10 +1,9 @@ package aminojson import ( - "context" + "bytes" "fmt" - "reflect" - "strings" + stdmath "math" "testing" "time" @@ -12,26 +11,12 @@ import ( gogoproto "github.com/cosmos/gogoproto/proto" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/anypb" - "google.golang.org/protobuf/types/known/durationpb" - "google.golang.org/protobuf/types/known/timestamppb" "pgregory.net/rapid" authapi "cosmossdk.io/api/cosmos/auth/v1beta1" - authzapi "cosmossdk.io/api/cosmos/authz/v1beta1" bankapi "cosmossdk.io/api/cosmos/bank/v1beta1" v1beta1 "cosmossdk.io/api/cosmos/base/v1beta1" - "cosmossdk.io/api/cosmos/crypto/ed25519" - multisigapi "cosmossdk.io/api/cosmos/crypto/multisig" - "cosmossdk.io/api/cosmos/crypto/secp256k1" - distapi "cosmossdk.io/api/cosmos/distribution/v1beta1" - gov_v1_api "cosmossdk.io/api/cosmos/gov/v1" - gov_v1beta1_api "cosmossdk.io/api/cosmos/gov/v1beta1" msgv1 "cosmossdk.io/api/cosmos/msg/v1" - slashingapi "cosmossdk.io/api/cosmos/slashing/v1beta1" - stakingapi "cosmossdk.io/api/cosmos/staking/v1beta1" - txv1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1" - vestingapi "cosmossdk.io/api/cosmos/vesting/v1beta1" "cosmossdk.io/math" authztypes "cosmossdk.io/x/authz" authzmodule "cosmossdk.io/x/authz/module" @@ -52,7 +37,6 @@ import ( "cosmossdk.io/x/staking" stakingtypes "cosmossdk.io/x/staking/types" "cosmossdk.io/x/tx/signing/aminojson" - signing_testutil "cosmossdk.io/x/tx/signing/testutil" "cosmossdk.io/x/upgrade" codectestutil "github.com/cosmos/cosmos-sdk/codec/testutil" @@ -61,17 +45,13 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" secp256k1types "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" "github.com/cosmos/cosmos-sdk/tests/integration/rapidgen" + "github.com/cosmos/cosmos-sdk/tests/integration/tx/internal" gogo_testpb "github.com/cosmos/cosmos-sdk/tests/integration/tx/internal/gogo/testpb" - pulsar_testpb "github.com/cosmos/cosmos-sdk/tests/integration/tx/internal/pulsar/testpb" "github.com/cosmos/cosmos-sdk/testutil/testdata" "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/bech32" "github.com/cosmos/cosmos-sdk/types/module/testutil" - signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" - "github.com/cosmos/cosmos-sdk/x/auth/signing" - "github.com/cosmos/cosmos-sdk/x/auth/tx" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/cosmos/cosmos-sdk/x/auth/vesting" vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" @@ -92,13 +72,23 @@ import ( // In order for step 3 to work certain restrictions on the data generated in step 1 must be enforced and are described // by the mutation of genOpts passed to the generator. func TestAminoJSON_Equivalence(t *testing.T) { - encCfg := testutil.MakeTestEncodingConfig( - codectestutil.CodecOptions{}, auth.AppModule{}, authzmodule.AppModule{}, bank.AppModule{}, - consensus.AppModule{}, distribution.AppModule{}, evidence.AppModule{}, feegrantmodule.AppModule{}, - gov.AppModule{}, groupmodule.AppModule{}, mint.AppModule{}, - slashing.AppModule{}, staking.AppModule{}, upgrade.AppModule{}, vesting.AppModule{}) - legacytx.RegressionTestingAminoCodec = encCfg.Amino - aj := aminojson.NewEncoder(aminojson.EncoderOptions{DoNotSortFields: true}) + fixture := internal.NewSigningFixture(t, internal.SigningFixtureOptions{}, + auth.AppModule{}, + authzmodule.AppModule{}, + bank.AppModule{}, + consensus.AppModule{}, + distribution.AppModule{}, + evidence.AppModule{}, + feegrantmodule.AppModule{}, + gov.AppModule{}, + groupmodule.AppModule{}, + mint.AppModule{}, + slashing.AppModule{}, + staking.AppModule{}, + upgrade.AppModule{}, + vesting.AppModule{}, + ) + aj := aminojson.NewEncoder(aminojson.EncoderOptions{}) for _, tt := range rapidgen.DefaultGeneratedTypes { desc := tt.Pulsar.ProtoReflect().Descriptor() @@ -106,7 +96,7 @@ func TestAminoJSON_Equivalence(t *testing.T) { t.Run(name, func(t *testing.T) { gen := rapidproto.MessageGenerator(tt.Pulsar, tt.Opts) fmt.Printf("testing %s\n", tt.Pulsar.ProtoReflect().Descriptor().FullName()) - rapid.Check(t, func(t *rapid.T) { + rapid.Check(t, func(r *rapid.T) { // uncomment to debug; catch a panic and inspect application state // defer func() { // if r := recover(); r != nil { @@ -115,39 +105,28 @@ func TestAminoJSON_Equivalence(t *testing.T) { // } // }() - msg := gen.Draw(t, "msg") + msg := gen.Draw(r, "msg") postFixPulsarMessage(msg) - // txBuilder.GetTx will fail if the msg has no signers - // so it does not make sense to run these cases, apparently. - signers, err := encCfg.TxConfig.SigningContext().GetSigners(msg) - if len(signers) == 0 { - // skip - return - } - if err != nil { - if strings.Contains(err.Error(), "empty address string is not allowed") { - return - } - require.NoError(t, err) - } gogo := tt.Gogo sanity := tt.Pulsar protoBz, err := proto.Marshal(msg) - require.NoError(t, err) + require.NoError(r, err) err = proto.Unmarshal(protoBz, sanity) - require.NoError(t, err) + require.NoError(r, err) - err = encCfg.Codec.Unmarshal(protoBz, gogo) - require.NoError(t, err) + err = fixture.UnmarshalGogoProto(protoBz, gogo) + require.NoError(r, err) - legacyAminoJSON, err := encCfg.Amino.MarshalJSON(gogo) - require.NoError(t, err) + legacyAminoJSON := fixture.MarshalLegacyAminoJSON(t, gogo) aminoJSON, err := aj.Marshal(msg) - require.NoError(t, err) - require.Equal(t, string(legacyAminoJSON), string(aminoJSON)) + require.NoError(r, err) + if !bytes.Equal(legacyAminoJSON, aminoJSON) { + require.Failf(r, "JSON mismatch", "legacy: %s\n x/tx: %s\n", + string(legacyAminoJSON), string(aminoJSON)) + } // test amino json signer handler equivalence if !proto.HasExtension(desc.Options(), msgv1.E_Signer) { @@ -155,368 +134,245 @@ func TestAminoJSON_Equivalence(t *testing.T) { return } - handlerOptions := signing_testutil.HandlerArgumentOptions{ - ChainID: "test-chain", - Memo: "sometestmemo", - Msg: tt.Pulsar, - AccNum: 1, - AccSeq: 2, - SignerAddress: "signerAddress", - Fee: &txv1beta1.Fee{ - Amount: []*v1beta1.Coin{{Denom: "uatom", Amount: "1000"}}, - }, - } - - signerData, txData, err := signing_testutil.MakeHandlerArguments(handlerOptions) - require.NoError(t, err) - - handler := aminojson.NewSignModeHandler(aminojson.SignModeHandlerOptions{}) - signBz, err := handler.GetSignBytes(context.Background(), signerData, txData) - require.NoError(t, err) - - legacyHandler := tx.NewSignModeLegacyAminoJSONHandler() - txBuilder := encCfg.TxConfig.NewTxBuilder() - require.NoError(t, txBuilder.SetMsgs([]types.Msg{tt.Gogo}...)) - txBuilder.SetMemo(handlerOptions.Memo) - txBuilder.SetFeeAmount(types.Coins{types.NewInt64Coin("uatom", 1000)}) - theTx := txBuilder.GetTx() - - legacySigningData := signing.SignerData{ - ChainID: handlerOptions.ChainID, - Address: handlerOptions.SignerAddress, - AccountNumber: handlerOptions.AccNum, - Sequence: handlerOptions.AccSeq, - } - legacySignBz, err := legacyHandler.GetSignBytes(signingtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, - legacySigningData, theTx) - require.NoError(t, err) - require.Equal(t, string(legacySignBz), string(signBz)) + fixture.RequireLegacyAminoEquivalent(t, gogo) }) }) } } -func newAny(t *testing.T, msg proto.Message) *anypb.Any { - t.Helper() - bz, err := proto.Marshal(msg) - require.NoError(t, err) - typeName := fmt.Sprintf("/%s", msg.ProtoReflect().Descriptor().FullName()) - return &anypb.Any{ - TypeUrl: typeName, - Value: bz, - } -} - // TestAminoJSON_LegacyParity tests that the Encoder encoder produces the same output as the Encoder encoder. func TestAminoJSON_LegacyParity(t *testing.T) { - encCfg := testutil.MakeTestEncodingConfig(codectestutil.CodecOptions{}, auth.AppModule{}, authzmodule.AppModule{}, + fixture := internal.NewSigningFixture(t, internal.SigningFixtureOptions{}, + auth.AppModule{}, authzmodule.AppModule{}, bank.AppModule{}, distribution.AppModule{}, slashing.AppModule{}, staking.AppModule{}, vesting.AppModule{}, gov.AppModule{}) - legacytx.RegressionTestingAminoCodec = encCfg.Amino + aj := aminojson.NewEncoder(aminojson.EncoderOptions{}) - aj := aminojson.NewEncoder(aminojson.EncoderOptions{DoNotSortFields: true}) addr1 := types.AccAddress("addr1") now := time.Now() - genericAuth, _ := codectypes.NewAnyWithValue(&authztypes.GenericAuthorization{Msg: "foo"}) - genericAuthPulsar := newAny(t, &authzapi.GenericAuthorization{Msg: "foo"}) pubkeyAny, _ := codectypes.NewAnyWithValue(&secp256k1types.PubKey{Key: []byte("foo")}) - pubkeyAnyPulsar := newAny(t, &secp256k1.PubKey{Key: []byte("foo")}) - dec10bz, _ := math.LegacyNewDec(10).Marshal() - int123bz, _ := math.NewInt(123).Marshal() + dec5point4 := math.LegacyMustNewDecFromStr("5.4") + failingBaseAccount := authtypes.NewBaseAccountWithAddress(addr1) + failingBaseAccount.AccountNumber = stdmath.MaxUint64 cases := map[string]struct { - gogo gogoproto.Message - pulsar proto.Message - pulsarMarshalFails bool - - // this will fail in cases where a lossy encoding of an empty array to protobuf occurs. the unmarshalled bytes - // represent the array as nil, and a subsequent marshal to JSON represent the array as null instead of empty. - roundTripUnequal bool - - // pulsar does not support marshaling a math.Dec as anything except a string. Therefore, we cannot unmarshal - // a pulsar encoded Math.dec (the string representation of a Decimal) into a gogo Math.dec (expecting an int64). - protoUnmarshalFails bool + gogo gogoproto.Message + fails bool }{ - "auth/params": {gogo: &authtypes.Params{TxSigLimit: 10}, pulsar: &authapi.Params{TxSigLimit: 10}}, - "auth/module_account": { + "auth/params": { + gogo: &authtypes.Params{TxSigLimit: 10}, + }, + "auth/module_account_nil_permissions": { gogo: &authtypes.ModuleAccount{ - BaseAccount: authtypes.NewBaseAccountWithAddress(addr1), Permissions: []string{}, + BaseAccount: authtypes.NewBaseAccountWithAddress( + addr1, + ), }, - pulsar: &authapi.ModuleAccount{ - BaseAccount: &authapi.BaseAccount{Address: addr1.String()}, Permissions: []string{}, + }, + "auth/module_account/max_uint64": { + gogo: &authtypes.ModuleAccount{ + BaseAccount: failingBaseAccount, }, - roundTripUnequal: true, + fails: true, + }, + "auth/module_account_empty_permissions": { + gogo: &authtypes.ModuleAccount{ + BaseAccount: authtypes.NewBaseAccountWithAddress( + addr1, + ), + // empty set and nil are indistinguishable from the protoreflect API since they both + // marshal to zero proto bytes, there empty set is not supported. + Permissions: []string{}, + }, + fails: true, }, "auth/base_account": { - gogo: &authtypes.BaseAccount{Address: addr1.String(), PubKey: pubkeyAny}, - pulsar: &authapi.BaseAccount{Address: addr1.String(), PubKey: pubkeyAnyPulsar}, + gogo: &authtypes.BaseAccount{Address: addr1.String(), PubKey: pubkeyAny, AccountNumber: 1, Sequence: 2}, }, "authz/msg_grant": { gogo: &authztypes.MsgGrant{ + Granter: addr1.String(), Grantee: addr1.String(), Grant: authztypes.Grant{Expiration: &now, Authorization: genericAuth}, }, - pulsar: &authzapi.MsgGrant{ - Grant: &authzapi.Grant{Expiration: timestamppb.New(now), Authorization: genericAuthPulsar}, - }, }, "authz/msg_update_params": { - gogo: &authtypes.MsgUpdateParams{Params: authtypes.Params{TxSigLimit: 10}}, - pulsar: &authapi.MsgUpdateParams{Params: &authapi.Params{TxSigLimit: 10}}, + gogo: &authtypes.MsgUpdateParams{Params: authtypes.Params{TxSigLimit: 10}}, }, "authz/msg_exec/empty_msgs": { - gogo: &authztypes.MsgExec{Msgs: []*codectypes.Any{}}, - pulsar: &authzapi.MsgExec{Msgs: []*anypb.Any{}}, + gogo: &authztypes.MsgExec{Msgs: []*codectypes.Any{}}, }, "distribution/delegator_starting_info": { - gogo: &disttypes.DelegatorStartingInfo{}, - pulsar: &distapi.DelegatorStartingInfo{}, + gogo: &disttypes.DelegatorStartingInfo{Stake: math.LegacyNewDec(10)}, }, "distribution/delegator_starting_info/non_zero_dec": { - gogo: &disttypes.DelegatorStartingInfo{Stake: math.LegacyNewDec(10)}, - pulsar: &distapi.DelegatorStartingInfo{Stake: "10.000000000000000000"}, - protoUnmarshalFails: true, + gogo: &disttypes.DelegatorStartingInfo{Stake: math.LegacyNewDec(10)}, }, "distribution/delegation_delegator_reward": { - gogo: &disttypes.DelegationDelegatorReward{}, - pulsar: &distapi.DelegationDelegatorReward{}, + gogo: &disttypes.DelegationDelegatorReward{}, }, "distribution/msg_withdraw_delegator_reward": { - gogo: &disttypes.MsgWithdrawDelegatorReward{DelegatorAddress: "foo"}, - pulsar: &distapi.MsgWithdrawDelegatorReward{DelegatorAddress: "foo"}, + gogo: &disttypes.MsgWithdrawDelegatorReward{DelegatorAddress: "foo"}, }, "crypto/ed25519": { - gogo: &ed25519types.PubKey{Key: []byte("key")}, - pulsar: &ed25519.PubKey{Key: []byte("key")}, + gogo: &ed25519types.PubKey{Key: []byte("key")}, }, "crypto/secp256k1": { - gogo: &secp256k1types.PubKey{Key: []byte("key")}, - pulsar: &secp256k1.PubKey{Key: []byte("key")}, + gogo: &secp256k1types.PubKey{Key: []byte("key")}, }, "crypto/legacy_amino_pubkey": { - gogo: &multisig.LegacyAminoPubKey{PubKeys: []*codectypes.Any{pubkeyAny}}, - pulsar: &multisigapi.LegacyAminoPubKey{PublicKeys: []*anypb.Any{pubkeyAnyPulsar}}, + gogo: &multisig.LegacyAminoPubKey{PubKeys: []*codectypes.Any{pubkeyAny}}, }, - "crypto/legacy_amino_pubkey/empty": { - gogo: &multisig.LegacyAminoPubKey{}, - pulsar: &multisigapi.LegacyAminoPubKey{}, + "crypto/legacy_amino_pubkey_empty": { + gogo: &multisig.LegacyAminoPubKey{}, }, "consensus/evidence_params/duration": { - gogo: &gov_v1beta1_types.VotingParams{VotingPeriod: 1e9 + 7}, - pulsar: &gov_v1beta1_api.VotingParams{VotingPeriod: &durationpb.Duration{Seconds: 1, Nanos: 7}}, + gogo: &gov_v1beta1_types.VotingParams{VotingPeriod: 1e9 + 7}, }, "consensus/evidence_params/big_duration": { - gogo: &gov_v1beta1_types.VotingParams{VotingPeriod: time.Duration(rapidproto.MaxDurationSeconds*1e9) + 999999999}, - pulsar: &gov_v1beta1_api.VotingParams{VotingPeriod: &durationpb.Duration{ - Seconds: rapidproto.MaxDurationSeconds, Nanos: 999999999, - }}, + gogo: &gov_v1beta1_types.VotingParams{ + VotingPeriod: time.Duration(rapidproto.MaxDurationSeconds*1e9) + 999999999, + }, }, "consensus/evidence_params/too_big_duration": { - gogo: &gov_v1beta1_types.VotingParams{VotingPeriod: time.Duration(rapidproto.MaxDurationSeconds*1e9) + 999999999}, - pulsar: &gov_v1beta1_api.VotingParams{VotingPeriod: &durationpb.Duration{ - Seconds: rapidproto.MaxDurationSeconds + 1, Nanos: 999999999, - }}, - pulsarMarshalFails: true, + gogo: &gov_v1beta1_types.VotingParams{ + VotingPeriod: time.Duration(rapidproto.MaxDurationSeconds*1e9) + 999999999, + }, }, // amino.dont_omitempty + empty/nil lists produce some surprising results "bank/send_authorization/empty_coins": { - gogo: &banktypes.SendAuthorization{SpendLimit: []types.Coin{}}, - pulsar: &bankapi.SendAuthorization{SpendLimit: []*v1beta1.Coin{}}, + gogo: &banktypes.SendAuthorization{SpendLimit: []types.Coin{}}, }, "bank/send_authorization/nil_coins": { - gogo: &banktypes.SendAuthorization{SpendLimit: nil}, - pulsar: &bankapi.SendAuthorization{SpendLimit: nil}, + gogo: &banktypes.SendAuthorization{SpendLimit: nil}, }, "bank/send_authorization/empty_list": { - gogo: &banktypes.SendAuthorization{AllowList: []string{}}, - pulsar: &bankapi.SendAuthorization{AllowList: []string{}}, + gogo: &banktypes.SendAuthorization{AllowList: []string{}}, }, "bank/send_authorization/nil_list": { - gogo: &banktypes.SendAuthorization{AllowList: nil}, - pulsar: &bankapi.SendAuthorization{AllowList: nil}, + gogo: &banktypes.SendAuthorization{AllowList: nil}, }, "bank/msg_multi_send/nil_everything": { - gogo: &banktypes.MsgMultiSend{}, - pulsar: &bankapi.MsgMultiSend{}, + gogo: &banktypes.MsgMultiSend{}, }, "gov/v1_msg_submit_proposal": { - gogo: &gov_v1_types.MsgSubmitProposal{}, - pulsar: &gov_v1_api.MsgSubmitProposal{}, + gogo: &gov_v1_types.MsgSubmitProposal{}, }, - "slashing/params/empty_dec": { - gogo: &slashingtypes.Params{DowntimeJailDuration: 1e9 + 7}, - pulsar: &slashingapi.Params{DowntimeJailDuration: &durationpb.Duration{Seconds: 1, Nanos: 7}}, + "gov/v1_params": { + gogo: &gov_v1_types.Params{ + Quorum: math.LegacyMustNewDecFromStr("0.33").String(), + }, }, - // This test cases demonstrates the expected contract and proper way to set a cosmos.Dec field represented - // as bytes in protobuf message, namely: - // dec10bz, _ := types.NewDec(10).Marshal() "slashing/params/dec": { gogo: &slashingtypes.Params{ - DowntimeJailDuration: 1e9 + 7, - MinSignedPerWindow: math.LegacyNewDec(10), - }, - pulsar: &slashingapi.Params{ - DowntimeJailDuration: &durationpb.Duration{Seconds: 1, Nanos: 7}, - MinSignedPerWindow: dec10bz, + DowntimeJailDuration: 1e9 + 7, + MinSignedPerWindow: math.LegacyNewDec(10), + SlashFractionDoubleSign: math.LegacyZeroDec(), + SlashFractionDowntime: math.LegacyZeroDec(), }, }, "staking/msg_update_params": { gogo: &stakingtypes.MsgUpdateParams{ Params: stakingtypes.Params{ - UnbondingTime: 0, - KeyRotationFee: types.Coin{}, - }, - }, - pulsar: &stakingapi.MsgUpdateParams{ - Params: &stakingapi.Params{ - UnbondingTime: &durationpb.Duration{Seconds: 0}, - KeyRotationFee: &v1beta1.Coin{}, + UnbondingTime: 0, + KeyRotationFee: types.Coin{}, + MinCommissionRate: math.LegacyZeroDec(), }, }, }, "staking/create_validator": { - gogo: &stakingtypes.MsgCreateValidator{Pubkey: pubkeyAny}, - pulsar: &stakingapi.MsgCreateValidator{ - Pubkey: pubkeyAnyPulsar, - Description: &stakingapi.Description{ - Metadata: &stakingapi.Metadata{}, + gogo: &stakingtypes.MsgCreateValidator{ + Pubkey: pubkeyAny, + Commission: stakingtypes.CommissionRates{ + Rate: dec5point4, + MaxRate: math.LegacyZeroDec(), + MaxChangeRate: math.LegacyZeroDec(), }, - Commission: &stakingapi.CommissionRates{}, - Value: &v1beta1.Coin{}, + MinSelfDelegation: math.NewIntFromUint64(10), }, }, "staking/msg_cancel_unbonding_delegation_response": { - gogo: &stakingtypes.MsgCancelUnbondingDelegationResponse{}, - pulsar: &stakingapi.MsgCancelUnbondingDelegationResponse{}, + gogo: &stakingtypes.MsgCancelUnbondingDelegationResponse{}, }, "staking/stake_authorization_empty": { - gogo: &stakingtypes.StakeAuthorization{}, - pulsar: &stakingapi.StakeAuthorization{}, + gogo: &stakingtypes.StakeAuthorization{}, }, "staking/stake_authorization_allow": { gogo: &stakingtypes.StakeAuthorization{ + MaxTokens: &types.Coin{Denom: "foo", Amount: math.NewInt(123)}, Validators: &stakingtypes.StakeAuthorization_AllowList{ - AllowList: &stakingtypes.StakeAuthorization_Validators{Address: []string{"foo"}}, + AllowList: &stakingtypes.StakeAuthorization_Validators{ + Address: []string{"foo"}, + }, }, + AuthorizationType: stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE, }, - pulsar: &stakingapi.StakeAuthorization{ - Validators: &stakingapi.StakeAuthorization_AllowList{ - AllowList: &stakingapi.StakeAuthorization_Validators{Address: []string{"foo"}}, + }, + "staking/stake_authorization_deny": { + gogo: &stakingtypes.StakeAuthorization{ + MaxTokens: &types.Coin{Denom: "foo", Amount: math.NewInt(123)}, + Validators: &stakingtypes.StakeAuthorization_DenyList{ + DenyList: &stakingtypes.StakeAuthorization_Validators{}, }, + AuthorizationType: stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE, }, + // to be fixed in https://github.com/cosmos/cosmos-sdk/pull/21782 + // TODO remove once merged + fails: true, }, "vesting/base_account_empty": { - gogo: &vestingtypes.BaseVestingAccount{BaseAccount: &authtypes.BaseAccount{}}, - pulsar: &vestingapi.BaseVestingAccount{BaseAccount: &authapi.BaseAccount{}}, + gogo: &vestingtypes.BaseVestingAccount{BaseAccount: &authtypes.BaseAccount{}}, }, "vesting/base_account_pubkey": { - gogo: &vestingtypes.BaseVestingAccount{BaseAccount: &authtypes.BaseAccount{PubKey: pubkeyAny}}, - pulsar: &vestingapi.BaseVestingAccount{BaseAccount: &authapi.BaseAccount{PubKey: pubkeyAnyPulsar}}, + gogo: &vestingtypes.BaseVestingAccount{ + BaseAccount: &authtypes.BaseAccount{PubKey: pubkeyAny}, + }, }, "math/int_as_string": { - gogo: &gogo_testpb.IntAsString{IntAsString: math.NewInt(123)}, - pulsar: &pulsar_testpb.IntAsString{IntAsString: "123"}, + gogo: &gogo_testpb.IntAsString{IntAsString: math.NewInt(123)}, }, "math/int_as_string/empty": { - gogo: &gogo_testpb.IntAsString{}, - pulsar: &pulsar_testpb.IntAsString{}, + gogo: &gogo_testpb.IntAsString{}, }, "math/int_as_bytes": { - gogo: &gogo_testpb.IntAsBytes{IntAsBytes: math.NewInt(123)}, - pulsar: &pulsar_testpb.IntAsBytes{IntAsBytes: int123bz}, + gogo: &gogo_testpb.IntAsBytes{IntAsBytes: math.NewInt(123)}, }, "math/int_as_bytes/empty": { - gogo: &gogo_testpb.IntAsBytes{}, - pulsar: &pulsar_testpb.IntAsBytes{}, + gogo: &gogo_testpb.IntAsBytes{}, }, } for name, tc := range cases { t.Run(name, func(t *testing.T) { - gogoBytes, err := encCfg.Amino.MarshalJSON(tc.gogo) + legacyBytes := fixture.MarshalLegacyAminoJSON(t, tc.gogo) + dynamicBytes, err := aj.Marshal(fixture.DynamicMessage(t, tc.gogo)) require.NoError(t, err) - pulsarBytes, err := aj.Marshal(tc.pulsar) - if tc.pulsarMarshalFails { - require.Error(t, err) + t.Logf("legacy: %s\n", string(legacyBytes)) + t.Logf(" sut: %s\n", string(dynamicBytes)) + if tc.fails { + require.NotEqual(t, string(legacyBytes), string(dynamicBytes)) return } - require.NoError(t, err) - - fmt.Printf("pulsar: %s\n", string(pulsarBytes)) - fmt.Printf(" gogo: %s\n", string(gogoBytes)) - require.Equal(t, string(gogoBytes), string(pulsarBytes)) - - pulsarProtoBytes, err := proto.Marshal(tc.pulsar) - require.NoError(t, err) - - gogoType := reflect.TypeOf(tc.gogo).Elem() - newGogo := reflect.New(gogoType).Interface().(gogoproto.Message) - - err = encCfg.Codec.Unmarshal(pulsarProtoBytes, newGogo) - if tc.protoUnmarshalFails { - require.Error(t, err) - return - } - require.NoError(t, err) - - newGogoBytes, err := encCfg.Amino.MarshalJSON(newGogo) - require.NoError(t, err) - if tc.roundTripUnequal { - require.NotEqual(t, string(gogoBytes), string(newGogoBytes)) - return - } - require.Equal(t, string(gogoBytes), string(newGogoBytes)) + require.Equal(t, string(legacyBytes), string(dynamicBytes)) // test amino json signer handler equivalence - msg, ok := tc.gogo.(legacytx.LegacyMsg) - if !ok { + if !proto.HasExtension(fixture.MessageDescriptor(t, tc.gogo).Options(), msgv1.E_Signer) { // not signable return } - - handlerOptions := signing_testutil.HandlerArgumentOptions{ - ChainID: "test-chain", - Memo: "sometestmemo", - Msg: tc.pulsar, - AccNum: 1, - AccSeq: 2, - SignerAddress: "signerAddress", - Fee: &txv1beta1.Fee{ - Amount: []*v1beta1.Coin{{Denom: "uatom", Amount: "1000"}}, - }, - } - - signerData, txData, err := signing_testutil.MakeHandlerArguments(handlerOptions) - require.NoError(t, err) - - handler := aminojson.NewSignModeHandler(aminojson.SignModeHandlerOptions{}) - signBz, err := handler.GetSignBytes(context.Background(), signerData, txData) - require.NoError(t, err) - - legacyHandler := tx.NewSignModeLegacyAminoJSONHandler() - txBuilder := encCfg.TxConfig.NewTxBuilder() - require.NoError(t, txBuilder.SetMsgs([]types.Msg{msg}...)) - txBuilder.SetMemo(handlerOptions.Memo) - txBuilder.SetFeeAmount(types.Coins{types.NewInt64Coin("uatom", 1000)}) - theTx := txBuilder.GetTx() - - legacySigningData := signing.SignerData{ - ChainID: handlerOptions.ChainID, - Address: handlerOptions.SignerAddress, - AccountNumber: handlerOptions.AccNum, - Sequence: handlerOptions.AccSeq, - } - legacySignBz, err := legacyHandler.GetSignBytes(signingtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, - legacySigningData, theTx) - require.NoError(t, err) - require.Equal(t, string(legacySignBz), string(signBz)) + fixture.RequireLegacyAminoEquivalent(t, tc.gogo) }) } } func TestSendAuthorization(t *testing.T) { - encCfg := testutil.MakeTestEncodingConfig(codectestutil.CodecOptions{}, auth.AppModule{}, authzmodule.AppModule{}, - distribution.AppModule{}, bank.AppModule{}) + encCfg := testutil.MakeTestEncodingConfig( + codectestutil.CodecOptions{}, + auth.AppModule{}, + authzmodule.AppModule{}, + distribution.AppModule{}, + bank.AppModule{}, + ) aj := aminojson.NewEncoder(aminojson.EncoderOptions{}) diff --git a/tests/integration/tx/context_test.go b/tests/integration/tx/context_test.go index ffe21c2053c..eb6ce325016 100644 --- a/tests/integration/tx/context_test.go +++ b/tests/integration/tx/context_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/stretchr/testify/require" - "google.golang.org/protobuf/proto" "cosmossdk.io/depinject" "cosmossdk.io/log" @@ -12,21 +11,14 @@ import ( "cosmossdk.io/x/tx/signing" codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/tests/integration/tx/internal" "github.com/cosmos/cosmos-sdk/tests/integration/tx/internal/pulsar/testpb" "github.com/cosmos/cosmos-sdk/testutil/configurator" simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" ) -func ProvideCustomGetSigners() signing.CustomGetSigner { - return signing.CustomGetSigner{ - MsgType: proto.MessageName(&testpb.TestRepeatedFields{}), - Fn: func(msg proto.Message) ([][]byte, error) { - testMsg := msg.(*testpb.TestRepeatedFields) - // arbitrary logic - signer := testMsg.NullableDontOmitempty[1].Value - return [][]byte{[]byte(signer)}, nil - }, - } +func ProvideCustomGetSigner() signing.CustomGetSigner { + return internal.TestRepeatedFieldsSigner } func TestDefineCustomGetSigners(t *testing.T) { @@ -41,7 +33,7 @@ func TestDefineCustomGetSigners(t *testing.T) { configurator.ConsensusModule(), ), depinject.Supply(log.NewNopLogger()), - depinject.Provide(ProvideCustomGetSigners), + depinject.Provide(ProvideCustomGetSigner), ), &interfaceRegistry, ) diff --git a/tests/integration/tx/internal/util.go b/tests/integration/tx/internal/util.go new file mode 100644 index 00000000000..d2e654a5881 --- /dev/null +++ b/tests/integration/tx/internal/util.go @@ -0,0 +1,221 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "testing" + + gogoproto "github.com/cosmos/gogoproto/proto" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/dynamicpb" + + "cosmossdk.io/core/transaction" + "cosmossdk.io/x/tx/signing" + "cosmossdk.io/x/tx/signing/aminojson" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/std" + "github.com/cosmos/cosmos-sdk/tests/integration/tx/internal/pulsar/testpb" + "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + "github.com/cosmos/cosmos-sdk/x/auth/tx" +) + +var TestRepeatedFieldsSigner = signing.CustomGetSigner{ + MsgType: proto.MessageName(&testpb.TestRepeatedFields{}), + Fn: func(msg proto.Message) ([][]byte, error) { + testMsg := msg.(*testpb.TestRepeatedFields) + // arbitrary logic + signer := testMsg.NullableDontOmitempty[1].Value + return [][]byte{[]byte(signer)}, nil + }, +} + +type noOpAddressCodec struct{} + +func (a noOpAddressCodec) StringToBytes(text string) ([]byte, error) { + return []byte(text), nil +} + +func (a noOpAddressCodec) BytesToString(bz []byte) (string, error) { + return string(bz), nil +} + +type SigningFixture struct { + txConfig client.TxConfig + legacy *codec.LegacyAmino + protoCodec *codec.ProtoCodec + options SigningFixtureOptions + registry codectypes.InterfaceRegistry +} + +type SigningFixtureOptions struct { + DoNotSortFields bool +} + +func NewSigningFixture( + t *testing.T, + options SigningFixtureOptions, + modules ...module.AppModule, +) *SigningFixture { + t.Helper() + // set up transaction and signing infra + addressCodec, valAddressCodec := noOpAddressCodec{}, noOpAddressCodec{} + customGetSigners := []signing.CustomGetSigner{TestRepeatedFieldsSigner} + interfaceRegistry, _, err := codec.ProvideInterfaceRegistry( + addressCodec, + valAddressCodec, + customGetSigners, + ) + require.NoError(t, err) + protoCodec := codec.ProvideProtoCodec(interfaceRegistry) + signingOptions := &signing.Options{ + FileResolver: interfaceRegistry, + AddressCodec: addressCodec, + ValidatorAddressCodec: valAddressCodec, + } + for _, customGetSigner := range customGetSigners { + signingOptions.DefineCustomGetSigners(customGetSigner.MsgType, customGetSigner.Fn) + } + txConfig, err := tx.NewTxConfigWithOptions( + protoCodec, + tx.ConfigOptions{ + EnabledSignModes: []signingtypes.SignMode{ + signingtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, + }, + SigningOptions: signingOptions, + }) + require.NoError(t, err) + + legacyAminoCodec := codec.NewLegacyAmino() + mb := module.NewManager(modules...) + std.RegisterLegacyAminoCodec(legacyAminoCodec) + std.RegisterInterfaces(interfaceRegistry) + mb.RegisterLegacyAminoCodec(legacyAminoCodec) + mb.RegisterInterfaces(interfaceRegistry) + + return &SigningFixture{ + txConfig: txConfig, + legacy: legacyAminoCodec, + options: options, + protoCodec: protoCodec, + registry: interfaceRegistry, + } +} + +func (s *SigningFixture) RequireLegacyAminoEquivalent(t *testing.T, msg transaction.Msg) { + t.Helper() + // create tx envelope + txBuilder := s.txConfig.NewTxBuilder() + err := txBuilder.SetMsgs([]types.Msg{msg}...) + require.NoError(t, err) + builtTx := txBuilder.GetTx() + + // round trip it to simulate application usage + txBz, err := s.txConfig.TxEncoder()(builtTx) + require.NoError(t, err) + theTx, err := s.txConfig.TxDecoder()(txBz) + require.NoError(t, err) + + // create signing envelope + signerData := signing.SignerData{ + Address: "sender-address", + ChainID: "test-chain", + AccountNumber: 0, + Sequence: 0, + } + adaptableTx, ok := theTx.(authsigning.V2AdaptableTx) + require.True(t, ok) + + legacytx.RegressionTestingAminoCodec = s.legacy + defer func() { + legacytx.RegressionTestingAminoCodec = nil + }() + legacyAminoSignHandler := tx.NewSignModeLegacyAminoJSONHandler() + legacyBz, err := legacyAminoSignHandler.GetSignBytes( + signingtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, + authsigning.SignerData{ + ChainID: signerData.ChainID, + Address: signerData.Address, + AccountNumber: signerData.AccountNumber, + Sequence: signerData.Sequence, + }, + theTx) + require.NoError(t, err) + + handler := aminojson.NewSignModeHandler(aminojson.SignModeHandlerOptions{}) + signBz, err := handler.GetSignBytes( + context.Background(), + signerData, + adaptableTx.GetSigningTxData(), + ) + require.NoError(t, err) + + require.Truef(t, + bytes.Equal(legacyBz, signBz), + "legacy: %s\n x/tx: %s", string(legacyBz), string(signBz)) +} + +func (s *SigningFixture) MarshalLegacyAminoJSON(t *testing.T, o any) []byte { + t.Helper() + bz, err := s.legacy.MarshalJSON(o) + require.NoError(t, err) + if s.options.DoNotSortFields { + return bz + } + sortedBz, err := sortJson(bz) + require.NoError(t, err) + return sortedBz +} + +func (s *SigningFixture) UnmarshalGogoProto(bz []byte, ptr transaction.Msg) error { + return s.protoCodec.Unmarshal(bz, ptr) +} + +func (s *SigningFixture) MessageDescriptor(t *testing.T, msg transaction.Msg) protoreflect.MessageDescriptor { + t.Helper() + typeName := gogoproto.MessageName(msg) + msgDesc, err := s.registry.FindDescriptorByName(protoreflect.FullName(typeName)) + require.NoError(t, err) + return msgDesc.(protoreflect.MessageDescriptor) +} + +// DynamicMessage is identical to the Decoder implementation in +// https://github.com/cosmos/cosmos-sdk/blob/6d2f6ff068c81c5783e01319beaa51c7dbb43edd/x/tx/decode/decode.go#L136 +// It is duplicated here to test dynamic message implementations specifically. +// The code path linked above is also covered in this package. +func (s *SigningFixture) DynamicMessage(t *testing.T, msg transaction.Msg) proto.Message { + t.Helper() + msgDesc := s.MessageDescriptor(t, msg) + protoBz, err := gogoproto.Marshal(msg) + require.NoError(t, err) + dynamicMsg := dynamicpb.NewMessageType(msgDesc).New().Interface() + err = proto.Unmarshal(protoBz, dynamicMsg) + require.NoError(t, err) + return dynamicMsg +} + +// sortJson sorts the JSON bytes by way of the side effect of unmarshalling and remarshalling +// the JSON using encoding/json. This hacky way of sorting JSON fields was used by the legacy +// amino JSON encoding x/auth/migrations/legacytx.StdSignBytes. It is used here ensure the x/tx +// JSON encoding is equivalent to the legacy amino JSON encoding. +func sortJson(bz []byte) ([]byte, error) { + var c any + err := json.Unmarshal(bz, &c) + if err != nil { + return nil, err + } + js, err := json.Marshal(c) + if err != nil { + return nil, err + } + return js, nil +} diff --git a/tests/systemtests/authz_test.go b/tests/systemtests/authz_test.go index c411ad20d36..f3e0ec748c8 100644 --- a/tests/systemtests/authz_test.go +++ b/tests/systemtests/authz_test.go @@ -4,6 +4,7 @@ package systemtests import ( "fmt" + "net/http" "os" "testing" "time" @@ -606,7 +607,7 @@ func TestAuthzExecRedelegateAuthorization(t *testing.T) { msgRedelegateTypeURL, granterAddr, val1Addr, val2Addr, testDenom, redelegationAmount) execMsg := WriteToTempJSONFile(t, redelegateTx) - redelegateCmd := []string{"tx", "authz", "exec", execMsg.Name(), "--from=" + granteeAddr, "--gas=auto"} + redelegateCmd := []string{"tx", "authz", "exec", execMsg.Name(), "--from=" + granteeAddr, "--gas=500000", "--fees=10stake"} rsp = cli.RunAndWait(redelegateCmd...) RequireTxSuccess(t, rsp) @@ -670,73 +671,84 @@ func TestAuthzGRPCQueries(t *testing.T) { invalidMsgTypeOutput := `{"code":2, "message":"codespace authz code 2: authorization not found: authorization not found for invalidMsg type", "details":[]}` expGrantOutput := fmt.Sprintf(`{"grants":[{%s}],"pagination":null}`, grant1) - grantTestCases := []GRPCTestCase{ + grantTestCases := []RestTestCase{ { "invalid granter address", fmt.Sprintf(grantURL, "invalid_granter", grantee1Addr, msgSendTypeURL), + http.StatusInternalServerError, bech32FailOutput, }, { "invalid grantee address", fmt.Sprintf(grantURL, granterAddr, "invalid_grantee", msgSendTypeURL), + http.StatusInternalServerError, bech32FailOutput, }, { "with empty granter", fmt.Sprintf(grantURL, "", grantee1Addr, msgSendTypeURL), + http.StatusInternalServerError, emptyStrOutput, }, { "with empty grantee", fmt.Sprintf(grantURL, granterAddr, "", msgSendTypeURL), + http.StatusInternalServerError, emptyStrOutput, }, { "invalid msg-type", fmt.Sprintf(grantURL, granterAddr, grantee1Addr, "invalidMsg"), + http.StatusInternalServerError, invalidMsgTypeOutput, }, { "valid grant query", fmt.Sprintf(grantURL, granterAddr, grantee1Addr, msgSendTypeURL), + http.StatusOK, expGrantOutput, }, } - RunGRPCQueries(t, grantTestCases) + RunRestQueries(t, grantTestCases) // test query grants grpc endpoint grantsURL := baseurl + "/cosmos/authz/v1beta1/grants?granter=%s&grantee=%s" - grantsTestCases := []GRPCTestCase{ + grantsTestCases := []RestTestCase{ { "expect single grant", fmt.Sprintf(grantsURL, granterAddr, grantee1Addr), + http.StatusOK, fmt.Sprintf(`{"grants":[{%s}],"pagination":{"next_key":null,"total":"1"}}`, grant1), }, { "expect two grants", fmt.Sprintf(grantsURL, granterAddr, grantee2Addr), + http.StatusOK, fmt.Sprintf(`{"grants":[{%s},{%s}],"pagination":{"next_key":null,"total":"2"}}`, grant2, grant3), }, { "expect single grant with pagination", fmt.Sprintf(grantsURL+"&pagination.limit=1", granterAddr, grantee2Addr), + http.StatusOK, fmt.Sprintf(`{"grants":[{%s}],"pagination":{"next_key":"L2Nvc21vcy5nb3YudjEuTXNnVm90ZQ==","total":"0"}}`, grant2), }, { "expect single grant with pagination limit and offset", fmt.Sprintf(grantsURL+"&pagination.limit=1&pagination.offset=1", granterAddr, grantee2Addr), + http.StatusOK, fmt.Sprintf(`{"grants":[{%s}],"pagination":{"next_key":null,"total":"0"}}`, grant3), }, { "expect two grants with pagination", fmt.Sprintf(grantsURL+"&pagination.limit=2", granterAddr, grantee2Addr), + http.StatusOK, fmt.Sprintf(`{"grants":[{%s},{%s}],"pagination":{"next_key":null,"total":"0"}}`, grant2, grant3), }, } - RunGRPCQueries(t, grantsTestCases) + RunRestQueries(t, grantsTestCases) // test query grants by granter grpc endpoint grantsByGranterURL := baseurl + "/cosmos/authz/v1beta1/grants/granter/%s" @@ -745,49 +757,55 @@ func TestAuthzGRPCQueries(t *testing.T) { granterQueryOutput := fmt.Sprintf(`{"grants":[{"granter":"%s","grantee":"%s",%s}],"pagination":{"next_key":null,"total":"1"}}`, grantee1Addr, grantee2Addr, grant4) - granterTestCases := []GRPCTestCase{ + granterTestCases := []RestTestCase{ { "invalid granter account address", fmt.Sprintf(grantsByGranterURL, "invalid address"), + http.StatusInternalServerError, decodingFailedOutput, }, { "no authorizations found from granter", fmt.Sprintf(grantsByGranterURL, grantee2Addr), + http.StatusOK, noAuthorizationsOutput, }, { "valid granter query", fmt.Sprintf(grantsByGranterURL, grantee1Addr), + http.StatusOK, granterQueryOutput, }, } - RunGRPCQueries(t, granterTestCases) + RunRestQueries(t, granterTestCases) // test query grants by grantee grpc endpoint grantsByGranteeURL := baseurl + "/cosmos/authz/v1beta1/grants/grantee/%s" grantee1GrantsOutput := fmt.Sprintf(`{"grants":[{"granter":"%s","grantee":"%s",%s}],"pagination":{"next_key":null,"total":"1"}}`, granterAddr, grantee1Addr, grant1) - granteeTestCases := []GRPCTestCase{ + granteeTestCases := []RestTestCase{ { "invalid grantee account address", fmt.Sprintf(grantsByGranteeURL, "invalid address"), + http.StatusInternalServerError, decodingFailedOutput, }, { "no authorizations found from grantee", fmt.Sprintf(grantsByGranteeURL, granterAddr), + http.StatusOK, noAuthorizationsOutput, }, { "valid grantee query", fmt.Sprintf(grantsByGranteeURL, grantee1Addr), + http.StatusOK, grantee1GrantsOutput, }, } - RunGRPCQueries(t, granteeTestCases) + RunRestQueries(t, granteeTestCases) } func setupChain(t *testing.T) (*CLIWrapper, string, string) { diff --git a/tests/systemtests/bank_test.go b/tests/systemtests/bank_test.go index 255e4bae966..d776bc06a87 100644 --- a/tests/systemtests/bank_test.go +++ b/tests/systemtests/bank_test.go @@ -4,15 +4,13 @@ package systemtests import ( "fmt" + "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" "github.com/tidwall/sjson" - - "github.com/cosmos/cosmos-sdk/testutil" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) func TestBankSendTxCmd(t *testing.T) { @@ -53,7 +51,7 @@ func TestBankSendTxCmd(t *testing.T) { insufficientCmdArgs = append(insufficientCmdArgs, fmt.Sprintf("%d%s", valBalance, denom), "--fees=10stake") rsp = cli.Run(insufficientCmdArgs...) RequireTxFailure(t, rsp) - require.Contains(t, rsp, sdkerrors.ErrInsufficientFunds.Error()) + require.Contains(t, rsp, "insufficient funds") // test tx bank send with unauthorized signature assertUnauthorizedErr := func(_ assert.TestingT, gotErr error, gotOutputs ...interface{}) bool { @@ -234,10 +232,11 @@ func TestBankGRPCQueries(t *testing.T) { blockHeight := sut.CurrentHeight() supplyTestCases := []struct { - name string - url string - headers map[string]string - expOut string + name string + url string + headers map[string]string + expHttpCode int + expOut string }{ { "test GRPC total supply", @@ -245,12 +244,14 @@ func TestBankGRPCQueries(t *testing.T) { map[string]string{ blockHeightHeader: fmt.Sprintf("%d", blockHeight), }, + http.StatusOK, expTotalSupplyOutput, }, { "test GRPC total supply of a specific denom", supplyUrl + "/by_denom?denom=" + newDenom, map[string]string{}, + http.StatusOK, specificDenomOutput, }, { @@ -259,67 +260,75 @@ func TestBankGRPCQueries(t *testing.T) { map[string]string{ blockHeightHeader: fmt.Sprintf("%d", blockHeight+5), }, + http.StatusInternalServerError, "invalid height", }, { "test GRPC total supply of a bogus denom", supplyUrl + "/by_denom?denom=foobar", map[string]string{}, + http.StatusOK, + // http.StatusNotFound, bogusDenomOutput, }, } for _, tc := range supplyTestCases { t.Run(tc.name, func(t *testing.T) { - resp, err := testutil.GetRequestWithHeaders(tc.url, tc.headers) - require.NoError(t, err) + resp := GetRequestWithHeaders(t, tc.url, tc.headers, tc.expHttpCode) require.Contains(t, string(resp), tc.expOut) }) } // test denom metadata endpoint denomMetadataUrl := baseurl + "/cosmos/bank/v1beta1/denoms_metadata" - dmTestCases := []GRPCTestCase{ + dmTestCases := []RestTestCase{ { "test GRPC client metadata", denomMetadataUrl, + http.StatusOK, fmt.Sprintf(`{"metadatas":%s,"pagination":{"next_key":null,"total":"2"}}`, bankDenomMetadata), }, { "test GRPC client metadata of a specific denom", denomMetadataUrl + "/uatom", + http.StatusOK, fmt.Sprintf(`{"metadata":%s}`, atomDenomMetadata), }, { "test GRPC client metadata of a bogus denom", denomMetadataUrl + "/foobar", + http.StatusNotFound, `{"code":5, "message":"client metadata for denom foobar", "details":[]}`, }, } - RunGRPCQueries(t, dmTestCases) + RunRestQueries(t, dmTestCases) // test bank balances endpoint balanceUrl := baseurl + "/cosmos/bank/v1beta1/balances/" allBalancesOutput := `{"balances":[` + specificDenomOutput + `,{"denom":"stake","amount":"10000000"}],"pagination":{"next_key":null,"total":"2"}}` - balanceTestCases := []GRPCTestCase{ + balanceTestCases := []RestTestCase{ { "test GRPC total account balance", balanceUrl + account1Addr, + http.StatusOK, allBalancesOutput, }, { "test GRPC account balance of a specific denom", fmt.Sprintf("%s%s/by_denom?denom=%s", balanceUrl, account1Addr, newDenom), + http.StatusOK, fmt.Sprintf(`{"balance":%s}`, specificDenomOutput), }, { "test GRPC account balance of a bogus denom", fmt.Sprintf("%s%s/by_denom?denom=foobar", balanceUrl, account1Addr), + http.StatusOK, fmt.Sprintf(`{"balance":%s}`, bogusDenomOutput), }, } - RunGRPCQueries(t, balanceTestCases) + RunRestQueries(t, balanceTestCases) } diff --git a/tests/systemtests/bankv2_test.go b/tests/systemtests/bankv2_test.go index 275b50161f5..b753ee0be43 100644 --- a/tests/systemtests/bankv2_test.go +++ b/tests/systemtests/bankv2_test.go @@ -50,10 +50,9 @@ func TestBankV2SendTxCmd(t *testing.T) { valBalanceAfer := gjson.Get(valRaw, "balance.amount").Int() // TODO: Make DeductFee ante handler work with bank/v2 - require.Equal(t, valBalanceAfer, valBalance - transferAmount) + require.Equal(t, valBalanceAfer, valBalance-transferAmount) receiverRaw := cli.CustomQuery("q", "bankv2", "balance", receiverAddr, denom) receiverBalance := gjson.Get(receiverRaw, "balance.amount").Int() require.Equal(t, receiverBalance, transferAmount) - } diff --git a/tests/systemtests/cometbft_client_test.go b/tests/systemtests/cometbft_client_test.go index e031ae0c8f5..cee97baa0c6 100644 --- a/tests/systemtests/cometbft_client_test.go +++ b/tests/systemtests/cometbft_client_test.go @@ -5,6 +5,8 @@ package systemtests import ( "context" "fmt" + "net/http" + "net/url" "testing" "time" @@ -15,7 +17,6 @@ import ( "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - "github.com/cosmos/cosmos-sdk/testutil" qtypes "github.com/cosmos/cosmos-sdk/types/query" ) @@ -32,7 +33,7 @@ func TestQueryNodeInfo(t *testing.T) { assert.Equal(t, res.ApplicationVersion.Version, v) // TODO: we should be adding a way to distinguish a v2. Eventually we should skip some v2 system depending on the consensus engine we want to test - restRes, err := testutil.GetRequest(fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/node_info", baseurl)) + restRes := GetRequest(t, mustV(url.JoinPath(baseurl, "/cosmos/base/tendermint/v1beta1/node_info"))) assert.NoError(t, err) assert.Equal(t, gjson.GetBytes(restRes, "application_version.version").String(), res.ApplicationVersion.Version) } @@ -46,8 +47,7 @@ func TestQuerySyncing(t *testing.T) { res, err := qc.GetSyncing(context.Background(), &cmtservice.GetSyncingRequest{}) assert.NoError(t, err) - restRes, err := testutil.GetRequest(fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/syncing", baseurl)) - assert.NoError(t, err) + restRes := GetRequest(t, mustV(url.JoinPath(baseurl, "/cosmos/base/tendermint/v1beta1/syncing"))) assert.Equal(t, gjson.GetBytes(restRes, "syncing").Bool(), res.Syncing) } @@ -61,8 +61,7 @@ func TestQueryLatestBlock(t *testing.T) { assert.NoError(t, err) assert.Contains(t, res.SdkBlock.Header.ProposerAddress, "cosmosvalcons") - _, err = testutil.GetRequest(fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/blocks/latest", baseurl)) - assert.NoError(t, err) + _ = GetRequest(t, mustV(url.JoinPath(baseurl, "/cosmos/base/tendermint/v1beta1/blocks/latest"))) } func TestQueryBlockByHeight(t *testing.T) { @@ -78,13 +77,16 @@ func TestQueryBlockByHeight(t *testing.T) { assert.Equal(t, res.SdkBlock.Header.Height, int64(2)) assert.Contains(t, res.SdkBlock.Header.ProposerAddress, "cosmosvalcons") - restRes, err := testutil.GetRequest(fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/blocks/%d", baseurl, 2)) - assert.NoError(t, err) + restRes := GetRequest(t, mustV(url.JoinPath(baseurl, "/cosmos/base/tendermint/v1beta1/blocks/2"))) assert.Equal(t, gjson.GetBytes(restRes, "sdk_block.header.height").Int(), int64(2)) assert.Contains(t, gjson.GetBytes(restRes, "sdk_block.header.proposer_address").String(), "cosmosvalcons") } 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) @@ -106,8 +108,7 @@ func TestQueryLatestValidatorSet(t *testing.T) { assert.NoError(t, err) assert.Equal(t, len(res.Validators), 2) - restRes, err := testutil.GetRequest(fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/validatorsets/latest?pagination.offset=%d&pagination.limit=%d", baseurl, 0, 2)) - assert.NoError(t, err) + 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) } @@ -144,6 +145,7 @@ func TestLatestValidatorSet(t *testing.T) { }) } } + func TestLatestValidatorSet_GRPCGateway(t *testing.T) { sut.ResetChain(t) sut.StartChain(t) @@ -164,14 +166,14 @@ func TestLatestValidatorSet_GRPCGateway(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - rsp, err := testutil.GetRequest(fmt.Sprintf("%s%s", baseurl, tc.url)) - assert.NoError(t, err) 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())) }) } } @@ -208,42 +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, err := testutil.GetRequest(tc.url) - assert.NoError(t, err) + 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)) @@ -317,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/distribution_test.go b/tests/systemtests/distribution_test.go new file mode 100644 index 00000000000..6d8eaf2ae0c --- /dev/null +++ b/tests/systemtests/distribution_test.go @@ -0,0 +1,301 @@ +//go:build system_test + +package systemtests + +import ( + "fmt" + "net/http" + "os" + "path/filepath" + "regexp" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" +) + +const ( + distrTestDenom = "stake" +) + +func TestWithdrawAllRewardsCmd(t *testing.T) { + // scenario: test distribution withdraw all rewards command + // given a running chain + + sut.ResetChain(t) + cli := NewCLIWrapper(t, sut, verbose) + + newAddr := cli.AddKey("newAddr") + require.NotEmpty(t, newAddr) + + var initialAmount int64 = 10000000 + initialBalance := fmt.Sprintf("%d%s", initialAmount, distrTestDenom) + sut.ModifyGenesisCLI(t, + []string{"genesis", "add-genesis-account", newAddr, initialBalance}, + ) + sut.StartChain(t) + + // query balance + newAddrBal := cli.QueryBalance(newAddr, distrTestDenom) + require.Equal(t, initialAmount, newAddrBal) + + // query validator operator address + rsp := cli.CustomQuery("q", "staking", "validators") + validators := gjson.Get(rsp, "validators.#.operator_address").Array() + require.GreaterOrEqual(t, len(validators), 2) + val1Addr := validators[0].String() + val2Addr := validators[1].String() + + var delegationAmount int64 = 100000 + delegation := fmt.Sprintf("%d%s", delegationAmount, distrTestDenom) + + // delegate tokens to validator1 + rsp = cli.RunAndWait("tx", "staking", "delegate", val1Addr, delegation, "--from="+newAddr, "--fees=1"+distrTestDenom) + RequireTxSuccess(t, rsp) + + // delegate tokens to validator2 + rsp = cli.RunAndWait("tx", "staking", "delegate", val2Addr, delegation, "--from="+newAddr, "--fees=1"+distrTestDenom) + RequireTxSuccess(t, rsp) + + // check updated balance: newAddrBal - delegatedBal - fees + expBal := newAddrBal - (delegationAmount * 2) - 2 + newAddrBal = cli.QueryBalance(newAddr, distrTestDenom) + require.Equal(t, expBal, newAddrBal) + + withdrawCmdArgs := []string{"tx", "distribution", "withdraw-all-rewards", "--from=" + newAddr, "--fees=1" + distrTestDenom} + + // test with --max-msgs + testCases := []struct { + name string + maxMsgs int + expTxLen int + }{ + { + "--max-msgs value is 1", + 1, + 2, + }, + { + "--max-msgs value is 2", + 2, + 1, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assertGenOnlyOutput := func(_ assert.TestingT, gotErr error, gotOutputs ...interface{}) bool { + require.Len(t, gotOutputs, 1) + // gets output combining two objects without any space or new line + splitOutput := strings.Split(gotOutputs[0].(string), "}{") + require.Len(t, splitOutput, tc.expTxLen) + return false + } + cmd := append(withdrawCmdArgs, fmt.Sprintf("--max-msgs=%d", tc.maxMsgs), "--generate-only") + _ = cli.WithRunErrorMatcher(assertGenOnlyOutput).Run(cmd...) + }) + } + + // test withdraw-all-rewards transaction + rsp = cli.RunAndWait(withdrawCmdArgs...) + RequireTxSuccess(t, rsp) +} + +func TestDistrValidatorGRPCQueries(t *testing.T) { + // scenario: test distribution validator grpc gateway queries + // given a running chain + + sut.ResetChain(t) + cli := NewCLIWrapper(t, sut, verbose) + // get validator address + valAddr := cli.GetKeyAddr("node0") + require.NotEmpty(t, valAddr) + valOperAddr := cli.GetKeyAddrPrefix("node0", "val") + require.NotEmpty(t, valOperAddr) + + sut.StartChain(t) + + sut.AwaitNBlocks(t, 3) + + baseurl := sut.APIAddress() + expectedAmountOutput := fmt.Sprintf(`{"denom":"%s","amount":"203.105000000000000000"}`, distrTestDenom) + + // test params grpc endpoint + paramsURL := baseurl + "/cosmos/distribution/v1beta1/params" + + paramsTestCases := []RestTestCase{ + { + "gRPC request params", + paramsURL, + http.StatusOK, + `{"params":{"community_tax":"0.020000000000000000","base_proposer_reward":"0.000000000000000000","bonus_proposer_reward":"0.000000000000000000","withdraw_addr_enabled":true}}`, + }, + } + RunRestQueries(t, paramsTestCases) + + // test validator distribution info grpc endpoint + validatorsURL := baseurl + `/cosmos/distribution/v1beta1/validators/%s` + validatorsOutput := fmt.Sprintf(`{"operator_address":"%s","self_bond_rewards":[],"commission":[%s]}`, valAddr, expectedAmountOutput) + + validatorsTestCases := []RestTestCase{ + { + "gRPC request validator with valid validator address", + fmt.Sprintf(validatorsURL, valOperAddr), + http.StatusOK, + validatorsOutput, + }, + } + TestRestQueryIgnoreNumbers(t, validatorsTestCases) + + // test outstanding rewards grpc endpoint + outstandingRewardsURL := baseurl + `/cosmos/distribution/v1beta1/validators/%s/outstanding_rewards` + + rewardsTestCases := []RestTestCase{ + { + "gRPC request outstanding rewards with valid validator address", + fmt.Sprintf(outstandingRewardsURL, valOperAddr), + http.StatusOK, + fmt.Sprintf(`{"rewards":{"rewards":[%s]}}`, expectedAmountOutput), + }, + } + TestRestQueryIgnoreNumbers(t, rewardsTestCases) + + // test validator commission grpc endpoint + commissionURL := baseurl + `/cosmos/distribution/v1beta1/validators/%s/commission` + + commissionTestCases := []RestTestCase{ + { + "gRPC request commission with valid validator address", + fmt.Sprintf(commissionURL, valOperAddr), + http.StatusOK, + fmt.Sprintf(`{"commission":{"commission":[%s]}}`, expectedAmountOutput), + }, + } + TestRestQueryIgnoreNumbers(t, commissionTestCases) + + // test validator slashes grpc endpoint + slashURL := baseurl + `/cosmos/distribution/v1beta1/validators/%s/slashes` + invalidHeightOutput := `{"code":3, "message":"strconv.ParseUint: parsing \"-3\": invalid syntax", "details":[]}` + + slashTestCases := []RestTestCase{ + { + "invalid start height", + fmt.Sprintf(slashURL+`?starting_height=%s&ending_height=%s`, valOperAddr, "-3", "3"), + http.StatusBadRequest, + invalidHeightOutput, + }, + { + "invalid end height", + fmt.Sprintf(slashURL+`?starting_height=%s&ending_height=%s`, valOperAddr, "1", "-3"), + http.StatusBadRequest, + invalidHeightOutput, + }, + { + "valid request get slashes", + fmt.Sprintf(slashURL+`?starting_height=%s&ending_height=%s`, valOperAddr, "1", "3"), + http.StatusOK, + `{"slashes":[],"pagination":{"next_key":null,"total":"0"}}`, + }, + } + RunRestQueries(t, slashTestCases) +} + +func TestDistrDelegatorGRPCQueries(t *testing.T) { + // scenario: test distribution validator gsrpc gateway queries + // given a running chain + + sut.ResetChain(t) + cli := NewCLIWrapper(t, sut, verbose) + + // get validator address + valAddr := cli.GetKeyAddr("node0") + require.NotEmpty(t, valAddr) + valOperAddr := cli.GetKeyAddrPrefix("node0", "val") + require.NotEmpty(t, valOperAddr) + + // update commission rate of node0 validator + // generate new gentx and copy it to genesis.json before starting network + rsp := cli.RunCommandWithArgs("genesis", "gentx", "node0", "100000000"+distrTestDenom, "--chain-id="+cli.chainID, "--commission-rate=0.01", "--home", sut.nodePath(0), "--keyring-backend=test") + // extract gentx path from above command output + re := regexp.MustCompile(`"(.*?\.json)"`) + match := re.FindStringSubmatch(rsp) + require.GreaterOrEqual(t, len(match), 1) + + updatedGentx := filepath.Join(WorkDir, match[1]) + updatedGentxBz, err := os.ReadFile(updatedGentx) // #nosec G304 + require.NoError(t, err) + + sut.ModifyGenesisJSON(t, func(genesis []byte) []byte { + state, err := sjson.SetRawBytes(genesis, "app_state.genutil.gen_txs.0", updatedGentxBz) + require.NoError(t, err) + return state + }) + + // create new address which will be used as delegator address + delAddr := cli.AddKey("delAddr") + require.NotEmpty(t, delAddr) + + var initialAmount int64 = 1000000000 + initialBalance := fmt.Sprintf("%d%s", initialAmount, distrTestDenom) + sut.ModifyGenesisCLI(t, + []string{"genesis", "add-genesis-account", delAddr, initialBalance}, + ) + + sut.StartChain(t) + + // delegate some tokens to valOperAddr + rsp = cli.RunAndWait("tx", "staking", "delegate", valOperAddr, "100000000"+distrTestDenom, "--from="+delAddr) + RequireTxSuccess(t, rsp) + + sut.AwaitNBlocks(t, 5) + + baseurl := sut.APIAddress() + + // test delegator rewards grpc endpoint + delegatorRewardsURL := baseurl + `/cosmos/distribution/v1beta1/delegators/%s/rewards` + expectedAmountOutput := `{"denom":"stake","amount":"0.121275000000000000"}` + rewardsOutput := fmt.Sprintf(`{"rewards":[{"validator_address":"%s","reward":[%s]}],"total":[%s]}`, valOperAddr, expectedAmountOutput, expectedAmountOutput) + + delegatorRewardsTestCases := []RestTestCase{ + { + "valid rewards request with valid delegator address", + fmt.Sprintf(delegatorRewardsURL, delAddr), + http.StatusOK, + rewardsOutput, + }, + { + "valid request(specific validator rewards)", + fmt.Sprintf(delegatorRewardsURL+`/%s`, delAddr, valOperAddr), + http.StatusOK, + fmt.Sprintf(`{"rewards":[%s]}`, expectedAmountOutput), + }, + } + TestRestQueryIgnoreNumbers(t, delegatorRewardsTestCases) + + // test delegator validators grpc endpoint + delegatorValsURL := baseurl + `/cosmos/distribution/v1beta1/delegators/%s/validators` + valsTestCases := []RestTestCase{ + { + "gRPC request delegator validators with valid delegator address", + fmt.Sprintf(delegatorValsURL, delAddr), + http.StatusOK, + fmt.Sprintf(`{"validators":["%s"]}`, valOperAddr), + }, + } + RunRestQueries(t, valsTestCases) + + // test withdraw address grpc endpoint + withdrawAddrURL := baseurl + `/cosmos/distribution/v1beta1/delegators/%s/withdraw_address` + withdrawAddrTestCases := []RestTestCase{ + { + "gRPC request withdraw address with valid delegator address", + fmt.Sprintf(withdrawAddrURL, delAddr), + http.StatusOK, + fmt.Sprintf(`{"withdraw_address":"%s"}`, delAddr), + }, + } + RunRestQueries(t, withdrawAddrTestCases) +} diff --git a/tests/systemtests/fraud_test.go b/tests/systemtests/fraud_test.go index 043db59a545..0479f526cc2 100644 --- a/tests/systemtests/fraud_test.go +++ b/tests/systemtests/fraud_test.go @@ -35,8 +35,7 @@ func TestValidatorDoubleSign(t *testing.T) { newNode := sut.AddFullnode(t, func(nodeNumber int, nodePath string) { valKeyFile := filepath.Join(WorkDir, nodePath, "config", "priv_validator_key.json") _ = os.Remove(valKeyFile) - _, err := copyFile(filepath.Join(WorkDir, sut.nodePath(0), "config", "priv_validator_key.json"), valKeyFile) - require.NoError(t, err) + _ = MustCopyFile(filepath.Join(WorkDir, sut.nodePath(0), "config", "priv_validator_key.json"), valKeyFile) }) sut.AwaitNodeUp(t, fmt.Sprintf("http://%s:%d", newNode.IP, newNode.RPCPort)) diff --git a/tests/systemtests/go.mod b/tests/systemtests/go.mod index 6203d6d05d0..f32c7597abe 100644 --- a/tests/systemtests/go.mod +++ b/tests/systemtests/go.mod @@ -146,16 +146,16 @@ require ( github.com/zondax/ledger-go v0.14.3 // indirect go.etcd.io/bbolt v1.3.8 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.1 // indirect diff --git a/tests/systemtests/go.sum b/tests/systemtests/go.sum index 74600923052..3fa24ef0038 100644 --- a/tests/systemtests/go.sum +++ b/tests/systemtests/go.sum @@ -769,8 +769,8 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= @@ -887,20 +887,20 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -984,8 +984,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/tests/systemtests/gov_test.go b/tests/systemtests/gov_test.go index 73bc7fd96ef..092773dc205 100644 --- a/tests/systemtests/gov_test.go +++ b/tests/systemtests/gov_test.go @@ -8,13 +8,14 @@ import ( "testing" "time" - "cosmossdk.io/math" - "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/testutil" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" + + "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/client/flags" + sdk "github.com/cosmos/cosmos-sdk/types" ) func TestSubmitProposal(t *testing.T) { @@ -39,7 +40,8 @@ func TestSubmitProposal(t *testing.T) { "type": "Text", "deposit": "-324foocoin" }` - invalidPropFile := testutil.WriteToNewTempFile(t, invalidProp) + + invalidPropFile := StoreTempFile(t, []byte(invalidProp)) defer invalidPropFile.Close() // Create a valid new proposal JSON. @@ -62,7 +64,7 @@ func TestSubmitProposal(t *testing.T) { "metadata": "%s", "deposit": "%s" }`, govAddress, base64.StdEncoding.EncodeToString(propMetadata), sdk.NewCoin("stake", math.NewInt(100000))) - validPropFile := testutil.WriteToNewTempFile(t, validProp) + validPropFile := StoreTempFile(t, []byte(validProp)) defer validPropFile.Close() testCases := []struct { @@ -136,7 +138,7 @@ func TestSubmitLegacyProposal(t *testing.T) { "type": "Text", "deposit": "-324foocoin" }` - invalidPropFile := testutil.WriteToNewTempFile(t, invalidProp) + invalidPropFile := StoreTempFile(t, []byte(invalidProp)) defer invalidPropFile.Close() validProp := fmt.Sprintf(`{ @@ -145,7 +147,7 @@ func TestSubmitLegacyProposal(t *testing.T) { "type": "Text", "deposit": "%s" }`, sdk.NewCoin("stake", math.NewInt(154310))) - validPropFile := testutil.WriteToNewTempFile(t, validProp) + validPropFile := StoreTempFile(t, []byte(validProp)) defer validPropFile.Close() testCases := []struct { @@ -253,7 +255,8 @@ func TestNewCmdWeightedVote(t *testing.T) { fmt.Sprintf("--%s=%s", flags.FlagFrom, valAddr), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), - fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(10))).String())} + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(10))).String()), + } rsp := cli.Run(proposalArgs...) txResult, found := cli.AwaitTxCommitted(rsp) require.True(t, found) @@ -389,7 +392,8 @@ func TestQueryDeposit(t *testing.T) { fmt.Sprintf("--%s=%s", flags.FlagFrom, valAddr), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), - fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(10))).String())} + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(10))).String()), + } rsp := cli.Run(proposalArgs...) txResult, found := cli.AwaitTxCommitted(rsp) require.True(t, found) diff --git a/tests/systemtests/io_utils.go b/tests/systemtests/io_utils.go new file mode 100644 index 00000000000..a9cd1094fba --- /dev/null +++ b/tests/systemtests/io_utils.go @@ -0,0 +1,65 @@ +package systemtests + +import ( + "bytes" + "fmt" + "io" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +// MustCopyFile copies the file from the source path `src` to the destination path `dest` and returns an open file handle to `dest`. +func MustCopyFile(src, dest string) *os.File { + in, err := os.Open(src) + if err != nil { + panic(fmt.Sprintf("failed to open %q: %v", src, err)) + } + defer in.Close() + + out, err := os.Create(dest) + if err != nil { + panic(fmt.Sprintf("failed to create %q: %v", dest, err)) + } + defer out.Close() + + _, err = io.Copy(out, in) + if err != nil { + panic(fmt.Sprintf("failed to copy from %q to %q: %v", src, dest, err)) + } + return out +} + +// MustCopyFilesInDir copies all files (excluding directories) from the source directory `src` to the destination directory `dest`. +func MustCopyFilesInDir(src, dest string) { + err := os.MkdirAll(dest, 0o750) + if err != nil { + panic(fmt.Sprintf("failed to create %q: %v", dest, err)) + } + + fs, err := os.ReadDir(src) + if err != nil { + panic(fmt.Sprintf("failed to read dir %q: %v", src, err)) + } + + for _, f := range fs { + if f.IsDir() { + continue + } + _ = MustCopyFile(filepath.Join(src, f.Name()), filepath.Join(dest, f.Name())) + } +} + +// StoreTempFile creates a temporary file in the test's temporary directory with the provided content. +// It returns a pointer to the created file. Errors during the process are handled with test assertions. +func StoreTempFile(t *testing.T, content []byte) *os.File { + t.Helper() + out, err := os.CreateTemp(t.TempDir(), "") + require.NoError(t, err) + _, err = io.Copy(out, bytes.NewReader(content)) + require.NoError(t, err) + require.NoError(t, out.Close()) + return out +} diff --git a/tests/systemtests/rest_cli.go b/tests/systemtests/rest_cli.go index c53533c0786..3ad1e30dda6 100644 --- a/tests/systemtests/rest_cli.go +++ b/tests/systemtests/rest_cli.go @@ -1,6 +1,9 @@ package systemtests import ( + "io" + "net/http" + "regexp" "testing" "github.com/stretchr/testify/require" @@ -8,22 +11,77 @@ import ( "github.com/cosmos/cosmos-sdk/testutil" ) -type GRPCTestCase struct { - name string - url string - expOut string +type RestTestCase struct { + name string + url string + expCode int + expOut string } -// RunGRPCQueries runs given grpc testcases by making requests and +// RunRestQueries runs given Rest testcases by making requests and // checking response with expected output -func RunGRPCQueries(t *testing.T, testCases []GRPCTestCase) { +func RunRestQueries(t *testing.T, testCases []RestTestCase) { + t.Helper() + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + resp := GetRequestWithHeaders(t, tc.url, nil, tc.expCode) + require.JSONEq(t, tc.expOut, string(resp)) + }) + } +} + +// TestRestQueryIgnoreNumbers runs given rest testcases by making requests and +// checking response with expected output ignoring number values +// This method is used when number values in response are non-deterministic +func TestRestQueryIgnoreNumbers(t *testing.T, testCases []RestTestCase) { t.Helper() for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { resp, err := testutil.GetRequest(tc.url) require.NoError(t, err) - require.JSONEq(t, tc.expOut, string(resp)) + + // regular expression pattern to match any numeric value in the JSON + numberRegexPattern := `"\d+(\.\d+)?"` + + // compile the regex + r, err := regexp.Compile(numberRegexPattern) + require.NoError(t, err) + + // replace all numeric values in the above JSONs with `NUMBER` text + expectedJSON := r.ReplaceAllString(tc.expOut, `"NUMBER"`) + actualJSON := r.ReplaceAllString(string(resp), `"NUMBER"`) + + // compare two jsons + require.JSONEq(t, expectedJSON, actualJSON) }) } } + +func GetRequest(t *testing.T, url string) []byte { + t.Helper() + return GetRequestWithHeaders(t, url, nil, http.StatusOK) +} + +func GetRequestWithHeaders(t *testing.T, url string, headers map[string]string, expCode int) []byte { + t.Helper() + req, err := http.NewRequest("GET", url, nil) + require.NoError(t, err) + + for key, value := range headers { + req.Header.Set(key, value) + } + + httpClient := &http.Client{} + res, err := httpClient.Do(req) + require.NoError(t, err) + defer func() { + _ = res.Body.Close() + }() + 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/snapshots_test.go b/tests/systemtests/snapshots_test.go index 42569a03179..d80eb530f41 100644 --- a/tests/systemtests/snapshots_test.go +++ b/tests/systemtests/snapshots_test.go @@ -4,13 +4,13 @@ package systemtests import ( "fmt" - "github.com/stretchr/testify/require" "os" "testing" + + "github.com/stretchr/testify/require" ) func TestSnapshots(t *testing.T) { - sut.ResetChain(t) cli := NewCLIWrapper(t, sut, verbose) sut.StartChain(t) diff --git a/tests/systemtests/system.go b/tests/systemtests/system.go index c4368c01558..f42bf96a79c 100644 --- a/tests/systemtests/system.go +++ b/tests/systemtests/system.go @@ -132,12 +132,17 @@ func (s *SystemUnderTest) SetupChain() { genesisBz, err = sjson.SetRawBytes(genesisBz, "consensus.params.block.max_gas", []byte(fmt.Sprintf(`"%d"`, 10_000_000))) if err != nil { - panic(fmt.Sprintf("failed set block max gas: %s", err)) + panic(fmt.Sprintf("failed to set block max gas: %s", err)) } // Short period for gov genesisBz, err = sjson.SetRawBytes(genesisBz, "app_state.gov.params.voting_period", []byte(fmt.Sprintf(`"%s"`, "8s"))) if err != nil { - panic(fmt.Sprintf("failed set block max gas: %s", err)) + panic(fmt.Sprintf("failed to set regular voting period: %s", err)) + } + // update expedited voting period to avoid validation error + genesisBz, err = sjson.SetRawBytes(genesisBz, "app_state.gov.params.expedited_voting_period", []byte(fmt.Sprintf(`"%s"`, "7s"))) + if err != nil { + panic(fmt.Sprintf("failed to set expedited voting period: %s", err)) } s.withEachNodeHome(func(i int, home string) { if err := saveGenesis(home, genesisBz); err != nil { @@ -147,15 +152,11 @@ func (s *SystemUnderTest) SetupChain() { // backup genesis dest := filepath.Join(WorkDir, s.nodePath(0), "config", "genesis.json.orig") - if _, err := copyFile(src, dest); err != nil { - panic(fmt.Sprintf("copy failed :%#+v", err)) - } + MustCopyFile(src, dest) // backup keyring src = filepath.Join(WorkDir, s.nodePath(0), "keyring-test") dest = filepath.Join(WorkDir, s.outputDir, "keyring-test") - if err := copyFilesInDir(src, dest); err != nil { - panic(fmt.Sprintf("copy files from dir :%#+v", err)) - } + MustCopyFilesInDir(src, dest) } func (s *SystemUnderTest) StartChain(t *testing.T, xargs ...string) { @@ -485,7 +486,7 @@ func (s *SystemUnderTest) modifyGenesisJSON(t *testing.T, mutators ...GenesisMut for _, m := range mutators { current = m(current) } - out := storeTempFile(t, current) + out := StoreTempFile(t, current) defer os.Remove(out.Name()) s.setGenesis(t, out.Name()) s.MarkDirty() @@ -712,8 +713,7 @@ func (s *SystemUnderTest) AddFullnode(t *testing.T, beforeStart ...func(nodeNumb for _, tomlFile := range []string{"config.toml", "app.toml"} { configFile := filepath.Join(configPath, tomlFile) _ = os.Remove(configFile) - _, err := copyFile(filepath.Join(WorkDir, s.nodePath(0), "config", tomlFile), configFile) - require.NoError(t, err) + _ = MustCopyFile(filepath.Join(WorkDir, s.nodePath(0), "config", tomlFile), configFile) } // start node allNodes := s.AllNodes(t) @@ -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 @@ -949,54 +954,6 @@ func restoreOriginalKeyring(t *testing.T, s *SystemUnderTest) { require.NoError(t, os.RemoveAll(dest)) for i := 0; i < s.initialNodesCount; i++ { src := filepath.Join(WorkDir, s.nodePath(i), "keyring-test") - require.NoError(t, copyFilesInDir(src, dest)) + MustCopyFilesInDir(src, dest) } } - -// copyFile copy source file to dest file path -func copyFile(src, dest string) (*os.File, error) { - in, err := os.Open(src) - if err != nil { - return nil, err - } - defer in.Close() - out, err := os.Create(dest) - if err != nil { - return nil, err - } - defer out.Close() - - _, err = io.Copy(out, in) - return out, err -} - -// copyFilesInDir copy files in src dir to dest path -func copyFilesInDir(src, dest string) error { - err := os.MkdirAll(dest, 0o750) - if err != nil { - return fmt.Errorf("mkdirs: %w", err) - } - fs, err := os.ReadDir(src) - if err != nil { - return fmt.Errorf("read dir: %w", err) - } - for _, f := range fs { - if f.IsDir() { - continue - } - if _, err := copyFile(filepath.Join(src, f.Name()), filepath.Join(dest, f.Name())); err != nil { - return fmt.Errorf("copy file: %q: %w", f.Name(), err) - } - } - return nil -} - -func storeTempFile(t *testing.T, content []byte) *os.File { - t.Helper() - out, err := os.CreateTemp(t.TempDir(), "genesis") - require.NoError(t, err) - _, err = io.Copy(out, bytes.NewReader(content)) - require.NoError(t, err) - require.NoError(t, out.Close()) - return out -} diff --git a/tests/systemtests/testnet_init.go b/tests/systemtests/testnet_init.go index bdb2a85d25d..9033c283e43 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 bd719d1e796..c7de9e6dfa9 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)) diff --git a/testutil/x/counter/depinject.go b/testutil/x/counter/depinject.go index b529d7f5429..3f05c7659f3 100644 --- a/testutil/x/counter/depinject.go +++ b/testutil/x/counter/depinject.go @@ -1,6 +1,10 @@ package counter import ( + "fmt" + "maps" + "slices" + "cosmossdk.io/core/appmodule" "cosmossdk.io/depinject" "cosmossdk.io/depinject/appconfig" @@ -18,6 +22,7 @@ func init() { appconfig.RegisterModule( &types.Module{}, appconfig.Provide(ProvideModule), + appconfig.Invoke(InvokeSetHooks), ) } @@ -31,7 +36,7 @@ type ModuleInputs struct { type ModuleOutputs struct { depinject.Out - Keeper keeper.Keeper + Keeper *keeper.Keeper Module appmodule.AppModule } @@ -44,3 +49,27 @@ func ProvideModule(in ModuleInputs) ModuleOutputs { Module: m, } } + +func InvokeSetHooks(keeper *keeper.Keeper, counterHooks map[string]types.CounterHooksWrapper) error { + if keeper == nil { + return fmt.Errorf("keeper is nil") + } + if counterHooks == nil { + return fmt.Errorf("counterHooks is nil") + } + + // Default ordering is lexical by module name. + // Explicit ordering can be added to the module config if required. + modNames := slices.Sorted(maps.Keys(counterHooks)) + var multiHooks types.MultiCounterHooks + for _, modName := range modNames { + hook, ok := counterHooks[modName] + if !ok { + return fmt.Errorf("can't find hooks for module %s", modName) + } + multiHooks = append(multiHooks, hook) + } + + keeper.SetHooks(multiHooks) + return nil +} diff --git a/testutil/x/counter/keeper/hooks.go b/testutil/x/counter/keeper/hooks.go new file mode 100644 index 00000000000..5b847bf95c6 --- /dev/null +++ b/testutil/x/counter/keeper/hooks.go @@ -0,0 +1,14 @@ +package keeper + +import ( + "context" +) + +type Hooks struct { + AfterCounterIncreased bool +} + +func (h *Hooks) AfterIncreaseCount(ctx context.Context, n int64) error { + h.AfterCounterIncreased = true + return nil +} diff --git a/testutil/x/counter/keeper/keeper.go b/testutil/x/counter/keeper/keeper.go index e3389089dad..8890f801fb4 100644 --- a/testutil/x/counter/keeper/keeper.go +++ b/testutil/x/counter/keeper/keeper.go @@ -21,11 +21,13 @@ type Keeper struct { appmodule.Environment CountStore collections.Item[int64] + + hooks types.CounterHooks } -func NewKeeper(env appmodule.Environment) Keeper { +func NewKeeper(env appmodule.Environment) *Keeper { sb := collections.NewSchemaBuilder(env.KVStoreService) - return Keeper{ + return &Keeper{ Environment: env, CountStore: collections.NewItem(sb, collections.NewPrefix(0), "count", collections.Int64Value), } @@ -67,6 +69,10 @@ func (k Keeper) IncreaseCount(ctx context.Context, msg *types.MsgIncreaseCounter return nil, err } + if err := k.Hooks().AfterIncreaseCount(ctx, num+msg.Count); err != nil { + return nil, err + } + if err := k.EventService.EventManager(ctx).EmitKV( "increase_counter", event.NewAttribute("signer", msg.Signer), @@ -78,3 +84,23 @@ func (k Keeper) IncreaseCount(ctx context.Context, msg *types.MsgIncreaseCounter NewCount: num + msg.Count, }, nil } + +// Hooks gets the hooks for counter Keeper +func (k *Keeper) Hooks() types.CounterHooks { + if k.hooks == nil { + // return a no-op implementation if no hooks are set + return types.MultiCounterHooks{} + } + + return k.hooks +} + +// SetHooks sets the hooks for counter +func (k *Keeper) SetHooks(gh types.CounterHooks) *Keeper { + if k.hooks != nil { + panic("cannot set governance hooks twice") + } + + k.hooks = gh + return k +} diff --git a/testutil/x/counter/module.go b/testutil/x/counter/module.go index 39f18857d8a..455b6a6e8bc 100644 --- a/testutil/x/counter/module.go +++ b/testutil/x/counter/module.go @@ -17,7 +17,7 @@ var ( // AppModule implements an application module type AppModule struct { - keeper keeper.Keeper + keeper *keeper.Keeper } // IsAppModule implements the appmodule.AppModule interface. @@ -31,7 +31,7 @@ func (am AppModule) RegisterServices(registrar grpc.ServiceRegistrar) error { } // NewAppModule creates a new AppModule object -func NewAppModule(keeper keeper.Keeper) AppModule { +func NewAppModule(keeper *keeper.Keeper) AppModule { return AppModule{ keeper: keeper, } diff --git a/testutil/x/counter/types/expected_keepers.go b/testutil/x/counter/types/expected_keepers.go new file mode 100644 index 00000000000..d4bc4e5dc21 --- /dev/null +++ b/testutil/x/counter/types/expected_keepers.go @@ -0,0 +1,12 @@ +package types + +import "context" + +type CounterHooks interface { + AfterIncreaseCount(ctx context.Context, newCount int64) error +} + +type CounterHooksWrapper struct{ CounterHooks } + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (CounterHooksWrapper) IsOnePerModuleType() {} diff --git a/testutil/x/counter/types/hooks.go b/testutil/x/counter/types/hooks.go new file mode 100644 index 00000000000..5d5e475188c --- /dev/null +++ b/testutil/x/counter/types/hooks.go @@ -0,0 +1,26 @@ +package types + +import ( + "context" + "errors" +) + +var _ CounterHooks = MultiCounterHooks{} + +// MultiCounterHooks is a slice of hooks to be called in sequence. +type MultiCounterHooks []CounterHooks + +// NewMultiCounterHooks returns a MultiCounterHooks from a list of CounterHooks +func NewMultiCounterHooks(hooks ...CounterHooks) MultiCounterHooks { + return hooks +} + +// AfterIncreaseCount calls AfterIncreaseCount on all hooks and collects the errors if any. +func (ch MultiCounterHooks) AfterIncreaseCount(ctx context.Context, newCount int64) error { + var errs error + for i := range ch { + errs = errors.Join(errs, ch[i].AfterIncreaseCount(ctx, newCount)) + } + + return errs +} diff --git a/tools/confix/README.md b/tools/confix/README.md index 64b2f49b3dc..00851ede192 100644 --- a/tools/confix/README.md +++ b/tools/confix/README.md @@ -23,7 +23,7 @@ import "cosmossdk.io/tools/confix/cmd" Find the following line: ```go -initRootCmd(rootCmd, encodingConfig) +initRootCmd(rootCmd, moduleManager) ``` After that line, add the following: @@ -39,10 +39,10 @@ An implementation example can be found in `simapp`. The command will be available as `simd config`. -```tip +:::tip Using confix directly in the application can have less features than using it standalone. This is because confix is versioned with the SDK, while `latest` is the standalone version. -``` +::: ### Using Confix Standalone @@ -138,7 +138,7 @@ confix view ~/.simapp/config/client.toml # views the current app client conf ### Maintainer -At each SDK modification of the default configuration, add the default SDK config under `data/v0.XX-app.toml`. +At each SDK modification of the default configuration, add the default SDK config under `data/vXX-app.toml`. This allows users to use the tool standalone. ### Compatibility @@ -149,7 +149,8 @@ The recommended standalone version is `latest`, which is using the latest develo | ----------- | -------------- | | v0.50 | v0.1.x | | v0.52 | v0.2.x | +| v2 | v0.2.x | ## Credits -This project is based on the [CometBFT RFC 019](https://github.com/cometbft/cometbft/blob/5013bc3f4a6d64dcc2bf02ccc002ebc9881c62e4/docs/rfc/rfc-019-config-version.md) and their own implementation of [confix](https://github.com/cometbft/cometbft/blob/v0.36.x/scripts/confix/confix.go). +This project is based on the [CometBFT RFC 019](https://github.com/cometbft/cometbft/blob/5013bc3f4a6d64dcc2bf02ccc002ebc9881c62e4/docs/rfc/rfc-019-config-version.md) and their never released own implementation of [confix](https://github.com/cometbft/cometbft/blob/v0.36.x/scripts/confix/confix.go). diff --git a/tools/confix/go.mod b/tools/confix/go.mod index e6800c66e46..605ff6f2752 100644 --- a/tools/confix/go.mod +++ b/tools/confix/go.mod @@ -139,18 +139,18 @@ require ( github.com/zondax/ledger-go v0.14.3 // indirect go.etcd.io/bbolt v1.3.10 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect google.golang.org/grpc v1.67.1 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect nhooyr.io/websocket v1.8.6 // indirect diff --git a/tools/confix/go.sum b/tools/confix/go.sum index 32242dcba2a..6ec2ebd78df 100644 --- a/tools/confix/go.sum +++ b/tools/confix/go.sum @@ -763,8 +763,8 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= @@ -883,20 +883,20 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -981,8 +981,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/tools/cosmovisor/CHANGELOG.md b/tools/cosmovisor/CHANGELOG.md index ecf3e6f6045..3dd65d80559 100644 --- a/tools/cosmovisor/CHANGELOG.md +++ b/tools/cosmovisor/CHANGELOG.md @@ -43,6 +43,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/README.md b/tools/cosmovisor/README.md index e726239a896..3b5f722c5d1 100644 --- a/tools/cosmovisor/README.md +++ b/tools/cosmovisor/README.md @@ -7,22 +7,21 @@ sidebar_position: 1 `cosmovisor` is a process manager for Cosmos SDK application binaries that automates application binary switch at chain upgrades. It polls the `upgrade-info.json` file that is created by the x/upgrade module at upgrade height, and then can automatically download the new binary, stop the current binary, switch from the old binary to the new one, and finally restart the node with the new binary. -* [Cosmovisor](#cosmovisor) - * [Design](#design) - * [Contributing](#contributing) - * [Setup](#setup) +* [Design](#design) +* [Contributing](#contributing) +* [Setup](#setup) * [Installation](#installation) * [Command Line Arguments And Environment Variables](#command-line-arguments-and-environment-variables) * [Folder Layout](#folder-layout) - * [Usage](#usage) +* [Usage](#usage) * [Initialization](#initialization) * [Detecting Upgrades](#detecting-upgrades) * [Adding Upgrade Binary](#adding-upgrade-binary) - * [Preparing for an Upgrade](#preparing-for-an-upgrade) * [Auto-Download](#auto-download) - * [Example: SimApp Upgrade](#example-simapp-upgrade) + * [Preparing for an Upgrade](#preparing-for-an-upgrade) +* [Example: SimApp Upgrade](#example-simapp-upgrade) * [Chain Setup](#chain-setup) - * [Prepare Cosmovisor and Start the Chain](#prepare-cosmovisor-and-start-the-chain) + * [Prepare Cosmovisor and Start the Chain](#prepare-cosmovisor-and-start-the-chain) * [Update App](#update-app) ## Design diff --git a/tools/cosmovisor/args.go b/tools/cosmovisor/args.go index 08062c7a56b..733f561faf1 100644 --- a/tools/cosmovisor/args.go +++ b/tools/cosmovisor/args.go @@ -118,14 +118,19 @@ func (cfg *Config) UpgradeInfoBatchFilePath() 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. @@ -139,27 +144,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 } @@ -390,24 +392,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 7cdbd196396..f805288c9da 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, @@ -519,7 +521,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, }, { @@ -539,25 +541,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, }, { @@ -569,19 +571,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, }, { @@ -593,19 +595,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, }, { @@ -623,7 +625,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, }, { @@ -635,7 +637,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, }, { @@ -659,7 +661,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, }, { @@ -671,7 +673,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, }, { @@ -689,19 +691,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, }, { @@ -713,7 +715,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, }, { @@ -725,19 +727,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, }, { @@ -749,7 +751,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, }, { @@ -760,7 +762,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, }, } @@ -795,7 +797,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) @@ -825,7 +827,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: "", @@ -840,13 +842,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 5f78e2845c0..90a35ca286f 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 97a082db729..35a73348cc3 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 1cd85746f33..92b1af2e11c 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 f835b59e528..4da2ac24d84 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/go.mod b/tools/cosmovisor/go.mod index df644719ac1..b04d101494b 100644 --- a/tools/cosmovisor/go.mod +++ b/tools/cosmovisor/go.mod @@ -163,20 +163,20 @@ require ( go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.6.0 // indirect google.golang.org/api v0.192.0 // indirect google.golang.org/genproto v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.1 // indirect diff --git a/tools/cosmovisor/go.sum b/tools/cosmovisor/go.sum index 0fdc70f990e..2937d1f261b 100644 --- a/tools/cosmovisor/go.sum +++ b/tools/cosmovisor/go.sum @@ -1015,8 +1015,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1251,13 +1251,13 @@ golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1268,8 +1268,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1577,8 +1577,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/tools/cosmovisor/process_test.go b/tools/cosmovisor/process_test.go index 988bcf0a807..26ef82900fe 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/testdata/download/cosmovisor/genesis/bin/autod b/tools/cosmovisor/testdata/download/cosmovisor/genesis/bin/autod index c1b2348787a..c8d7e1e30fa 100755 --- a/tools/cosmovisor/testdata/download/cosmovisor/genesis/bin/autod +++ b/tools/cosmovisor/testdata/download/cosmovisor/genesis/bin/autod @@ -10,7 +10,7 @@ cat > "$3" < "$3" < I ``` - -## Implementing the Authentication Interface +### Implementing the Authentication Interface To implement the Authentication interface, an account must handle the execution of `MsgAuthenticate`. Here's an example of how to do this: @@ -80,33 +427,32 @@ func (a Account) RegisterExecuteHandlers(builder *accountstd.ExecuteBuilder) { } ``` -### Key Implementation Points +#### Key Implementation Points 1. **Sender Verification**: Always verify that the sender is the x/accounts module. This prevents unauthorized accounts from triggering authentication. 2. **Authentication Safety**: Ensure your authentication mechanism is secure: - Prevent replay attacks by making it impossible to reuse the same action with the same signature. - -#### Implementation example +##### Implementation example Please find an example [here](./defaults/base/account.go). -# Supporting Custom Accounts in the x/auth gRPC Server +## Supporting Custom Accounts in the x/auth gRPC Server -## Overview +### Overview The x/auth module provides a mechanism for custom account types to be exposed via its `Account` and `AccountInfo` gRPC -queries. This feature is particularly useful for ensuring compatibility with existing wallets that have not yet integrated +queries. This feature is particularly useful for ensuring compatibility with existing wallets that have not yet integrated with x/accounts but still need to parse account information post-migration. -## Implementation +### Implementation To support this feature, your custom account type needs to implement the `auth.QueryLegacyAccount` handler. Here are some important points to consider: 1. **Selective Implementation**: This implementation is not required for every account type. It's only necessary for accounts you want to expose through the x/auth gRPC `Account` and `AccountInfo` methods. 2. **Flexible Response**: The `info` field in the `QueryLegacyAccountResponse` is optional. If your custom account cannot be represented as a `BaseAccount`, you can leave this field empty. -## Example Implementation +### Example Implementation A concrete example of implementation can be found in `defaults/base/account.go`. Here's a simplified version: @@ -143,23 +489,23 @@ func (a Account) AuthRetroCompatibility(ctx context.Context, _ *authtypes.QueryL } ``` -## Usage Notes +### Usage Notes -* Implement this handler only for account types you want to expose via x/auth gRPC methods. -* The `info` field in the response can be nil if your account doesn't fit the `BaseAccount` structure. +- Implement this handler only for account types you want to expose via x/auth gRPC methods. +- The `info` field in the response can be nil if your account doesn't fit the `BaseAccount` structure. -# Genesis +## Genesis -## Creating accounts on genesis +### Creating accounts on genesis In order to create accounts at genesis, the `x/accounts` module allows developers to provide a list of genesis `MsgInit` messages that will be executed in the `x/accounts` genesis flow. The init messages are generated offline. You can also use the following CLI command to generate the -json messages: `simd accounts tx init [account type] [msg] --from me --genesis`. This will generate +json messages: `simd accounts tx init [account type] [msg] --from me --genesis`. This will generate a jsonified init message wrapped in an x/accounts `MsgInit`. -This follows the same initialization flow and rules that would happen if the chain is running. +This follows the same initialization flow and rules that would happen if the chain is running. The only concrete difference is that this is happening at the genesis block. For example, given the following `genesis.json` file: diff --git a/x/accounts/defaults/base/go.mod b/x/accounts/defaults/base/go.mod index ea0ef43187d..50c7b6e3ca6 100644 --- a/x/accounts/defaults/base/go.mod +++ b/x/accounts/defaults/base/go.mod @@ -13,7 +13,7 @@ require ( github.com/cosmos/gogoproto v1.7.0 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 github.com/stretchr/testify v1.9.0 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 ) require ( @@ -147,14 +147,14 @@ require ( go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect diff --git a/x/accounts/defaults/base/go.sum b/x/accounts/defaults/base/go.sum index 25dd6bf3bb3..72343047ffd 100644 --- a/x/accounts/defaults/base/go.sum +++ b/x/accounts/defaults/base/go.sum @@ -510,8 +510,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -590,19 +590,19 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -661,8 +661,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/x/accounts/defaults/lockup/TUTORIAL.md b/x/accounts/defaults/lockup/TUTORIAL.md new file mode 100644 index 00000000000..1a308cbcea7 --- /dev/null +++ b/x/accounts/defaults/lockup/TUTORIAL.md @@ -0,0 +1,243 @@ +# Using lockup account on Cosmos sdk + +* [Setup](#setup) +* [Init](#init) +* [Execution](#execution) + * [Delegate](#delegate) + * [Undelegate](#undelegate) + * [Withdraw reward](#withdraw-reward) + * [Withdraw unlocked token](#withdraw-unlocked-token) + * [Send coins](#send-coins) +* [Query](#query) + * [Query account info](#query-account-info) + * [Query periodic lockup account locking periods](#query-periodic-lockup-account-locking-periods) + +To learn more about lockup account, please also check out [readme](./README.md) + +## Setup + +To create a lockup account we need 2 wallets (newly created or use any of the existing wallet that you have) one for the creator and one for the owner of the lockup account. + +```bash +simd keys add creator +simd keys add owner +``` + +## Init + +Normally the creator must have enough token to grant to the lockup account during the lockup account init process. The owner wallet should be associated with the individual that the creator want to grant the fund to. + +Now, the creator can craft the lockup account init messages. This message depend on what type of lockup account the creator want to create. +For continous, delayed, permanent locking account: + +```json +{ + "owner": "cosmos1vaqh39cdex9sgr69ef0tdln5cn0hdyd3s0lx45", + "end_time": 1495793860 + "start_time": 1465793854 +} +``` + +:::info +`start_time` is only needed for continous locking account init process. For the other two, you dont have to set it in. Error will returned if `start_time` is not provided when creating continous locking account* +::: + +For periodic locking account: + +```json + { + "owner": "cosmos1vaqh39cdex9sgr69ef0tdln5cn0hdyd3s0lx45", + "locking_periods": [ + { + "length": 84600 + "amount": { + "denom": "stake", + "amount": 2000 + } + }, + { + "length": 84600 + "amount": { + "denom": "stake", + "amount": 1500 + } + } + ] + "start_time": 1465793854 + } +``` + +Periodic locking account locking duration is the combines of all the period length from `locking_periods`. + +The `owner` field takes a string while `start_time` and `end_time` takes a timestamp as value. `locking_periods` are an array of `period`s which consist of 2 field: `length` for the duration of that period and the `amount` that will be release after such duration. + +To initialize the account, we have to run the accounts init command passing the account type and the json string for the init message. + +```bash +initcontents=$(cat init.json) +simd tx accounts init $initcontents --from creator +``` + +Whereas the available `lockup_type` options are: + +* continuous-locking-account + +* delayed-locking-account + +* periodic-locking-account + +* permanent-locking-account + +If success, we'll check the tx result for the lockup account address. You can send token to it like a normal account. + +## Execution + +To execute a message, we can use the command below: + +```bash +msgcontents=$(cat msg.json) +simd tx accounts execute $msgcontents --from owner +``` + +Whereas `execute-msg-type-url` and `msgcontents` corresponds to lockup account available executions, which are: + +### Delegate + +The execute message type url for this execution is `cosmos.accounts.defaults.lockup.MsgDelegate`. + +Example of json file: + +```json +{ + "sender": "cosmos1vaqh39cdex9sgr69ef0tdln5cn0hdyd3s0lx45", + "validator_address": "cosmosvaloper1vaqh39cdex9sgr69ef0tdln5cn0hdyd3s0lx45", + "amount": { + "amount": 100 + "denom": "stake" + } +} +``` + +:::warning +The `sender` field are the address of the owner of the lockup account. If the sender is not the owner an error will be returned. +::: + +### Undelegate + +The execute message type url for this execution is `cosmos.accounts.defaults.lockup.MsgUndelegate`. + +Example of json file: + +```json +{ + "sender": "cosmos1vaqh39cdex9sgr69ef0tdln5cn0hdyd3s0lx45", + "validator_address": "cosmosvaloper1vaqh39cdex9sgr69ef0tdln5cn0hdyd3s0lx45", + "amount": { + "amount": 100 + "denom": "stake" + } +} +``` + +:::warning +The `sender` field are the address of the owner of the lockup account. If the sender is not the owner an error will be returned. +::: + +### Withdraw reward + +The execute message type url for this execution is `cosmos.accounts.defaults.lockup.MsgWithdrawReward`. + +Example of json file: + +```json +{ + "sender": "cosmos1vaqh39cdex9sgr69ef0tdln5cn0hdyd3s0lx45", + "validator_address": "cosmosvaloper1vaqh39cdex9sgr69ef0tdln5cn0hdyd3s0lx45", +} +``` + +:::warning +The `sender` field are the address of the owner of the lockup account. If the sender is not the owner an error will be returned. +::: + +### Withdraw unlocked token + +The execute message type url for this execution is `cosmos.accounts.defaults.lockup.MsgWithdraw`. + +Example of json file: + +```json +{ + // lockup account owner address + "withdrawer": "cosmos1vaqh39cdex9sgr69ef0tdln5cn0hdyd3s0lx46", + // withdraw to an account of choice + "to_address": "cosmos1vaqh39cdex9sgr69ef0tdln5cn0hdyd3s0lx47", + "denoms": ["stake"] +} +``` + +:::warning +The `withdrawer` field are the address of the owner of the lockup account. If the sender is not the owner an error will be returned. +::: + +### Send coins + +The execute message type url for this execution is `cosmos.accounts.defaults.lockup.MsgSend`. + +Example of json file: + +```json +{ + "sender": "cosmos1vaqh39cdex9sgr69ef0tdln5cn0hdyd3s0lx45", + "to_address": "cosmos1vaqh39cdex9sgr69ef0tdln5cn0hdyd3s0lx46", + "amount": { + "amount": 100 + "denom": "stake" + } +} +``` + +:::warning +The `sender` field are the address of the owner of the lockup account. If the sender is not the owner an error will be returned. +::: + +## Query + +To query a lockup account state, we can use the command below: + +```bash +querycontents=$(cat query.json) +simd tx accounts query $querycontents --from owner +``` + +### Query account info + +The query request type url for this query is `cosmos.accounts.defaults.lockup.QueryLockupAccountInfoRequest`. And query json file can be an empty object since `QueryLockupAccountInfoRequest` does not required an input. + +Account informations including: + +* original locked amount + +* delegated amount that are locked + +* delegated amount that are free + +* start and end time + +* owner address + +* current locked and unlocked amount + +### Query periodic lockup account locking periods + +:::info +Note, can only be queried from a periodic lockup account +::: + +The query request type url for this query is `cosmos.accounts.defaults.lockup.QueryLockingPeriodsRequest`. And query json file can be an empty object since `QueryLockingPeriodsRequest` does not required an input. + +Locking periods including: + +* List of period with its duration and amount + + diff --git a/x/accounts/defaults/lockup/go.mod b/x/accounts/defaults/lockup/go.mod index 2f66fafe70f..fcda08a89ad 100644 --- a/x/accounts/defaults/lockup/go.mod +++ b/x/accounts/defaults/lockup/go.mod @@ -125,18 +125,18 @@ require ( go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect google.golang.org/grpc v1.67.1 // indirect - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.1 // indirect diff --git a/x/accounts/defaults/lockup/go.sum b/x/accounts/defaults/lockup/go.sum index 3609dca29bf..5173c72592b 100644 --- a/x/accounts/defaults/lockup/go.sum +++ b/x/accounts/defaults/lockup/go.sum @@ -449,8 +449,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -516,18 +516,18 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -576,8 +576,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/x/accounts/defaults/multisig/go.mod b/x/accounts/defaults/multisig/go.mod index 3d2600c39a0..0d048b168cd 100644 --- a/x/accounts/defaults/multisig/go.mod +++ b/x/accounts/defaults/multisig/go.mod @@ -12,7 +12,7 @@ require ( github.com/cosmos/cosmos-sdk v0.53.0 github.com/cosmos/gogoproto v1.7.0 github.com/stretchr/testify v1.9.0 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 ) require ( @@ -147,14 +147,14 @@ require ( go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect diff --git a/x/accounts/defaults/multisig/go.sum b/x/accounts/defaults/multisig/go.sum index 25dd6bf3bb3..72343047ffd 100644 --- a/x/accounts/defaults/multisig/go.sum +++ b/x/accounts/defaults/multisig/go.sum @@ -510,8 +510,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -590,19 +590,19 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -661,8 +661,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/x/accounts/go.mod b/x/accounts/go.mod index 3232de28c02..75a49ef00dd 100644 --- a/x/accounts/go.mod +++ b/x/accounts/go.mod @@ -15,7 +15,7 @@ require ( github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 google.golang.org/grpc v1.67.1 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 ) require ( @@ -151,14 +151,14 @@ require ( go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect diff --git a/x/accounts/go.sum b/x/accounts/go.sum index 7588b565a0d..4d7d4939fa7 100644 --- a/x/accounts/go.sum +++ b/x/accounts/go.sum @@ -511,8 +511,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -593,19 +593,19 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -665,8 +665,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/x/accounts/internal/prefixstore/prefixstore.go b/x/accounts/internal/prefixstore/prefixstore.go index 3121691a25d..ef84b70703d 100644 --- a/x/accounts/internal/prefixstore/prefixstore.go +++ b/x/accounts/internal/prefixstore/prefixstore.go @@ -21,7 +21,7 @@ func New(store store.KVStore, prefix []byte) store.KVStore { var _ store.KVStore = Store{} -// Store is similar with cometbft/cometbft/libs/db/prefix_db +// Store is similar with cometbft/cometbft-db/blob/v1.0.1/prefixdb.go // both gives access only to the limited subset of the store // for convenience or safety type Store struct { @@ -63,7 +63,7 @@ func (s Store) Set(key, value []byte) error { func (s Store) Delete(key []byte) error { return s.parent.Delete(s.key(key)) } // Implements KVStore -// Check https://github.com/cometbft/cometbft/blob/master/libs/db/prefix_db.go#L106 +// Check https://github.com/cometbft/cometbft-db/blob/v1.0.1/prefixdb.go#L109 func (s Store) Iterator(start, end []byte) (store.Iterator, error) { newstart := cloneAppend(s.prefix, start) @@ -83,7 +83,7 @@ func (s Store) Iterator(start, end []byte) (store.Iterator, error) { } // ReverseIterator implements KVStore -// Check https://github.com/cometbft/cometbft/blob/master/libs/db/prefix_db.go#L129 +// Check https://github.com/cometbft/cometbft-db/blob/v1.0.1/prefixdb.go#L132 func (s Store) ReverseIterator(start, end []byte) (store.Iterator, error) { newstart := cloneAppend(s.prefix, start) @@ -180,7 +180,7 @@ func (pi *prefixIterator) Error() error { return nil } -// copied from github.com/cometbft/cometbft/libs/db/prefix_db.go +// copied from github.com/cometbft/cometbft-db/blob/v1.0.1/prefixdb.go func stripPrefix(key, prefix []byte) []byte { if len(key) < len(prefix) || !bytes.Equal(key[:len(prefix)], prefix) { panic("should not happen") diff --git a/x/auth/migrations/legacytx/stdtx_test.go b/x/auth/migrations/legacytx/stdtx_test.go index 83909d45d6e..27156448d9b 100644 --- a/x/auth/migrations/legacytx/stdtx_test.go +++ b/x/auth/migrations/legacytx/stdtx_test.go @@ -44,7 +44,7 @@ func TestStdSignBytes(t *testing.T) { Amount: []*basev1beta1.Coin{{Denom: "atom", Amount: "150"}}, GasLimit: 100000, } - msgStr := fmt.Sprintf(`{"type":"testpb/TestMsg","value":{"decField":"0","signers":["%s"]}}`, addr) + msgStr := fmt.Sprintf(`{"type":"testpb/TestMsg","value":{"decField":"0.000000000000000000","signers":["%s"]}}`, addr) tests := []struct { name string args args diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index 423bca3a087..2b1c866e8e0 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -112,7 +112,7 @@ var marshalOption = proto.MarshalOptions{ func (w *builder) getTx() (*gogoTxWrapper, error) { anyMsgs, err := msgsV1toAnyV2(w.msgs) if err != nil { - return nil, err + return nil, fmt.Errorf("unable to convert messages: %w", err) } body := &txv1beta1.TxBody{ Messages: anyMsgs, @@ -136,12 +136,12 @@ func (w *builder) getTx() (*gogoTxWrapper, error) { bodyBytes, err := marshalOption.Marshal(body) if err != nil { - return nil, err + return nil, fmt.Errorf("unable to marshal body: %w", err) } authInfoBytes, err := marshalOption.Marshal(authInfo) if err != nil { - return nil, err + return nil, fmt.Errorf("unable to marshal auth info: %w", err) } txRawBytes, err := marshalOption.Marshal(&txv1beta1.TxRaw{ @@ -150,12 +150,12 @@ func (w *builder) getTx() (*gogoTxWrapper, error) { Signatures: w.signatures, }) if err != nil { - return nil, err + return nil, fmt.Errorf("unable to marshal tx raw: %w", err) } decodedTx, err := w.decoder.Decode(txRawBytes) if err != nil { - return nil, err + return nil, fmt.Errorf("unable to decode tx: %w", err) } return newWrapperFromDecodedTx(w.addressCodec, w.codec, decodedTx) diff --git a/x/auth/vesting/types/period_test.go b/x/auth/vesting/types/period_test.go new file mode 100644 index 00000000000..636e6255a0d --- /dev/null +++ b/x/auth/vesting/types/period_test.go @@ -0,0 +1,49 @@ +package types_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" +) + +func TestPeriodsString(t *testing.T) { + tests := []struct { + name string + periods types.Periods + want string + }{ + { + "empty slice", + nil, + "Vesting Periods:", + }, + { + "1 period", + types.Periods{ + {Length: int64(12 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin("feeatom", 500), sdk.NewInt64Coin("statom", 50)}}, + }, + "Vesting Periods:\n\t\t" + `length:43200 amount: amount:`, + }, + { + "many", + types.Periods{ + {Length: int64(12 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}}, + {Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}}, + {Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 100), sdk.NewInt64Coin(stakeDenom, 15)}}, + }, + "Vesting Periods:\n\t\t" + `length:43200 amount: amount: , ` + + `length:21600 amount: amount: , ` + + `length:21600 amount: amount:`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.periods.String() + if got != tt.want { + t.Fatalf("Mismatch in values:\n\tGot: %q\n\tWant: %q", got, tt.want) + } + }) + } +} diff --git a/x/authz/README.md b/x/authz/README.md index c4752c2eddc..405bd5427e6 100644 --- a/x/authz/README.md +++ b/x/authz/README.md @@ -38,12 +38,12 @@ The `x/authz` module defines interfaces and messages grant authorizations to per on behalf of one account to other accounts. The design is defined in the [ADR 030](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-030-authz-module.md). A *grant* is an allowance to execute a Msg by the grantee on behalf of the granter. -Authorization is an interface that must be implemented by a concrete authorization logic to validate and execute grants. Authorizations are extensible and can be defined for any Msg service method even outside of the module where the Msg method is defined. See the `SendAuthorization` example in the next section for more details. +Authorization is an interface that must be implemented by a concrete authorization logic to validate and execute grants. Authorizations are extensible and can be defined for any Msg service method, even if the Msg method is defined outside of the module. See the `SendAuthorization` example in the next section for more details. -**Note:** The authz module is different from the [auth (authentication)](../modules/auth/) module that is responsible for specifying the base transaction and account types. +**Note:** The authz module is different from the [auth (authentication)](../modules/auth/) module, which is responsible for specifying the base transaction and account types. ```go reference -https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/x/authz/authorizations.go#L11-L25 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/authz/authorizations.go#L14-L28 ``` ### Built-in Authorizations @@ -55,11 +55,11 @@ The Cosmos SDK `x/authz` module comes with following authorization types: `GenericAuthorization` implements the `Authorization` interface that gives unrestricted permission to execute the provided Msg on behalf of granter's account. ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/authz/v1beta1/authz.proto#L14-L22 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/authz/proto/cosmos/authz/v1beta1/authz.proto#L14-L22 ``` ```go reference -https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/x/authz/generic_authorization.go#L16-L29 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/authz/generic_authorization.go#L18-L34 ``` * `msg` stores Msg type URL. @@ -72,11 +72,11 @@ https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/x/authz/generic_authorizat * It takes an (optional) `AllowList` that specifies to which addresses a grantee can send token. ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/bank/v1beta1/authz.proto#L11-L30 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/bank/proto/cosmos/bank/v1beta1/authz.proto#L11-L29 ``` ```go reference -https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/x/bank/types/send_authorization.go#L29-L62 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/bank/types/send_authorization.go#L33-L73 ``` * `spend_limit` keeps track of how many coins are left in the authorization. @@ -84,21 +84,21 @@ https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/x/bank/types/send_authoriz #### StakeAuthorization -`StakeAuthorization` implements the `Authorization` interface for messages in the [staking module](https://docs.cosmos.network/main/build/modules/staking). It takes an `AuthorizationType` to specify whether you want to authorise delegating, undelegating or redelegating (i.e. these have to be authorised separately). It also takes an optional `MaxTokens` that keeps track of a limit to the amount of tokens that can be delegated/undelegated/redelegated. If left empty, the amount is unlimited. Additionally, this Msg takes an `AllowList` or a `DenyList`, which allows you to select which validators you allow or deny grantees to stake with. +`StakeAuthorization` implements the `Authorization` interface for messages in the [staking module](https://docs.cosmos.network/main/build/modules/staking). It takes an `AuthorizationType` to specify whether you want to authorize delegation, undelegation, redelegation or cancel unbonding delegation, each of which must be authorized separately. It also takes an optional `MaxTokens` that keeps track of a limit to the amount of tokens that can be delegated/undelegated/redelegated. If left empty, the amount is unlimited. Additionally, this Msg takes an `AllowList` or a `DenyList`, enabling you to specify which validators the grantee can or cannot stake with. ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/staking/v1beta1/authz.proto#L11-L35 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/staking/proto/cosmos/staking/v1beta1/authz.proto#L11-L34 ``` ```go reference -https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/x/staking/types/authz.go#L15-L35 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/staking/types/authz.go#L78-L166 ``` ### Gas -In order to prevent DoS attacks, granting `StakeAuthorization`s with `x/authz` incurs gas. `StakeAuthorization` allows you to authorize another account to delegate, undelegate, or redelegate to validators. The authorizer can define a list of validators they allow or deny delegations to. The Cosmos SDK iterates over these lists and charge 10 gas for each validator in both of the lists. +To prevent DoS attacks, granting `StakeAuthorization`s with `x/authz` incurs gas. `StakeAuthorization` allows you to authorize another account to delegate, undelegate, or redelegate tokens to validators. The granter can define a list of validators for which they allow or deny delegations. The Cosmos SDK then iterates over these lists and charge 10 gas for each validator included in both lists. -Since the state maintaining a list for granter, grantee pair with same expiration, we are iterating over the list to remove the grant (in case of any revoke of particular `msgType`) from the list and we are charging 20 gas per iteration. +Since the state maintains a list of granter-grantee pairs with same expiration, we iterate over this list to remove the grant from the list (in case of any revoke of particular `msgType`), charging 20 gas for each iteration. ## State @@ -111,17 +111,17 @@ Grants are identified by combining granter address (the address bytes of the gra The grant object encapsulates an `Authorization` type and an expiration timestamp: ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/authz/v1beta1/authz.proto#L24-L32 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/authz/proto/cosmos/authz/v1beta1/authz.proto#L24-L32 ``` ### GrantQueue We are maintaining a queue for authz pruning. Whenever a grant is created, an item will be added to `GrantQueue` with a key of expiration, granter, grantee. -In `EndBlock` (which runs for every block) we continuously check and prune the expired grants by forming a prefix key with current blocktime that passed the stored expiration in `GrantQueue`, we iterate through all the matched records from `GrantQueue` and delete them from the `GrantQueue` & `Grant`s store. +In `EndBlock` (which runs for every block) we continuously check and prune the expired grants by forming a prefix key with current blocktime that passed the stored expiration in `GrantQueue`, we iterate through all the matched records from `GrantQueue` and delete maximum of 200 grants from the `GrantQueue` & `Grant`s store for each run. ```go reference -https://github.com/cosmos/cosmos-sdk/blob/5f4ddc6f80f9707320eec42182184207fff3833a/x/authz/keeper/keeper.go#L378-L403 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/authz/keeper/keeper.go#L479-L520 ``` * GrantQueue: `0x02 | expiration_bytes | granter_address_len (1 byte) | granter_address_bytes | grantee_address_len (1 byte) | grantee_address_bytes -> ProtocolBuffer(GrantQueueItem)` @@ -129,7 +129,7 @@ https://github.com/cosmos/cosmos-sdk/blob/5f4ddc6f80f9707320eec42182184207fff383 The `expiration_bytes` are the expiration date in UTC with the format `"2006-01-02T15:04:05.000000000"`. ```go reference -https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/x/authz/keeper/keys.go#L77-L93 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/authz/keeper/keys.go#L84-L100 ``` The `GrantQueueItem` object contains the list of type urls between granter and grantee that expire at the time indicated in the key. @@ -146,7 +146,7 @@ If there is already a grant for the `(granter, grantee, Authorization)` triple, An authorization grant for authz `MsgGrant` is not allowed and will return an error. This is for preventing user from accidentally authorizing their entire account to a different account. ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/authz/v1beta1/tx.proto#L35-L45 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/authz/proto/cosmos/authz/v1beta1/tx.proto#L45-L55 ``` The message handling should fail if: @@ -161,7 +161,7 @@ The message handling should fail if: A grant can be removed with the `MsgRevoke` message. ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/authz/v1beta1/tx.proto#L69-L78 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/authz/proto/cosmos/authz/v1beta1/tx.proto#L79-L88 ``` The message handling should fail if: @@ -176,7 +176,7 @@ NOTE: The `MsgExec` message removes a grant if the grant has expired. The `MsgRevokeAll` message revokes all grants issued by the specified granter. This is useful for quickly removing all authorizations granted by a single granter without specifying individual message types or grantees. ```protobuf reference -https://github.com/cosmos/cosmos-sdk/tree/main/x/authz/proto/cosmos/authz/v1beta1/tx.proto#L93-L100 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/authz/proto/cosmos/authz/v1beta1/tx.proto#L93-L100 ``` The message handling should fail if: @@ -189,7 +189,7 @@ The message handling should fail if: When a grantee wants to execute a transaction on behalf of a granter, they must send `MsgExec`. ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/authz/v1beta1/tx.proto#L52-L63 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/authz/proto/cosmos/authz/v1beta1/tx.proto#L60-L72 ``` The message handling should fail if: @@ -314,6 +314,20 @@ Example: simd tx authz revoke cosmos1.. /cosmos.bank.v1beta1.MsgSend --from=cosmos1.. ``` +##### revoke-all + +The `revoke-all` command allows a granter to revoke all authorizations created by the granter. + +```bash +simd tx authz revoke-all --from=[granter] [flags] +``` + +Example: + +```bash +simd tx authz revoke-all --from=cosmos1.. +``` + ### gRPC A user can query the `authz` module using gRPC endpoints. @@ -335,27 +349,6 @@ grpcurl -plaintext \ cosmos.authz.v1beta1.Query/Grants ``` -Example Output: - -```bash -{ - "grants": [ - { - "authorization": { - "@type": "/cosmos.bank.v1beta1.SendAuthorization", - "spendLimit": [ - { - "denom":"stake", - "amount":"100" - } - ] - }, - "expiration": "2022-01-01T00:00:00Z" - } - ] -} -``` - ### REST A user can query the `authz` module using REST endpoints. @@ -369,25 +362,3 @@ Example: ```bash curl "localhost:1317/cosmos/authz/v1beta1/grants?granter=cosmos1..&grantee=cosmos1..&msg_type_url=/cosmos.bank.v1beta1.MsgSend" ``` - -Example Output: - -```bash -{ - "grants": [ - { - "authorization": { - "@type": "/cosmos.bank.v1beta1.SendAuthorization", - "spend_limit": [ - { - "denom": "stake", - "amount": "100" - } - ] - }, - "expiration": "2022-01-01T00:00:00Z" - } - ], - "pagination": null -} -``` diff --git a/x/authz/go.mod b/x/authz/go.mod index a40b44423b9..1f83435bb72 100644 --- a/x/authz/go.mod +++ b/x/authz/go.mod @@ -23,7 +23,7 @@ require ( github.com/stretchr/testify v1.9.0 google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 google.golang.org/grpc v1.67.1 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 ) require ( @@ -145,14 +145,14 @@ require ( go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect diff --git a/x/authz/go.sum b/x/authz/go.sum index 7588b565a0d..4d7d4939fa7 100644 --- a/x/authz/go.sum +++ b/x/authz/go.sum @@ -511,8 +511,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -593,19 +593,19 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -665,8 +665,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/x/bank/CHANGELOG.md b/x/bank/CHANGELOG.md index f226eac8850..7cfd8ed0410 100644 --- a/x/bank/CHANGELOG.md +++ b/x/bank/CHANGELOG.md @@ -35,13 +35,14 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [#18636](https://github.com/cosmos/cosmos-sdk/pull/18636) `SendCoinsFromModuleToAccount`, `SendCoinsFromModuleToModule`, `SendCoinsFromAccountToModule`, `DelegateCoinsFromAccountToModule`, `UndelegateCoinsFromModuleToAccount`, `MintCoins` and `BurnCoins` methods now returns an error instead of panicking if any module accounts does not exist or unauthorized. * [#20517](https://github.com/cosmos/cosmos-sdk/pull/20517) `SendCoins` now checks for `SendRestrictions` before instead of after deducting coins using `subUnlockedCoins`. * [#20354](https://github.com/cosmos/cosmos-sdk/pull/20354) Reduce the number of `ValidateDenom` calls in `bank.SendCoins`. +* [#21976](https://github.com/cosmos/cosmos-sdk/pull/21976) Resolve a footgun by swapping send restrictions check in `InputOutputCoins` before coin deduction. ### Bug Fixes * [#21407](https://github.com/cosmos/cosmos-sdk/pull/21407) Fix handling of negative spendable balances. - * The `SpendableBalances` query now correctly reports spendable balances when one or more denoms are negative (used to report all zeros). Also, this query now looks up only the balances for the requested page. - * The `SpendableCoins` keeper method now returns the positive spendable balances even when one or more denoms have more locked than available (used to return an empty `Coins`). - * The `SpendableCoin` keeper method now returns a zero coin if there's more locked than available (used to return a negative coin). + * The `SpendableBalances` query now correctly reports spendable balances when one or more denoms are negative (used to report all zeros). Also, this query now looks up only the balances for the requested page. + * The `SpendableCoins` keeper method now returns the positive spendable balances even when one or more denoms have more locked than available (used to return an empty `Coins`). + * The `SpendableCoin` keeper method now returns a zero coin if there's more locked than available (used to return a negative coin). ### API Breaking Changes diff --git a/x/bank/README.md b/x/bank/README.md index 5ac389feb5a..f6f77f1f947 100644 --- a/x/bank/README.md +++ b/x/bank/README.md @@ -10,15 +10,13 @@ This document specifies the bank module of the Cosmos SDK. The bank module is responsible for handling multi-asset coin transfers between accounts and tracking special-case pseudo-transfers which must work differently -with particular kinds of accounts (notably delegating/undelegating for vesting +with particular kinds of accounts (notably delegating/undelegating for legacy vesting accounts). It exposes several interfaces with varying capabilities for secure interaction with other modules which must alter user balances. In addition, the bank module tracks and provides query support for the total supply of all assets used in the application. -This module is used in the Cosmos Hub. - ## Contents * [Supply](#supply) @@ -51,9 +49,9 @@ The `supply` functionality: ### Total Supply -The total `Supply` of the network is equal to the sum of all coins from the -account. The total supply is updated every time a `Coin` is minted (eg: as part -of the inflation mechanism) or burned (eg: due to slashing or if a governance +The total `Supply` of the network is equal to the sum of all coins from all +accounts within a chain. The total supply is updated every time a `Coin` is minted +(eg: as part of the inflation mechanism) or burned (eg: due to slashing or if a governance proposal is vetoed). ## Module Accounts @@ -83,13 +81,12 @@ type ModuleAccount interface { > **WARNING!** > Any module or message handler that allows either direct or indirect sending of funds must explicitly guarantee those funds cannot be sent to module accounts (unless allowed). -The supply `Keeper` also introduces new wrapper functions for the auth `Keeper` -and the bank `Keeper` that are related to `ModuleAccount`s in order to be able -to: +The supply `Keeper` interface also introduces new wrapper functions for the auth `Keeper` +and the bank `SendKeeper` in order to be able to: -* Get and set `ModuleAccount`s by providing the `Name`. -* Send coins from and to other `ModuleAccount`s or standard `Account`s - (`BaseAccount` or `VestingAccount`) by passing only the `Name`. +* Get `ModuleAccount`s by providing its `Name`. +* Send coins from and to other `ModuleAccount`s by passing only the `Name` or standard `Account`s + (`BaseAccount` or legacy `VestingAccount`). * `Mint` or `Burn` coins for a `ModuleAccount` (restricted to its permissions). ### Permissions @@ -122,6 +119,7 @@ aforementioned state: * Denom Metadata Index: `0x1 | byte(denom) -> ProtocolBuffer(Metadata)` * Balances Index: `0x2 | byte(address length) | []byte(address) | []byte(balance.Denom) -> ProtocolBuffer(balance)` * Reverse Denomination to Address Index: `0x03 | byte(denom) | 0x00 | []byte(address) -> 0` +* Send enabled Denoms: `0x4 | string -> bool` ## Params @@ -131,7 +129,7 @@ it can be updated with governance or the address with authority. * Params: `0x05 | ProtocolBuffer(Params)` ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/bank/v1beta1/bank.proto#L12-L23 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/bank/proto/cosmos/bank/v1beta1/bank.proto#L12-L23 ``` ## Keepers @@ -142,11 +140,11 @@ should use the least-permissive interface that provides the functionality they require. Best practices dictate careful review of `bank` module code to ensure that -permissions are limited in the way that you expect. +permissions are limited in the way that you expected. ### Denied Addresses -The `x/bank` module accepts a map of addresses that are considered blocklisted +The `x/bank` module accepts a map of addresses (`blockedAddrs`) that are considered blocklisted from directly and explicitly receiving funds through means such as `MsgSend` and `MsgMultiSend` and direct API calls like `SendCoinsFromModuleToAccount`. @@ -160,7 +158,7 @@ By providing the `x/bank` module with a blocklisted set of addresses, an error o #### Input -An input of a multiparty transfer +An input of a multi-send transaction ```protobuf // Input models transaction input. @@ -172,7 +170,7 @@ message Input { #### Output -An output of a multiparty transfer. +An output of a multi-send transaction. ```protobuf // Output models transaction outputs. @@ -195,8 +193,8 @@ type Keeper interface { SendKeeper WithMintCoinsRestriction(MintingRestrictionFn) BaseKeeper - InitGenesis(context.Context, *types.GenesisState) - ExportGenesis(context.Context) *types.GenesisState + InitGenesis(context.Context, *types.GenesisState) error + ExportGenesis(context.Context) (*types.GenesisState, error) GetSupply(ctx context.Context, denom string) sdk.Coin HasSupply(ctx context.Context, denom string) bool @@ -213,14 +211,11 @@ type Keeper interface { DelegateCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error UndelegateCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error MintCoins(ctx context.Context, moduleName string, amt sdk.Coins) error - BurnCoins(ctx context.Context, moduleName string, amt sdk.Coins) error + BurnCoins(ctx context.Context, address []byte, amt sdk.Coins) error DelegateCoins(ctx context.Context, delegatorAddr, moduleAccAddr sdk.AccAddress, amt sdk.Coins) error UndelegateCoins(ctx context.Context, moduleAccAddr, delegatorAddr sdk.AccAddress, amt sdk.Coins) error - // GetAuthority gets the address capable of executing governance proposal messages. Usually the gov module account. - GetAuthority() string - types.QueryServer } ``` @@ -274,17 +269,18 @@ Both functions compose the provided restriction with any previously provided res `AppendSendRestriction` adds the provided restriction to be run after any previously provided send restrictions. `PrependSendRestriction` adds the restriction to be run before any previously provided send restrictions. The composition will short-circuit when an error is encountered. I.e. if the first one returns an error, the second is not run. +Send restrictions can also be cleared by using `ClearSendRestriction`. -During `SendCoins`, the send restriction is applied before coins are removed from the from address and adding them to the to address. -During `InputOutputCoins`, the send restriction is applied after the input coins are removed and once for each output before the funds are added. - -A send restriction function should make use of a custom value in the context to allow bypassing that specific restriction. +During `SendCoins`, the send restriction is applied before coins are removed from the `from_address` and adding them to the `to_address`. +During `InputOutputCoins`, the send restriction is applied before the input coins are removed, once for each output before the funds are added. Send Restrictions are not placed on `ModuleToAccount` or `ModuleToModule` transfers. This is done due to modules needing to move funds to user accounts and other module accounts. This is a design decision to allow for more flexibility in the state machine. The state machine should be able to move funds between module accounts and user accounts without restrictions. Secondly this limitation would limit the usage of the state machine even for itself. users would not be able to receive rewards, not be able to move funds between module accounts. In the case that a user sends funds from a user account to the community pool and then a governance proposal is used to get those tokens into the users account this would fall under the discretion of the app chain developer to what they would like to do here. We can not make strong assumptions here. -Thirdly, this issue could lead into a chain halt if a token is disabled and the token is moved in the begin/endblock. This is the last reason we see the current change and more damaging then beneficial for users. +Thirdly, this issue could lead into a chain halt if a token is disabled and the token is moved in the begin/endblock. This is the last reason we see the current change as they are more damaging then beneficial for users. + +A send restriction function should make use of a custom value in the context to allow bypassing that specific restriction. For example, in your module's keeper package, you'd define the send restriction function: ```golang @@ -337,7 +333,8 @@ func HasBypass(ctx context.Context) bool { } ``` -Now, anywhere where you want to use `SendCoins` or `InputOutputCoins`, but you don't want your send restriction applied: +Now, anywhere where you want to use `SendCoins` or `InputOutputCoins` but you don't want your send restriction applied +you just need to apply custom value in the context: ```golang func (k Keeper) DoThing(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) error { @@ -375,35 +372,35 @@ type ViewKeeper interface { Send coins from one address to another. ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/bank/v1beta1/tx.proto#L38-L53 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/bank/proto/cosmos/bank/v1beta1/tx.proto#L44-L59 ``` The message will fail under the following conditions: * The coins do not have sending enabled -* The `to` address is restricted +* The `to_address` is restricted ### MsgMultiSend -Send coins from one sender and to a series of different address. If any of the receiving addresses do not correspond to an existing account, a new account is created. +Send coins from one sender and to a series of different address. ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/bank/v1beta1/tx.proto#L58-L69 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/bank/proto/cosmos/bank/v1beta1/tx.proto#L65-L75 ``` The message will fail under the following conditions: * Any of the coins do not have sending enabled -* Any of the `to` addresses are restricted +* Any of the `to_address` are restricted * Any of the coins are locked -* The inputs and outputs do not correctly correspond to one another +* The inputs and outputs do not correctly correspond to one another (eg: total_in not equal to total_out) ### MsgUpdateParams The `bank` module params can be updated through `MsgUpdateParams`, which can be done using governance proposal. The signer will always be the `gov` module account address. ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/bank/v1beta1/tx.proto#L74-L88 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/bank/proto/cosmos/bank/v1beta1/tx.proto#L81-L93 ``` The message handling can fail if: @@ -412,30 +409,30 @@ The message handling can fail if: ### MsgSetSendEnabled -Used with the x/gov module to set create/edit SendEnabled entries. +Used with the x/gov module to create or edit SendEnabled entries. ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/bank/v1beta1/tx.proto#L96-L117 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/bank/proto/cosmos/bank/v1beta1/tx.proto#L106-L122 ``` The message will fail under the following conditions: -* The authority is not a decodable address. -* The authority is not x/gov module's address. -* There are multiple SendEnabled entries with the same Denom. -* One or more SendEnabled entries has an invalid Denom. +* The authority is not a decodable address +* The authority is not x/gov module's address +* There are multiple SendEnabled entries with the same Denom +* One or more SendEnabled entries has an invalid Denom ### MsgBurn Used to burn coins from an account. The coins are removed from the account and the total supply is reduced. ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/1af000b3ef6296f9928caf494fe5bb812990f22d/proto/cosmos/bank/v1beta1/tx.proto#L131-L148 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/bank/proto/cosmos/bank/v1beta1/tx.proto#L130-L139 ``` This message will fail under the following conditions: -* The signer is not present +* The `from_address` is not a decodable address * The coins are not spendable * The coins are not positive * The coins are not valid @@ -451,20 +448,20 @@ The bank module emits the following events: | Type | Attribute Key | Attribute Value | | -------- | ------------- | ------------------ | | transfer | recipient | {recipientAddress} | +| transfer | sender | {senderAddress} | | transfer | amount | {amount} | | message | module | bank | | message | action | send | -| message | sender | {senderAddress} | #### MsgMultiSend | Type | Attribute Key | Attribute Value | | -------- | ------------- | ------------------ | | transfer | recipient | {recipientAddress} | +| transfer | sender | {senderAddress} | | transfer | amount | {amount} | | message | module | bank | | message | action | multisend | -| message | sender | {senderAddress} | ### Keeper Events @@ -592,9 +589,7 @@ The bank module contains the following parameters ### SendEnabled -The SendEnabled parameter is now deprecated and not to be use. It is replaced -with state store records. - +SendEnabled is depreacted and only kept for backward compatibility. For genesis, use the newly added send_enabled field in the genesis object. Storage, lookup, and manipulation of this information is now in the keeper. ### DefaultSendEnabled @@ -616,6 +611,20 @@ The `query` commands allow users to query `bank` state. simd query bank --help ``` +##### balance + +The `balance` command allows users to query account balance by specific denom. + +```shell +simd query bank balance [address] [denom] [flags] +``` + +Example: + +```shell +simd query bank balance cosmos1.. stake +``` + ##### balances The `balances` command allows users to query account balances by address. @@ -630,49 +639,65 @@ Example: simd query bank balances cosmos1.. ``` -Example Output: +##### spendable balances + +The `spendable-balances` command allows users to query account spendable balances by address. + +```shell +simd query spendable-balances [address] [flags] +``` + +Example: + +```shell +simd query bank spendable-balances cosmos1.. +``` + +##### spendable balance by denom + +The `spendable-balance` command allows users to query account spendable balance by address for a specific denom. + +```shell +simd query spendable-balance [address] [denom] [flags] +``` + +Example: -```yml -balances: -- amount: "1000000000" - denom: stake -pagination: - next_key: null - total: "0" +```shell +simd query bank spendable-balance cosmos1.. stake ``` ##### denom-metadata -The `denom-metadata` command allows users to query metadata for coin denominations. A user can query metadata for a single denomination using the `--denom` flag or all denominations without it. +The `denom-metadata` command allows users to query metadata for coin denominations. ```shell -simd query bank denom-metadata [flags] +simd query bank denom-metadata [denom] ``` Example: ```shell -simd query bank denom-metadata --denom stake +simd query bank denom-metadata stake +``` + +##### denoms-metadata + +The `denoms-metadata` command allows users to query metadata for all coin denominations. + +```shell +simd query bank denoms-metadata [flags] ``` -Example Output: +Example: -```yml -metadata: - base: stake - denom_units: - - aliases: - - STAKE - denom: stake - description: native staking token of simulation app - display: stake - name: SimApp Token - symbol: STK +```shell +simd query bank denoms-metadata ``` -##### total +##### total supply -The `total` command allows users to query the total supply of coins. A user can query the total supply for a single coin using the `--denom` flag or all coins without it. +The `total-supply` (or `total` for short) command allows users to query the total supply of coins. ```shell simd query bank total [flags] @@ -684,11 +709,18 @@ Example: simd query bank total --denom stake ``` -Example Output: +##### total supply of + +The `total-supply-of` command allows users to query the total supply for a specific coin denominations. + +```shell +simd query bank total-supply-of [denom] +``` + +Example: -```yml -amount: "10000000000" -denom: stake +```shell +simd query bank total-supply-of stake ``` ##### send-enabled @@ -705,16 +737,26 @@ Example: simd query bank send-enabled ``` -Example output: +##### params -```yml -send_enabled: -- denom: foocoin - enabled: true -- denom: barcoin -pagination: - next-key: null - total: 2 +The `params` command allows users to query for the current bank parameters. + +```shell +simd query bank params [flags] +``` + +##### denom owners + +The `denom-owners` command allows users to query for all account addresses that own a particular token denomination. + +```shell +simd query bank denom-owners [denom] [flags] +``` + +Example: + +```shell +simd query bank denom-owners stake ``` #### Transactions @@ -760,17 +802,6 @@ grpcurl -plaintext \ cosmos.bank.v1beta1.Query/Balance ``` -Example Output: - -```json -{ - "balance": { - "denom": "stake", - "amount": "1000000000" - } -} -``` - ### AllBalances The `AllBalances` endpoint allows users to query account balance by address for all denominations. @@ -788,22 +819,6 @@ grpcurl -plaintext \ cosmos.bank.v1beta1.Query/AllBalances ``` -Example Output: - -```json -{ - "balances": [ - { - "denom": "stake", - "amount": "1000000000" - } - ], - "pagination": { - "total": "1" - } -} -``` - ### DenomMetadata The `DenomMetadata` endpoint allows users to query metadata for a single coin denomination. @@ -821,28 +836,6 @@ grpcurl -plaintext \ cosmos.bank.v1beta1.Query/DenomMetadata ``` -Example Output: - -```json -{ - "metadata": { - "description": "native staking token of simulation app", - "denomUnits": [ - { - "denom": "stake", - "aliases": [ - "STAKE" - ] - } - ], - "base": "stake", - "display": "stake", - "name": "SimApp Token", - "symbol": "STK" - } -} -``` - ### DenomsMetadata The `DenomsMetadata` endpoint allows users to query metadata for all coin denominations. @@ -859,33 +852,6 @@ grpcurl -plaintext \ cosmos.bank.v1beta1.Query/DenomsMetadata ``` -Example Output: - -```json -{ - "metadatas": [ - { - "description": "native staking token of simulation app", - "denomUnits": [ - { - "denom": "stake", - "aliases": [ - "STAKE" - ] - } - ], - "base": "stake", - "display": "stake", - "name": "SimApp Token", - "symbol": "STK" - } - ], - "pagination": { - "total": "1" - } -} -``` - ### DenomOwners The `DenomOwners` endpoint allows users to query metadata for a single coin denomination. @@ -903,32 +869,6 @@ grpcurl -plaintext \ cosmos.bank.v1beta1.Query/DenomOwners ``` -Example Output: - -```json -{ - "denomOwners": [ - { - "address": "cosmos1..", - "balance": { - "denom": "stake", - "amount": "5000000000" - } - }, - { - "address": "cosmos1..", - "balance": { - "denom": "stake", - "amount": "5000000000" - } - }, - ], - "pagination": { - "total": "2" - } -} -``` - ### TotalSupply The `TotalSupply` endpoint allows users to query the total supply of all coins. @@ -945,22 +885,6 @@ grpcurl -plaintext \ cosmos.bank.v1beta1.Query/TotalSupply ``` -Example Output: - -```json -{ - "supply": [ - { - "denom": "stake", - "amount": "10000000000" - } - ], - "pagination": { - "total": "1" - } -} -``` - ### SupplyOf The `SupplyOf` endpoint allows users to query the total supply of a single coin. @@ -978,17 +902,6 @@ grpcurl -plaintext \ cosmos.bank.v1beta1.Query/SupplyOf ``` -Example Output: - -```json -{ - "amount": { - "denom": "stake", - "amount": "10000000000" - } -} -``` - ### Params The `Params` endpoint allows users to query the parameters of the `bank` module. @@ -1005,16 +918,6 @@ grpcurl -plaintext \ cosmos.bank.v1beta1.Query/Params ``` -Example Output: - -```json -{ - "params": { - "defaultSendEnabled": true - } -} -``` - ### SendEnabled The `SendEnabled` endpoints allows users to query the SendEnabled entries of the `bank` module. @@ -1033,22 +936,3 @@ grpcurl -plaintext \ cosmos.bank.v1beta1.Query/SendEnabled ``` -Example Output: - -```json -{ - "send_enabled": [ - { - "denom": "foocoin", - "enabled": true - }, - { - "denom": "barcoin" - } - ], - "pagination": { - "next-key": null, - "total": 2 - } -} -``` diff --git a/x/bank/go.mod b/x/bank/go.mod index 53fd49dabcd..619a816df1a 100644 --- a/x/bank/go.mod +++ b/x/bank/go.mod @@ -143,18 +143,18 @@ require ( go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect pgregory.net/rapid v1.1.0 // indirect diff --git a/x/bank/go.sum b/x/bank/go.sum index 7588b565a0d..4d7d4939fa7 100644 --- a/x/bank/go.sum +++ b/x/bank/go.sum @@ -511,8 +511,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -593,19 +593,19 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -665,8 +665,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/x/bank/keeper/keeper_test.go b/x/bank/keeper/keeper_test.go index e625712f08b..4fe76503963 100644 --- a/x/bank/keeper/keeper_test.go +++ b/x/bank/keeper/keeper_test.go @@ -790,6 +790,7 @@ func (suite *KeeperTestSuite) TestInputOutputCoinsWithRestrictions() { suite.Require().NoError(err) fromAcc := authtypes.NewBaseAccountWithAddress(fromAddr) inputAccs := []sdk.AccountI{fromAcc} + suite.authKeeper.EXPECT().GetAccount(suite.ctx, inputAccs[0].GetAddress()).Return(inputAccs[0]).AnyTimes() toAddr1 := accAddrs[1] toAddr1Str, err := suite.authKeeper.AddressCodec().BytesToString(toAddr1) suite.Require().NoError(err) @@ -878,7 +879,7 @@ func (suite *KeeperTestSuite) TestInputOutputCoinsWithRestrictions() { }, expErr: "restriction test error", expBals: expBals{ - from: sdk.NewCoins(newFooCoin(959), newBarCoin(412)), + from: sdk.NewCoins(newFooCoin(959), newBarCoin(500)), to1: sdk.NewCoins(newFooCoin(15)), to2: sdk.NewCoins(newFooCoin(26)), }, @@ -907,7 +908,7 @@ func (suite *KeeperTestSuite) TestInputOutputCoinsWithRestrictions() { }, }, expBals: expBals{ - from: sdk.NewCoins(newFooCoin(948), newBarCoin(400)), + from: sdk.NewCoins(newFooCoin(948), newBarCoin(488)), to1: sdk.NewCoins(newFooCoin(26)), to2: sdk.NewCoins(newFooCoin(26), newBarCoin(12)), }, @@ -937,8 +938,8 @@ func (suite *KeeperTestSuite) TestInputOutputCoinsWithRestrictions() { }, expErr: "second restriction error", expBals: expBals{ - from: sdk.NewCoins(newFooCoin(904), newBarCoin(400)), - to1: sdk.NewCoins(newFooCoin(38)), + from: sdk.NewCoins(newFooCoin(948), newBarCoin(488)), + to1: sdk.NewCoins(newFooCoin(26)), to2: sdk.NewCoins(newFooCoin(26), newBarCoin(12)), }, }, @@ -966,8 +967,8 @@ func (suite *KeeperTestSuite) TestInputOutputCoinsWithRestrictions() { }, }, expBals: expBals{ - from: sdk.NewCoins(newFooCoin(904), newBarCoin(365)), - to1: sdk.NewCoins(newFooCoin(38), newBarCoin(25)), + from: sdk.NewCoins(newFooCoin(948), newBarCoin(453)), + to1: sdk.NewCoins(newFooCoin(26), newBarCoin(25)), to2: sdk.NewCoins(newFooCoin(26), newBarCoin(22)), }, }, @@ -980,7 +981,6 @@ func (suite *KeeperTestSuite) TestInputOutputCoinsWithRestrictions() { actualRestrictionArgs = nil suite.bankKeeper.SetSendRestriction(tc.fn) ctx := suite.ctx - suite.mockInputOutputCoins(inputAccs, tc.outputAddrs) input := banktypes.Input{ Address: fromStrAddr, Coins: tc.inputCoins, diff --git a/x/bank/keeper/send.go b/x/bank/keeper/send.go index 063417b6ca0..929ff2e5028 100644 --- a/x/bank/keeper/send.go +++ b/x/bank/keeper/send.go @@ -148,14 +148,15 @@ func (k BaseSendKeeper) InputOutputCoins(ctx context.Context, input types.Input, return err } - err = k.subUnlockedCoins(ctx, inAddress, input.Coins) - if err != nil { - return err + // ensure all coins can be sent + type toSend struct { + AddressStr string + Address []byte + Coins sdk.Coins } - - var outAddress sdk.AccAddress + sending := make([]toSend, 0) for _, out := range outputs { - outAddress, err = k.ak.AddressCodec().StringToBytes(out.Address) + outAddress, err := k.ak.AddressCodec().StringToBytes(out.Address) if err != nil { return err } @@ -165,13 +166,25 @@ func (k BaseSendKeeper) InputOutputCoins(ctx context.Context, input types.Input, return err } - if err := k.addCoins(ctx, outAddress, out.Coins); err != nil { + sending = append(sending, toSend{ + Address: outAddress, + AddressStr: out.Address, + Coins: out.Coins, + }) + } + + if err := k.subUnlockedCoins(ctx, inAddress, input.Coins); err != nil { + return err + } + + for _, out := range sending { + if err := k.addCoins(ctx, out.Address, out.Coins); err != nil { return err } if err := k.EventService.EventManager(ctx).EmitKV( types.EventTypeTransfer, - event.NewAttribute(types.AttributeKeyRecipient, out.Address), + event.NewAttribute(types.AttributeKeyRecipient, out.AddressStr), event.NewAttribute(types.AttributeKeySender, input.Address), event.NewAttribute(sdk.AttributeKeyAmount, out.Coins.String()), ); err != nil { diff --git a/x/circuit/autocli.go b/x/circuit/autocli.go index 89802824780..e9cc67701cf 100644 --- a/x/circuit/autocli.go +++ b/x/circuit/autocli.go @@ -43,7 +43,7 @@ func (am AppModule) AutoCLIOptions() *autocliv1.ModuleOptions { "SOME_MSGS" = 1, "ALL_MSGS" = 2, "SUPER_ADMIN" = 3,`, - Example: fmt.Sprintf(`%s circuit authorize [address] '{"level":1,"limit_type_urls":["/cosmos.bank.v1beta1.MsgSend, /cosmos.bank.v1beta1.MsgMultiSend"]}'"`, version.AppName), + Example: fmt.Sprintf(`%s tx circuit authorize [address] '{"level":1,"limit_type_urls":["/cosmos.bank.v1beta1.MsgSend, /cosmos.bank.v1beta1.MsgMultiSend"]}'"`, version.AppName), PositionalArgs: []*autocliv1.PositionalArgDescriptor{ {ProtoField: "grantee"}, {ProtoField: "permissions"}, // TODO(@julienrbrt) Support flattening msg for setting each field as a positional arg @@ -53,7 +53,7 @@ func (am AppModule) AutoCLIOptions() *autocliv1.ModuleOptions { RpcMethod: "TripCircuitBreaker", Use: "disable ", Short: "Disable a message from being executed", - Example: fmt.Sprintf(`%s circuit disable "/cosmos.bank.v1beta1.MsgSend /cosmos.bank.v1beta1.MsgMultiSend"`, version.AppName), + Example: fmt.Sprintf(`%s tx circuit disable "/cosmos.bank.v1beta1.MsgSend /cosmos.bank.v1beta1.MsgMultiSend"`, version.AppName), PositionalArgs: []*autocliv1.PositionalArgDescriptor{ {ProtoField: "msg_type_urls", Varargs: true}, }, @@ -62,7 +62,7 @@ func (am AppModule) AutoCLIOptions() *autocliv1.ModuleOptions { RpcMethod: "ResetCircuitBreaker", Use: "reset ", Short: "Enable a message to be executed", - Example: fmt.Sprintf(`%s circuit reset "/cosmos.bank.v1beta1.MsgSend /cosmos.bank.v1beta1.MsgMultiSend"`, version.AppName), + Example: fmt.Sprintf(`%s tx circuit reset "/cosmos.bank.v1beta1.MsgSend /cosmos.bank.v1beta1.MsgMultiSend"`, version.AppName), PositionalArgs: []*autocliv1.PositionalArgDescriptor{ {ProtoField: "msg_type_urls", Varargs: true}, }, diff --git a/x/circuit/go.mod b/x/circuit/go.mod index 57801be443d..80d3c9de3db 100644 --- a/x/circuit/go.mod +++ b/x/circuit/go.mod @@ -148,18 +148,18 @@ require ( go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.1 // indirect diff --git a/x/circuit/go.sum b/x/circuit/go.sum index c9df997e10e..32bdeed756c 100644 --- a/x/circuit/go.sum +++ b/x/circuit/go.sum @@ -513,8 +513,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -595,19 +595,19 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -667,8 +667,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/x/consensus/README.md b/x/consensus/README.md index 2af97a6a32d..83add3f0bc9 100644 --- a/x/consensus/README.md +++ b/x/consensus/README.md @@ -10,33 +10,53 @@ Functionality to modify CometBFT's ABCI consensus params. ## Contents +* [Abstract](#abstract) +* [Contents](#contents) * [State](#state) * [Params](#params) -* [Keepers](#keepers) +* [Keeper](#keeper) * [Messages](#messages) -* [Consensus Messages](#consensus-messages) + * [UpdateParams](#updateparams) * [Events](#events) - * [Message Events](#message-events) - ## State -The `x/consensus` module keeps state of the consensus params from cometbft.: +The `x/consensus` module keeps state of the consensus params from CometBFT. ## Params -The consensus module stores it's params in state with the prefix of `0x05`, +The consensus module stores its params in state with the prefix of `0x05`, it can be updated with governance or the address with authority. * Params: `0x05 | ProtocolBuffer(cometbft.ConsensusParams)` ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/release/v0.52.x/x/consensus/proto/cosmos/consensus/v1/consensus.proto#L9-L15 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/consensus/proto/cosmos/consensus/v1/query.proto#L21-L27 +``` + +```protobuf reference +https://github.com/cometbft/cometbft/blob/v0.34.35/proto/tendermint/types/params.proto#L11-L18 ``` -## Keepers +## Keeper + +The Keeper of the `x/consensus` module provides the following functions: + +* `Params`: Retrieves the current consensus parameters. + +* `UpdateParams`: Updates the consensus parameters. Only the authority can perform this operation. + +* `BlockParams`: Returns the maximum gas and bytes allowed in a block. + +* `ValidatorPubKeyTypes`: Provides the list of public key types allowed for validators. + +* `EvidenceParams`: Returns the evidence parameters, including maximum age and bytes. + +* `AppVersion`: Returns the current application version. + + +Note: It is recommended to use the `x/consensus` module keeper to get consensus params instead of accessing them through the context. -The consensus module provides methods to Set and Get consensus params. It is recommended to use the `x/consensus` module keeper to get consensus params instead of accessing them through the context. ## Messages @@ -45,7 +65,7 @@ The consensus module provides methods to Set and Get consensus params. It is rec Update consensus params. ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/release/v0.52.x/x/consensus/proto/cosmos/consensus/v1/tx.proto#L23-L44 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/consensus/proto/cosmos/consensus/v1/tx.proto#L24-L44 ``` The message will fail under the following conditions: diff --git a/x/consensus/go.mod b/x/consensus/go.mod index cee2372dc92..b33bdb6344f 100644 --- a/x/consensus/go.mod +++ b/x/consensus/go.mod @@ -147,18 +147,18 @@ require ( go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.1 // indirect diff --git a/x/consensus/go.sum b/x/consensus/go.sum index 6a6b2df0b20..4c92f8acc3d 100644 --- a/x/consensus/go.sum +++ b/x/consensus/go.sum @@ -512,8 +512,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -592,19 +592,19 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -663,8 +663,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/x/distribution/README.md b/x/distribution/README.md index d49b309f50f..8cd03dc5e45 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/go.mod b/x/distribution/go.mod index dac4ae3041e..d930923ddf3 100644 --- a/x/distribution/go.mod +++ b/x/distribution/go.mod @@ -148,18 +148,18 @@ require ( go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.1 // indirect diff --git a/x/distribution/go.sum b/x/distribution/go.sum index 7588b565a0d..4d7d4939fa7 100644 --- a/x/distribution/go.sum +++ b/x/distribution/go.sum @@ -511,8 +511,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -593,19 +593,19 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -665,8 +665,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/x/distribution/keeper/delegation.go b/x/distribution/keeper/delegation.go index 7bf2091edb8..1959d3a109f 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 46605690c26..47a21d32dec 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 0ded047e93d..6ee76320f98 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) diff --git a/x/epochs/README.md b/x/epochs/README.md index dcd7eb7b517..7b0b0b28576 100644 --- a/x/epochs/README.md +++ b/x/epochs/README.md @@ -68,22 +68,6 @@ The `epochs` module emits the following events: Epochs keeper module provides utility functions to manage epochs. -``` go -// Keeper is the interface for epochs module keeper -type Keeper interface { - // GetEpochInfo returns epoch info by identifier - GetEpochInfo(ctx sdk.Context, identifier string) types.EpochInfo - // SetEpochInfo set epoch info - SetEpochInfo(ctx sdk.Context, epoch types.EpochInfo) - // DeleteEpochInfo delete epoch info - DeleteEpochInfo(ctx sdk.Context, identifier string) - // IterateEpochInfo iterate through epochs - IterateEpochInfo(ctx sdk.Context, fn func(index int64, epochInfo types.EpochInfo) (stop bool)) - // Get all epoch infos - AllEpochInfos(ctx sdk.Context) []types.EpochInfo -} -``` - ## Hooks ```go diff --git a/x/epochs/go.mod b/x/epochs/go.mod index b8262cba4f3..76282884976 100644 --- a/x/epochs/go.mod +++ b/x/epochs/go.mod @@ -146,18 +146,18 @@ require ( go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.1 // indirect diff --git a/x/epochs/go.sum b/x/epochs/go.sum index 6a6b2df0b20..4c92f8acc3d 100644 --- a/x/epochs/go.sum +++ b/x/epochs/go.sum @@ -512,8 +512,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -592,19 +592,19 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -663,8 +663,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/x/epochs/keeper/epoch.go b/x/epochs/keeper/epoch.go index 3943cf4c61c..f743227ed5b 100644 --- a/x/epochs/keeper/epoch.go +++ b/x/epochs/keeper/epoch.go @@ -7,6 +7,11 @@ import ( "cosmossdk.io/x/epochs/types" ) +// GetEpochInfo returns epoch info by identifier. +func (k Keeper) GetEpochInfo(ctx context.Context, identifier string) (types.EpochInfo, error) { + return k.EpochInfo.Get(ctx, identifier) +} + // AddEpochInfo adds a new epoch info. Will return an error if the epoch fails validation, // or re-uses an existing identifier. // This method also sets the start time if left unset, and sets the epoch start height. diff --git a/x/evidence/go.mod b/x/evidence/go.mod index 94af124c0a3..ee4784f1bb3 100644 --- a/x/evidence/go.mod +++ b/x/evidence/go.mod @@ -21,7 +21,7 @@ require ( github.com/stretchr/testify v1.9.0 google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 google.golang.org/grpc v1.67.1 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 ) require ( @@ -149,14 +149,14 @@ require ( go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect diff --git a/x/evidence/go.sum b/x/evidence/go.sum index c9df997e10e..32bdeed756c 100644 --- a/x/evidence/go.sum +++ b/x/evidence/go.sum @@ -513,8 +513,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -595,19 +595,19 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -667,8 +667,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/x/feegrant/README.md b/x/feegrant/README.md index f60720a51ff..63e563d2efb 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 diff --git a/x/feegrant/go.mod b/x/feegrant/go.mod index 56310d95579..be9061fa3ad 100644 --- a/x/feegrant/go.mod +++ b/x/feegrant/go.mod @@ -24,7 +24,7 @@ require ( github.com/stretchr/testify v1.9.0 google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 google.golang.org/grpc v1.67.1 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 gotest.tools/v3 v3.5.1 ) @@ -157,14 +157,14 @@ require ( go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect diff --git a/x/feegrant/go.sum b/x/feegrant/go.sum index 8e11511b6d5..9d5ec6f2fe7 100644 --- a/x/feegrant/go.sum +++ b/x/feegrant/go.sum @@ -521,8 +521,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -605,19 +605,19 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -677,8 +677,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/x/genutil/client/cli/migrate.go b/x/genutil/client/cli/migrate.go index 2763ba06cfb..912aae4600f 100644 --- a/x/genutil/client/cli/migrate.go +++ b/x/genutil/client/cli/migrate.go @@ -13,13 +13,19 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/version" + v052 "github.com/cosmos/cosmos-sdk/x/genutil/migration/v052" "github.com/cosmos/cosmos-sdk/x/genutil/types" ) -const flagGenesisTime = "genesis-time" +const ( + flagGenesisTime = "genesis-time" + v52 = "v0.52" +) // MigrationMap is a map of SDK versions to their respective genesis migration functions. -var MigrationMap = types.MigrationMap{} +var MigrationMap = types.MigrationMap{ + v52: v052.Migrate, +} // MigrateGenesisCmd returns a command to execute genesis state migration. // Applications should pass their own migration map to this function. @@ -29,7 +35,7 @@ func MigrateGenesisCmd(migrations types.MigrationMap) *cobra.Command { Use: "migrate ", Short: "Migrate genesis to a specified target version", Long: "Migrate the source genesis into the target version and print to STDOUT", - Example: fmt.Sprintf("%s migrate v0.47 /path/to/genesis.json --chain-id=cosmoshub-3 --genesis-time=2019-04-22T17:00:00Z", version.AppName), + Example: fmt.Sprintf("%s genesis migrate v0.47 /path/to/genesis.json --chain-id=cosmoshub-3 --genesis-time=2019-04-22T17:00:00Z", version.AppName), Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { return MigrateHandler(cmd, args, migrations) @@ -56,7 +62,17 @@ func MigrateHandler(cmd *cobra.Command, args []string, migrations types.Migratio } importGenesis := args[1] - appGenesis, err := types.AppGenesisFromFile(importGenesis) + outputDocument, _ := cmd.Flags().GetString(flags.FlagOutputDocument) + + // for v52 we need to migrate the consensus validator address from hex bytes to + // sdk consensus address. + var appGenesis *types.AppGenesis + var err error + if target == v52 { + appGenesis, err = v052.MigrateGenesisFile(importGenesis) + } else { + appGenesis, err = types.AppGenesisFromFile(importGenesis) + } if err != nil { return err } @@ -110,7 +126,6 @@ func MigrateHandler(cmd *cobra.Command, args []string, migrations types.Migratio return fmt.Errorf("failed to marshal app genesis: %w", err) } - outputDocument, _ := cmd.Flags().GetString(flags.FlagOutputDocument) if outputDocument == "" { cmd.Println(string(bz)) return nil diff --git a/x/genutil/migration/v052/migrate.go b/x/genutil/migration/v052/migrate.go new file mode 100644 index 00000000000..350240850cf --- /dev/null +++ b/x/genutil/migration/v052/migrate.go @@ -0,0 +1,173 @@ +package migrate + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "time" + + cmtjson "github.com/cometbft/cometbft/libs/json" + cmttypes "github.com/cometbft/cometbft/types" + + "github.com/cosmos/cosmos-sdk/client" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/version" + "github.com/cosmos/cosmos-sdk/x/genutil/types" +) + +type legacyAppGenesis struct { + AppName string `json:"app_name"` + AppVersion string `json:"app_version"` + GenesisTime time.Time `json:"genesis_time"` + ChainID string `json:"chain_id"` + InitialHeight int64 `json:"initial_height"` + AppHash []byte `json:"app_hash"` + AppState json.RawMessage `json:"app_state,omitempty"` + Consensus *legacyConsensusGenesis `json:"consensus,omitempty"` +} + +type legacyConsensusGenesis struct { + Validators []cmttypes.GenesisValidator `json:"validators,omitempty"` + Params *cmttypes.ConsensusParams `json:"params,omitempty"` +} + +func MigrateGenesisFile(oldGenFile string) (*types.AppGenesis, error) { + file, err := os.Open(filepath.Clean(oldGenFile)) + if err != nil { + return nil, err + } + defer file.Close() + + appGenesis, err := migrateGenesisValidator(file) + if err != nil { + return nil, fmt.Errorf("failed to read genesis from file %s: %w", oldGenFile, err) + } + + return appGenesis, nil +} + +// migrateGenesisValidator migrate current genesis file genesis validator to match of the +// new genesis validator type. +func migrateGenesisValidator(r io.Reader) (*types.AppGenesis, error) { + var newAg types.AppGenesis + var ag legacyAppGenesis + var err error + + if rs, ok := r.(io.ReadSeeker); ok { + err = json.NewDecoder(rs).Decode(&ag) + if err == nil { + vals, err := convertValidators(ag.Consensus.Validators) + if err != nil { + return nil, err + } + newAg = types.AppGenesis{ + AppName: ag.AppName, + AppVersion: ag.AppVersion, + GenesisTime: ag.GenesisTime, + ChainID: ag.ChainID, + InitialHeight: ag.InitialHeight, + AppHash: ag.AppHash, + AppState: ag.AppState, + Consensus: &types.ConsensusGenesis{ + Validators: vals, + Params: ag.Consensus.Params, + }, + } + + return &newAg, nil + } + + err = fmt.Errorf("error unmarshalling legacy AppGenesis: %w", err) + if _, serr := rs.Seek(0, io.SeekStart); serr != nil { + err = errors.Join(err, fmt.Errorf("error seeking back to the front: %w", serr)) + return nil, err + } + } + + jsonBlob, ioerr := io.ReadAll(r) + if ioerr != nil { + err = errors.Join(err, fmt.Errorf("failed to read file completely: %w", ioerr)) + return nil, err + } + + // fallback to comet genesis parsing + var ctmGenesis cmttypes.GenesisDoc + if uerr := cmtjson.Unmarshal(jsonBlob, &ctmGenesis); uerr != nil { + err = errors.Join(err, fmt.Errorf("failed fallback to CometBFT GenDoc: %w", uerr)) + return nil, err + } + + vals, err := convertValidators(ctmGenesis.Validators) + if err != nil { + return nil, err + } + newAg = types.AppGenesis{ + AppName: version.AppName, + GenesisTime: ctmGenesis.GenesisTime, + ChainID: ctmGenesis.ChainID, + InitialHeight: ctmGenesis.InitialHeight, + AppHash: ctmGenesis.AppHash, + AppState: ctmGenesis.AppState, + Consensus: &types.ConsensusGenesis{ + Validators: vals, + Params: ctmGenesis.ConsensusParams, + }, + } + + return &newAg, nil +} + +func convertValidators(cmtVals []cmttypes.GenesisValidator) ([]sdk.GenesisValidator, error) { + vals := make([]sdk.GenesisValidator, len(cmtVals)) + for i, cmtVal := range cmtVals { + pk, err := cryptocodec.FromCmtPubKeyInterface(cmtVal.PubKey) + if err != nil { + return nil, err + } + jsonPk, err := cryptocodec.PubKeyFromProto(pk) + if err != nil { + return nil, err + } + vals[i] = sdk.GenesisValidator{ + Address: cmtVal.Address.Bytes(), + PubKey: jsonPk, + Power: cmtVal.Power, + Name: cmtVal.Name, + } + } + return vals, nil +} + +// CometBFT Genesis Handling for JSON, +// this is necessary for json unmarshaling of legacyConsensusGenesis +func (cs *legacyConsensusGenesis) MarshalJSON() ([]byte, error) { + type Alias legacyConsensusGenesis + return cmtjson.Marshal(&Alias{ + Validators: cs.Validators, + Params: cs.Params, + }) +} + +func (cs *legacyConsensusGenesis) UnmarshalJSON(b []byte) error { + type Alias legacyConsensusGenesis + + result := Alias{} + if err := cmtjson.Unmarshal(b, &result); err != nil { + return err + } + + cs.Params = result.Params + cs.Validators = result.Validators + + return nil +} + +// since we only need migrate the consensus validators content so there is no +// exported state migration. +func Migrate(appState types.AppMap, _ client.Context) (types.AppMap, error) { + return appState, nil +} diff --git a/x/genutil/migration/v052/migrate_test.go b/x/genutil/migration/v052/migrate_test.go new file mode 100644 index 00000000000..5c38bb3401f --- /dev/null +++ b/x/genutil/migration/v052/migrate_test.go @@ -0,0 +1,51 @@ +package migrate + +import ( + "bytes" + "encoding/json" + "os" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/x/genutil/types" +) + +var oldGenFilePath = "./testdata/old_app_genesis.json" + +func TestMigration(t *testing.T) { + tempDir := t.TempDir() + + // clean all content on this directory + err := os.RemoveAll(tempDir) + require.NoError(t, err) + + // should not be able to get app genesis from new genesis file + // since validators address are still in hex string and not cons address + _, err = types.AppGenesisFromFile(oldGenFilePath) + require.ErrorContains(t, err, "error unmarshalling AppGenesis: decoding bech32 failed") + + newAppGenesis, err := MigrateGenesisFile(oldGenFilePath) + require.NoError(t, err) + // save the new app genesis to new temp dir + err = newAppGenesis.SaveAs(tempDir) + require.NoError(t, err) + + // read the old app genesis to compare with the new app genesis + var oldAppGenesis legacyAppGenesis + r, err := os.Open(oldGenFilePath) + require.NoError(t, err) + err = json.NewDecoder(r).Decode(&oldAppGenesis) + require.NoError(t, err) + + // should be able to get app genesis from new genesis file + newAppGenesis, err = types.AppGenesisFromFile(tempDir) + require.NotNil(t, newAppGenesis) + require.NotNil(t, newAppGenesis.Consensus) + require.True(t, bytes.Equal(oldAppGenesis.AppHash, newAppGenesis.AppHash)) + require.True(t, bytes.Equal(oldAppGenesis.Consensus.Validators[0].Address.Bytes(), newAppGenesis.Consensus.Validators[0].Address.Bytes())) + require.True(t, bytes.Equal(oldAppGenesis.Consensus.Validators[0].PubKey.Bytes(), newAppGenesis.Consensus.Validators[0].PubKey.Bytes())) + require.Equal(t, len(oldAppGenesis.Consensus.Validators), len(newAppGenesis.Consensus.Validators), "Number of validators should remain the same after migration") + + require.NoError(t, err) +} diff --git a/x/genutil/migration/v052/testdata/old_app_genesis.json b/x/genutil/migration/v052/testdata/old_app_genesis.json new file mode 100644 index 00000000000..57ca37536b6 --- /dev/null +++ b/x/genutil/migration/v052/testdata/old_app_genesis.json @@ -0,0 +1 @@ +{"app_name":"\u003cappd\u003e","app_version":"","genesis_time":"2023-02-20T11:08:30.588307671Z","chain_id":"demo","initial_height":48,"app_hash":"","app_state":{"auth":{"accounts":[{"@type":"/cosmos.auth.v1beta1.BaseAccount","account_number":"1","address":"cosmos1qmkksxlxqdslq6kkca25m4jn344nx29lytq8f9","pub_key":null,"sequence":"0"},{"@type":"/cosmos.auth.v1beta1.BaseAccount","account_number":"8","address":"cosmos1pnt5523etwtzv6mj7haryfw6w8h5tkcuhd99m8","pub_key":null,"sequence":"0"},{"@type":"/cosmos.auth.v1beta1.ModuleAccount","base_account":{"account_number":"4","address":"cosmos1fl48vsnmsdzcv85q5d2q4z5ajdha8yu34mf0eh","pub_key":null,"sequence":"0"},"name":"bonded_tokens_pool","permissions":["burner","staking"]},{"@type":"/cosmos.auth.v1beta1.ModuleAccount","base_account":{"account_number":"5","address":"cosmos1tygms3xhhs3yv487phx3dw4a95jn7t7lpm470r","pub_key":null,"sequence":"0"},"name":"not_bonded_tokens_pool","permissions":["burner","staking"]},{"@type":"/cosmos.auth.v1beta1.ModuleAccount","base_account":{"account_number":"6","address":"cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn","pub_key":null,"sequence":"0"},"name":"gov","permissions":["burner"]},{"@type":"/cosmos.auth.v1beta1.ModuleAccount","base_account":{"account_number":"3","address":"cosmos1jv65s3grqf6v6jl3dp4t6c9t9rk99cd88lyufl","pub_key":null,"sequence":"0"},"name":"distribution","permissions":[]},{"@type":"/cosmos.auth.v1beta1.BaseAccount","account_number":"0","address":"cosmos15jenkldw6348lpgdev3vjzw90zzknxa9a3vg0j","pub_key":{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A3uyZdXedyvYx9VCL6xRjkxtcFpgxjhXFIz9b2mWz+aV"},"sequence":"4"},{"@type":"/cosmos.auth.v1beta1.ModuleAccount","base_account":{"account_number":"7","address":"cosmos1m3h30wlvsf8llruxtpukdvsy0km2kum8g38c8q","pub_key":null,"sequence":"0"},"name":"mint","permissions":["minter"]},{"@type":"/cosmos.auth.v1beta1.ModuleAccount","base_account":{"account_number":"2","address":"cosmos17xpfvakm2amg962yls6f84z3kell8c5lserqta","pub_key":null,"sequence":"0"},"name":"fee_collector","permissions":[]}],"params":{"max_memo_characters":"256","sig_verify_cost_ed25519":"590","sig_verify_cost_secp256k1":"1000","tx_sig_limit":"7","tx_size_cost_per_byte":"10"}},"authz":{"authorization":[]},"bank":{"balances":[{"address":"cosmos1qmkksxlxqdslq6kkca25m4jn344nx29lytq8f9","coins":[{"amount":"5000000000","denom":"stake"}]},{"address":"cosmos1pnt5523etwtzv6mj7haryfw6w8h5tkcuhd99m8","coins":[{"amount":"1000","denom":"stake"}]},{"address":"cosmos1fl48vsnmsdzcv85q5d2q4z5ajdha8yu34mf0eh","coins":[{"amount":"1000000","denom":"stake"}]},{"address":"cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn","coins":[{"amount":"10010000","denom":"stake"}]},{"address":"cosmos1jv65s3grqf6v6jl3dp4t6c9t9rk99cd88lyufl","coins":[{"amount":"9635","denom":"stake"}]},{"address":"cosmos15jenkldw6348lpgdev3vjzw90zzknxa9a3vg0j","coins":[{"amount":"4988989000","denom":"stake"}]}],"denom_metadata":[],"params":{"default_send_enabled":true,"send_enabled":[]},"send_enabled":[],"supply":[{"amount":"10000009635","denom":"stake"}]},"consensus":null,"crisis":{"constant_fee":{"amount":"1000","denom":"stake"}},"distribution":{"delegator_starting_infos":[{"delegator_address":"cosmos15jenkldw6348lpgdev3vjzw90zzknxa9a3vg0j","starting_info":{"height":"0","previous_period":"1","stake":"1000000.000000000000000000"},"validator_address":"cosmosvaloper15jenkldw6348lpgdev3vjzw90zzknxa9c9carp"}],"delegator_withdraw_infos":[],"fee_pool":{"community_pool":[{"amount":"192.700000000000000000","denom":"stake"}]},"outstanding_rewards":[{"outstanding_rewards":[{"amount":"9442.300000000000000000","denom":"stake"}],"validator_address":"cosmosvaloper15jenkldw6348lpgdev3vjzw90zzknxa9c9carp"}],"params":{"base_proposer_reward":"0.000000000000000000","bonus_proposer_reward":"0.000000000000000000","community_tax":"0.020000000000000000","withdraw_addr_enabled":true},"previous_proposer":"cosmosvalcons16vm0nx49eam4q0xasdnwdzsdl6ymgyjt757sgr","validator_accumulated_commissions":[{"accumulated":{"commission":[{"amount":"944.230000000000000000","denom":"stake"}]},"validator_address":"cosmosvaloper15jenkldw6348lpgdev3vjzw90zzknxa9c9carp"}],"validator_current_rewards":[{"rewards":{"period":"2","rewards":[{"amount":"8498.070000000000000000","denom":"stake"}]},"validator_address":"cosmosvaloper15jenkldw6348lpgdev3vjzw90zzknxa9c9carp"}],"validator_historical_rewards":[{"period":"1","rewards":{"cumulative_reward_ratio":[],"reference_count":2},"validator_address":"cosmosvaloper15jenkldw6348lpgdev3vjzw90zzknxa9c9carp"}],"validator_slash_events":[]},"evidence":{"evidence":[]},"feegrant":{"allowances":[]},"genutil":{"gen_txs":[]},"gov":{"deposit_params":null,"deposits":[{"amount":[{"amount":"10010000","denom":"stake"}],"depositor":"cosmos15jenkldw6348lpgdev3vjzw90zzknxa9a3vg0j","proposal_id":"1"}],"params":{"expedited_min_deposit":[{"amount":"50000000","denom":"stake"}],"expedited_threshold":"0.667000000000000000","expedited_voting_period":"86400s","max_deposit_period":"172800s","min_deposit":[{"amount":"10000000","denom":"stake"}],"min_initial_deposit_ratio":"0.000000000000000000","proposal_cancel_dest":"","proposal_cancel_ratio":"0.500000000000000000","quorum":"0.334000000000000000","threshold":"0.500000000000000000","veto_threshold":"0.334000000000000000","voting_period":"172800s"},"proposals":[{"deposit_end_time":"2023-02-22T11:11:52.776167376Z","expedited":false,"final_tally_result":{"abstain_count":"0","no_count":"0","no_with_veto_count":"0","yes_count":"0"},"id":"1","messages":[{"@type":"/cosmos.distribution.v1beta1.MsgCommunityPoolSpend","amount":[],"authority":"cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn","recipient":"cosmos1pnt5523etwtzv6mj7haryfw6w8h5tkcuhd99m8"}],"metadata":"ipfs://CID","proposer":"cosmos15jenkldw6348lpgdev3vjzw90zzknxa9a3vg0j","status":"PROPOSAL_STATUS_VOTING_PERIOD","submit_time":"2023-02-20T11:11:52.776167376Z","summary":"test proposal","title":"test proposal","total_deposit":[{"amount":"10010000","denom":"stake"}],"voting_end_time":"2023-02-22T11:12:07.801161984Z","voting_start_time":"2023-02-20T11:12:07.801161984Z"}],"starting_proposal_id":"2","tally_params":null,"votes":[],"voting_params":null},"group":{"group_members":[],"group_policies":[],"group_policy_seq":"0","group_seq":"0","groups":[],"proposal_seq":"0","proposals":[],"votes":[]},"mint":{"minter":{"annual_provisions":"1300010905.175073197786747950","inflation":"0.130000967926594565"},"params":{"blocks_per_year":"6311520","goal_bonded":"0.670000000000000000","inflation_max":"0.200000000000000000","inflation_min":"0.070000000000000000","inflation_rate_change":"0.130000000000000000","mint_denom":"stake"}},"nft":{"classes":[],"entries":[]},"params":null,"slashing":{"missed_blocks":[{"address":"cosmosvalcons16vm0nx49eam4q0xasdnwdzsdl6ymgyjt757sgr","missed_blocks":[]}],"params":{"downtime_jail_duration":"600s","min_signed_per_window":"0.500000000000000000","signed_blocks_window":"100","slash_fraction_double_sign":"0.050000000000000000","slash_fraction_downtime":"0.010000000000000000"},"signing_infos":[{"address":"cosmosvalcons16vm0nx49eam4q0xasdnwdzsdl6ymgyjt757sgr","validator_signing_info":{"address":"cosmosvalcons16vm0nx49eam4q0xasdnwdzsdl6ymgyjt757sgr","index_offset":"46","jailed_until":"1970-01-01T00:00:00Z","missed_blocks_counter":"0","start_height":"0","tombstoned":false}}]},"staking":{"delegations":[{"delegator_address":"cosmos15jenkldw6348lpgdev3vjzw90zzknxa9a3vg0j","shares":"1000000.000000000000000000","validator_address":"cosmosvaloper15jenkldw6348lpgdev3vjzw90zzknxa9c9carp"}],"exported":true,"last_total_power":"1","last_validator_powers":[{"address":"cosmosvaloper15jenkldw6348lpgdev3vjzw90zzknxa9c9carp","power":"1"}],"params":{"bond_denom":"stake","historical_entries":10000,"max_entries":7,"max_validators":100,"min_commission_rate":"0.000000000000000000","unbonding_time":"1814400s"},"redelegations":[],"unbonding_delegations":[],"validators":[{"commission":{"commission_rates":{"max_change_rate":"0.010000000000000000","max_rate":"0.200000000000000000","rate":"0.100000000000000000"},"update_time":"2023-02-20T11:08:30.588307671Z"},"consensus_pubkey":{"@type":"/cosmos.crypto.ed25519.PubKey","key":"tMZonPQYoooG/xbFVhHg95pTLxx7aO43/qgHFxDagWM="},"delegator_shares":"1000000.000000000000000000","description":{"details":"","identity":"","moniker":"test","security_contact":"","website":""},"jailed":false,"min_self_delegation":"1","operator_address":"cosmosvaloper15jenkldw6348lpgdev3vjzw90zzknxa9c9carp","status":"BOND_STATUS_BONDED","tokens":"1000000","unbonding_height":"0","unbonding_ids":[],"unbonding_on_hold_ref_count":"0","unbonding_time":"1970-01-01T00:00:00Z"}]},"upgrade":{},"vesting":{}},"consensus":{"validators":[{"address":"D336F99AA5CF77503CDD8366E68A0DFE89B4124B","pub_key":{"type":"tendermint/PubKeyEd25519","value":"tMZonPQYoooG/xbFVhHg95pTLxx7aO43/qgHFxDagWM="},"power":"1","name":"test"}],"params":{"block":{"max_bytes":"22020096","max_gas":"-1"},"evidence":{"max_age_num_blocks":"100000","max_age_duration":"172800000000000","max_bytes":"1048576"},"validator":{"pub_key_types":["ed25519"]},"version":{"app":"0"},"synchrony":{"precision":"0","message_delay":"0"},"feature":{"vote_extensions_enable_height":"0","pbts_enable_height":"0"}}}} \ No newline at end of file diff --git a/x/genutil/types/genesis.go b/x/genutil/types/genesis.go index 1566c0affaa..023abfba082 100644 --- a/x/genutil/types/genesis.go +++ b/x/genutil/types/genesis.go @@ -14,6 +14,8 @@ import ( cmtjson "github.com/cometbft/cometbft/libs/json" cmttypes "github.com/cometbft/cometbft/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/version" ) @@ -118,6 +120,26 @@ func AppGenesisFromReader(reader io.Reader) (*AppGenesis, error) { return nil, err } + vals := []sdk.GenesisValidator{} + for _, cmtVal := range ctmGenesis.Validators { + pk, err := cryptocodec.FromCmtPubKeyInterface(cmtVal.PubKey) + if err != nil { + return nil, err + } + jsonPk, err := cryptocodec.PubKeyFromProto(pk) + if err != nil { + return nil, err + } + val := sdk.GenesisValidator{ + Address: cmtVal.Address.Bytes(), + PubKey: jsonPk, + Power: cmtVal.Power, + Name: cmtVal.Name, + } + + vals = append(vals, val) + } + ag = AppGenesis{ AppName: version.AppName, // AppVersion is not filled as we do not know it from a CometBFT genesis @@ -127,7 +149,7 @@ func AppGenesisFromReader(reader io.Reader) (*AppGenesis, error) { AppHash: ctmGenesis.AppHash, AppState: ctmGenesis.AppState, Consensus: &ConsensusGenesis{ - Validators: ctmGenesis.Validators, + Validators: vals, Params: ctmGenesis.ConsensusParams, }, } @@ -160,13 +182,36 @@ func AppGenesisFromFile(genFile string) (*AppGenesis, error) { // ToGenesisDoc converts the AppGenesis to a CometBFT GenesisDoc. func (ag *AppGenesis) ToGenesisDoc() (*cmttypes.GenesisDoc, error) { + cmtValidators := []cmttypes.GenesisValidator{} + for _, val := range ag.Consensus.Validators { + pk, err := cryptocodec.PubKeyToProto(val.PubKey) + if err != nil { + return nil, err + } + cmtPk, err := cryptocodec.ToCmtPubKeyInterface(pk) + if err != nil { + return nil, err + } + cmtVal := cmttypes.GenesisValidator{ + Address: val.Address.Bytes(), + PubKey: cmtPk, + Power: val.Power, + Name: val.Name, + } + + cmtValidators = append(cmtValidators, cmtVal) + } + // assert nil value for empty validators set + if len(cmtValidators) == 0 { + cmtValidators = nil + } return &cmttypes.GenesisDoc{ GenesisTime: ag.GenesisTime, ChainID: ag.ChainID, InitialHeight: ag.InitialHeight, AppHash: ag.AppHash, AppState: ag.AppState, - Validators: ag.Consensus.Validators, + Validators: cmtValidators, ConsensusParams: ag.Consensus.Params, }, nil } @@ -174,13 +219,13 @@ func (ag *AppGenesis) ToGenesisDoc() (*cmttypes.GenesisDoc, error) { // ConsensusGenesis defines the consensus layer's genesis. // TODO(@julienrbrt) eventually abstract from CometBFT types type ConsensusGenesis struct { - Validators []cmttypes.GenesisValidator `json:"validators,omitempty"` - Params *cmttypes.ConsensusParams `json:"params,omitempty"` + Validators []sdk.GenesisValidator `json:"validators,omitempty"` + Params *cmttypes.ConsensusParams `json:"params,omitempty"` } // NewConsensusGenesis returns a ConsensusGenesis with given values. // It takes a proto consensus params so it can called from server export command. -func NewConsensusGenesis(params cmtproto.ConsensusParams, validators []cmttypes.GenesisValidator) *ConsensusGenesis { +func NewConsensusGenesis(params cmtproto.ConsensusParams, validators []sdk.GenesisValidator) *ConsensusGenesis { return &ConsensusGenesis{ Params: &cmttypes.ConsensusParams{ Block: cmttypes.BlockParams{ @@ -211,7 +256,7 @@ func (cs *ConsensusGenesis) MarshalJSON() ([]byte, error) { func (cs *ConsensusGenesis) UnmarshalJSON(b []byte) error { type Alias ConsensusGenesis - result := Alias{} + var result Alias if err := cmtjson.Unmarshal(b, &result); err != nil { return err } @@ -241,7 +286,7 @@ func (cs *ConsensusGenesis) ValidateAndComplete() error { return fmt.Errorf("incorrect address for validator %v in the genesis file, should be %v", v, v.PubKey.Address()) } if len(v.Address) == 0 { - cs.Validators[i].Address = v.PubKey.Address() + cs.Validators[i].Address = v.PubKey.Address().Bytes() } } diff --git a/x/genutil/types/testdata/app_genesis.json b/x/genutil/types/testdata/app_genesis.json index 57ca37536b6..20bca1589f7 100644 --- a/x/genutil/types/testdata/app_genesis.json +++ b/x/genutil/types/testdata/app_genesis.json @@ -1 +1 @@ -{"app_name":"\u003cappd\u003e","app_version":"","genesis_time":"2023-02-20T11:08:30.588307671Z","chain_id":"demo","initial_height":48,"app_hash":"","app_state":{"auth":{"accounts":[{"@type":"/cosmos.auth.v1beta1.BaseAccount","account_number":"1","address":"cosmos1qmkksxlxqdslq6kkca25m4jn344nx29lytq8f9","pub_key":null,"sequence":"0"},{"@type":"/cosmos.auth.v1beta1.BaseAccount","account_number":"8","address":"cosmos1pnt5523etwtzv6mj7haryfw6w8h5tkcuhd99m8","pub_key":null,"sequence":"0"},{"@type":"/cosmos.auth.v1beta1.ModuleAccount","base_account":{"account_number":"4","address":"cosmos1fl48vsnmsdzcv85q5d2q4z5ajdha8yu34mf0eh","pub_key":null,"sequence":"0"},"name":"bonded_tokens_pool","permissions":["burner","staking"]},{"@type":"/cosmos.auth.v1beta1.ModuleAccount","base_account":{"account_number":"5","address":"cosmos1tygms3xhhs3yv487phx3dw4a95jn7t7lpm470r","pub_key":null,"sequence":"0"},"name":"not_bonded_tokens_pool","permissions":["burner","staking"]},{"@type":"/cosmos.auth.v1beta1.ModuleAccount","base_account":{"account_number":"6","address":"cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn","pub_key":null,"sequence":"0"},"name":"gov","permissions":["burner"]},{"@type":"/cosmos.auth.v1beta1.ModuleAccount","base_account":{"account_number":"3","address":"cosmos1jv65s3grqf6v6jl3dp4t6c9t9rk99cd88lyufl","pub_key":null,"sequence":"0"},"name":"distribution","permissions":[]},{"@type":"/cosmos.auth.v1beta1.BaseAccount","account_number":"0","address":"cosmos15jenkldw6348lpgdev3vjzw90zzknxa9a3vg0j","pub_key":{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A3uyZdXedyvYx9VCL6xRjkxtcFpgxjhXFIz9b2mWz+aV"},"sequence":"4"},{"@type":"/cosmos.auth.v1beta1.ModuleAccount","base_account":{"account_number":"7","address":"cosmos1m3h30wlvsf8llruxtpukdvsy0km2kum8g38c8q","pub_key":null,"sequence":"0"},"name":"mint","permissions":["minter"]},{"@type":"/cosmos.auth.v1beta1.ModuleAccount","base_account":{"account_number":"2","address":"cosmos17xpfvakm2amg962yls6f84z3kell8c5lserqta","pub_key":null,"sequence":"0"},"name":"fee_collector","permissions":[]}],"params":{"max_memo_characters":"256","sig_verify_cost_ed25519":"590","sig_verify_cost_secp256k1":"1000","tx_sig_limit":"7","tx_size_cost_per_byte":"10"}},"authz":{"authorization":[]},"bank":{"balances":[{"address":"cosmos1qmkksxlxqdslq6kkca25m4jn344nx29lytq8f9","coins":[{"amount":"5000000000","denom":"stake"}]},{"address":"cosmos1pnt5523etwtzv6mj7haryfw6w8h5tkcuhd99m8","coins":[{"amount":"1000","denom":"stake"}]},{"address":"cosmos1fl48vsnmsdzcv85q5d2q4z5ajdha8yu34mf0eh","coins":[{"amount":"1000000","denom":"stake"}]},{"address":"cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn","coins":[{"amount":"10010000","denom":"stake"}]},{"address":"cosmos1jv65s3grqf6v6jl3dp4t6c9t9rk99cd88lyufl","coins":[{"amount":"9635","denom":"stake"}]},{"address":"cosmos15jenkldw6348lpgdev3vjzw90zzknxa9a3vg0j","coins":[{"amount":"4988989000","denom":"stake"}]}],"denom_metadata":[],"params":{"default_send_enabled":true,"send_enabled":[]},"send_enabled":[],"supply":[{"amount":"10000009635","denom":"stake"}]},"consensus":null,"crisis":{"constant_fee":{"amount":"1000","denom":"stake"}},"distribution":{"delegator_starting_infos":[{"delegator_address":"cosmos15jenkldw6348lpgdev3vjzw90zzknxa9a3vg0j","starting_info":{"height":"0","previous_period":"1","stake":"1000000.000000000000000000"},"validator_address":"cosmosvaloper15jenkldw6348lpgdev3vjzw90zzknxa9c9carp"}],"delegator_withdraw_infos":[],"fee_pool":{"community_pool":[{"amount":"192.700000000000000000","denom":"stake"}]},"outstanding_rewards":[{"outstanding_rewards":[{"amount":"9442.300000000000000000","denom":"stake"}],"validator_address":"cosmosvaloper15jenkldw6348lpgdev3vjzw90zzknxa9c9carp"}],"params":{"base_proposer_reward":"0.000000000000000000","bonus_proposer_reward":"0.000000000000000000","community_tax":"0.020000000000000000","withdraw_addr_enabled":true},"previous_proposer":"cosmosvalcons16vm0nx49eam4q0xasdnwdzsdl6ymgyjt757sgr","validator_accumulated_commissions":[{"accumulated":{"commission":[{"amount":"944.230000000000000000","denom":"stake"}]},"validator_address":"cosmosvaloper15jenkldw6348lpgdev3vjzw90zzknxa9c9carp"}],"validator_current_rewards":[{"rewards":{"period":"2","rewards":[{"amount":"8498.070000000000000000","denom":"stake"}]},"validator_address":"cosmosvaloper15jenkldw6348lpgdev3vjzw90zzknxa9c9carp"}],"validator_historical_rewards":[{"period":"1","rewards":{"cumulative_reward_ratio":[],"reference_count":2},"validator_address":"cosmosvaloper15jenkldw6348lpgdev3vjzw90zzknxa9c9carp"}],"validator_slash_events":[]},"evidence":{"evidence":[]},"feegrant":{"allowances":[]},"genutil":{"gen_txs":[]},"gov":{"deposit_params":null,"deposits":[{"amount":[{"amount":"10010000","denom":"stake"}],"depositor":"cosmos15jenkldw6348lpgdev3vjzw90zzknxa9a3vg0j","proposal_id":"1"}],"params":{"expedited_min_deposit":[{"amount":"50000000","denom":"stake"}],"expedited_threshold":"0.667000000000000000","expedited_voting_period":"86400s","max_deposit_period":"172800s","min_deposit":[{"amount":"10000000","denom":"stake"}],"min_initial_deposit_ratio":"0.000000000000000000","proposal_cancel_dest":"","proposal_cancel_ratio":"0.500000000000000000","quorum":"0.334000000000000000","threshold":"0.500000000000000000","veto_threshold":"0.334000000000000000","voting_period":"172800s"},"proposals":[{"deposit_end_time":"2023-02-22T11:11:52.776167376Z","expedited":false,"final_tally_result":{"abstain_count":"0","no_count":"0","no_with_veto_count":"0","yes_count":"0"},"id":"1","messages":[{"@type":"/cosmos.distribution.v1beta1.MsgCommunityPoolSpend","amount":[],"authority":"cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn","recipient":"cosmos1pnt5523etwtzv6mj7haryfw6w8h5tkcuhd99m8"}],"metadata":"ipfs://CID","proposer":"cosmos15jenkldw6348lpgdev3vjzw90zzknxa9a3vg0j","status":"PROPOSAL_STATUS_VOTING_PERIOD","submit_time":"2023-02-20T11:11:52.776167376Z","summary":"test proposal","title":"test proposal","total_deposit":[{"amount":"10010000","denom":"stake"}],"voting_end_time":"2023-02-22T11:12:07.801161984Z","voting_start_time":"2023-02-20T11:12:07.801161984Z"}],"starting_proposal_id":"2","tally_params":null,"votes":[],"voting_params":null},"group":{"group_members":[],"group_policies":[],"group_policy_seq":"0","group_seq":"0","groups":[],"proposal_seq":"0","proposals":[],"votes":[]},"mint":{"minter":{"annual_provisions":"1300010905.175073197786747950","inflation":"0.130000967926594565"},"params":{"blocks_per_year":"6311520","goal_bonded":"0.670000000000000000","inflation_max":"0.200000000000000000","inflation_min":"0.070000000000000000","inflation_rate_change":"0.130000000000000000","mint_denom":"stake"}},"nft":{"classes":[],"entries":[]},"params":null,"slashing":{"missed_blocks":[{"address":"cosmosvalcons16vm0nx49eam4q0xasdnwdzsdl6ymgyjt757sgr","missed_blocks":[]}],"params":{"downtime_jail_duration":"600s","min_signed_per_window":"0.500000000000000000","signed_blocks_window":"100","slash_fraction_double_sign":"0.050000000000000000","slash_fraction_downtime":"0.010000000000000000"},"signing_infos":[{"address":"cosmosvalcons16vm0nx49eam4q0xasdnwdzsdl6ymgyjt757sgr","validator_signing_info":{"address":"cosmosvalcons16vm0nx49eam4q0xasdnwdzsdl6ymgyjt757sgr","index_offset":"46","jailed_until":"1970-01-01T00:00:00Z","missed_blocks_counter":"0","start_height":"0","tombstoned":false}}]},"staking":{"delegations":[{"delegator_address":"cosmos15jenkldw6348lpgdev3vjzw90zzknxa9a3vg0j","shares":"1000000.000000000000000000","validator_address":"cosmosvaloper15jenkldw6348lpgdev3vjzw90zzknxa9c9carp"}],"exported":true,"last_total_power":"1","last_validator_powers":[{"address":"cosmosvaloper15jenkldw6348lpgdev3vjzw90zzknxa9c9carp","power":"1"}],"params":{"bond_denom":"stake","historical_entries":10000,"max_entries":7,"max_validators":100,"min_commission_rate":"0.000000000000000000","unbonding_time":"1814400s"},"redelegations":[],"unbonding_delegations":[],"validators":[{"commission":{"commission_rates":{"max_change_rate":"0.010000000000000000","max_rate":"0.200000000000000000","rate":"0.100000000000000000"},"update_time":"2023-02-20T11:08:30.588307671Z"},"consensus_pubkey":{"@type":"/cosmos.crypto.ed25519.PubKey","key":"tMZonPQYoooG/xbFVhHg95pTLxx7aO43/qgHFxDagWM="},"delegator_shares":"1000000.000000000000000000","description":{"details":"","identity":"","moniker":"test","security_contact":"","website":""},"jailed":false,"min_self_delegation":"1","operator_address":"cosmosvaloper15jenkldw6348lpgdev3vjzw90zzknxa9c9carp","status":"BOND_STATUS_BONDED","tokens":"1000000","unbonding_height":"0","unbonding_ids":[],"unbonding_on_hold_ref_count":"0","unbonding_time":"1970-01-01T00:00:00Z"}]},"upgrade":{},"vesting":{}},"consensus":{"validators":[{"address":"D336F99AA5CF77503CDD8366E68A0DFE89B4124B","pub_key":{"type":"tendermint/PubKeyEd25519","value":"tMZonPQYoooG/xbFVhHg95pTLxx7aO43/qgHFxDagWM="},"power":"1","name":"test"}],"params":{"block":{"max_bytes":"22020096","max_gas":"-1"},"evidence":{"max_age_num_blocks":"100000","max_age_duration":"172800000000000","max_bytes":"1048576"},"validator":{"pub_key_types":["ed25519"]},"version":{"app":"0"},"synchrony":{"precision":"0","message_delay":"0"},"feature":{"vote_extensions_enable_height":"0","pbts_enable_height":"0"}}}} \ No newline at end of file +{"app_name":"\u003cappd\u003e","app_version":"","genesis_time":"2023-02-20T11:08:30.588307671Z","chain_id":"demo","initial_height":48,"app_hash":"","app_state":{"auth":{"accounts":[{"@type":"/cosmos.auth.v1beta1.BaseAccount","account_number":"1","address":"cosmos1qmkksxlxqdslq6kkca25m4jn344nx29lytq8f9","pub_key":null,"sequence":"0"},{"@type":"/cosmos.auth.v1beta1.BaseAccount","account_number":"8","address":"cosmos1pnt5523etwtzv6mj7haryfw6w8h5tkcuhd99m8","pub_key":null,"sequence":"0"},{"@type":"/cosmos.auth.v1beta1.ModuleAccount","base_account":{"account_number":"4","address":"cosmos1fl48vsnmsdzcv85q5d2q4z5ajdha8yu34mf0eh","pub_key":null,"sequence":"0"},"name":"bonded_tokens_pool","permissions":["burner","staking"]},{"@type":"/cosmos.auth.v1beta1.ModuleAccount","base_account":{"account_number":"5","address":"cosmos1tygms3xhhs3yv487phx3dw4a95jn7t7lpm470r","pub_key":null,"sequence":"0"},"name":"not_bonded_tokens_pool","permissions":["burner","staking"]},{"@type":"/cosmos.auth.v1beta1.ModuleAccount","base_account":{"account_number":"6","address":"cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn","pub_key":null,"sequence":"0"},"name":"gov","permissions":["burner"]},{"@type":"/cosmos.auth.v1beta1.ModuleAccount","base_account":{"account_number":"3","address":"cosmos1jv65s3grqf6v6jl3dp4t6c9t9rk99cd88lyufl","pub_key":null,"sequence":"0"},"name":"distribution","permissions":[]},{"@type":"/cosmos.auth.v1beta1.BaseAccount","account_number":"0","address":"cosmos15jenkldw6348lpgdev3vjzw90zzknxa9a3vg0j","pub_key":{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A3uyZdXedyvYx9VCL6xRjkxtcFpgxjhXFIz9b2mWz+aV"},"sequence":"4"},{"@type":"/cosmos.auth.v1beta1.ModuleAccount","base_account":{"account_number":"7","address":"cosmos1m3h30wlvsf8llruxtpukdvsy0km2kum8g38c8q","pub_key":null,"sequence":"0"},"name":"mint","permissions":["minter"]},{"@type":"/cosmos.auth.v1beta1.ModuleAccount","base_account":{"account_number":"2","address":"cosmos17xpfvakm2amg962yls6f84z3kell8c5lserqta","pub_key":null,"sequence":"0"},"name":"fee_collector","permissions":[]}],"params":{"max_memo_characters":"256","sig_verify_cost_ed25519":"590","sig_verify_cost_secp256k1":"1000","tx_sig_limit":"7","tx_size_cost_per_byte":"10"}},"authz":{"authorization":[]},"bank":{"balances":[{"address":"cosmos1qmkksxlxqdslq6kkca25m4jn344nx29lytq8f9","coins":[{"amount":"5000000000","denom":"stake"}]},{"address":"cosmos1pnt5523etwtzv6mj7haryfw6w8h5tkcuhd99m8","coins":[{"amount":"1000","denom":"stake"}]},{"address":"cosmos1fl48vsnmsdzcv85q5d2q4z5ajdha8yu34mf0eh","coins":[{"amount":"1000000","denom":"stake"}]},{"address":"cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn","coins":[{"amount":"10010000","denom":"stake"}]},{"address":"cosmos1jv65s3grqf6v6jl3dp4t6c9t9rk99cd88lyufl","coins":[{"amount":"9635","denom":"stake"}]},{"address":"cosmos15jenkldw6348lpgdev3vjzw90zzknxa9a3vg0j","coins":[{"amount":"4988989000","denom":"stake"}]}],"denom_metadata":[],"params":{"default_send_enabled":true,"send_enabled":[]},"send_enabled":[],"supply":[{"amount":"10000009635","denom":"stake"}]},"consensus":null,"crisis":{"constant_fee":{"amount":"1000","denom":"stake"}},"distribution":{"delegator_starting_infos":[{"delegator_address":"cosmos15jenkldw6348lpgdev3vjzw90zzknxa9a3vg0j","starting_info":{"height":"0","previous_period":"1","stake":"1000000.000000000000000000"},"validator_address":"cosmosvaloper15jenkldw6348lpgdev3vjzw90zzknxa9c9carp"}],"delegator_withdraw_infos":[],"fee_pool":{"community_pool":[{"amount":"192.700000000000000000","denom":"stake"}]},"outstanding_rewards":[{"outstanding_rewards":[{"amount":"9442.300000000000000000","denom":"stake"}],"validator_address":"cosmosvaloper15jenkldw6348lpgdev3vjzw90zzknxa9c9carp"}],"params":{"base_proposer_reward":"0.000000000000000000","bonus_proposer_reward":"0.000000000000000000","community_tax":"0.020000000000000000","withdraw_addr_enabled":true},"previous_proposer":"cosmosvalcons16vm0nx49eam4q0xasdnwdzsdl6ymgyjt757sgr","validator_accumulated_commissions":[{"accumulated":{"commission":[{"amount":"944.230000000000000000","denom":"stake"}]},"validator_address":"cosmosvaloper15jenkldw6348lpgdev3vjzw90zzknxa9c9carp"}],"validator_current_rewards":[{"rewards":{"period":"2","rewards":[{"amount":"8498.070000000000000000","denom":"stake"}]},"validator_address":"cosmosvaloper15jenkldw6348lpgdev3vjzw90zzknxa9c9carp"}],"validator_historical_rewards":[{"period":"1","rewards":{"cumulative_reward_ratio":[],"reference_count":2},"validator_address":"cosmosvaloper15jenkldw6348lpgdev3vjzw90zzknxa9c9carp"}],"validator_slash_events":[]},"evidence":{"evidence":[]},"feegrant":{"allowances":[]},"genutil":{"gen_txs":[]},"gov":{"deposit_params":null,"deposits":[{"amount":[{"amount":"10010000","denom":"stake"}],"depositor":"cosmos15jenkldw6348lpgdev3vjzw90zzknxa9a3vg0j","proposal_id":"1"}],"params":{"expedited_min_deposit":[{"amount":"50000000","denom":"stake"}],"expedited_threshold":"0.667000000000000000","expedited_voting_period":"86400s","max_deposit_period":"172800s","min_deposit":[{"amount":"10000000","denom":"stake"}],"min_initial_deposit_ratio":"0.000000000000000000","proposal_cancel_dest":"","proposal_cancel_ratio":"0.500000000000000000","quorum":"0.334000000000000000","threshold":"0.500000000000000000","veto_threshold":"0.334000000000000000","voting_period":"172800s"},"proposals":[{"deposit_end_time":"2023-02-22T11:11:52.776167376Z","expedited":false,"final_tally_result":{"abstain_count":"0","no_count":"0","no_with_veto_count":"0","yes_count":"0"},"id":"1","messages":[{"@type":"/cosmos.distribution.v1beta1.MsgCommunityPoolSpend","amount":[],"authority":"cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn","recipient":"cosmos1pnt5523etwtzv6mj7haryfw6w8h5tkcuhd99m8"}],"metadata":"ipfs://CID","proposer":"cosmos15jenkldw6348lpgdev3vjzw90zzknxa9a3vg0j","status":"PROPOSAL_STATUS_VOTING_PERIOD","submit_time":"2023-02-20T11:11:52.776167376Z","summary":"test proposal","title":"test proposal","total_deposit":[{"amount":"10010000","denom":"stake"}],"voting_end_time":"2023-02-22T11:12:07.801161984Z","voting_start_time":"2023-02-20T11:12:07.801161984Z"}],"starting_proposal_id":"2","tally_params":null,"votes":[],"voting_params":null},"group":{"group_members":[],"group_policies":[],"group_policy_seq":"0","group_seq":"0","groups":[],"proposal_seq":"0","proposals":[],"votes":[]},"mint":{"minter":{"annual_provisions":"1300010905.175073197786747950","inflation":"0.130000967926594565"},"params":{"blocks_per_year":"6311520","goal_bonded":"0.670000000000000000","inflation_max":"0.200000000000000000","inflation_min":"0.070000000000000000","inflation_rate_change":"0.130000000000000000","mint_denom":"stake"}},"nft":{"classes":[],"entries":[]},"params":null,"slashing":{"missed_blocks":[{"address":"cosmosvalcons16vm0nx49eam4q0xasdnwdzsdl6ymgyjt757sgr","missed_blocks":[]}],"params":{"downtime_jail_duration":"600s","min_signed_per_window":"0.500000000000000000","signed_blocks_window":"100","slash_fraction_double_sign":"0.050000000000000000","slash_fraction_downtime":"0.010000000000000000"},"signing_infos":[{"address":"cosmosvalcons16vm0nx49eam4q0xasdnwdzsdl6ymgyjt757sgr","validator_signing_info":{"address":"cosmosvalcons16vm0nx49eam4q0xasdnwdzsdl6ymgyjt757sgr","index_offset":"46","jailed_until":"1970-01-01T00:00:00Z","missed_blocks_counter":"0","start_height":"0","tombstoned":false}}]},"staking":{"delegations":[{"delegator_address":"cosmos15jenkldw6348lpgdev3vjzw90zzknxa9a3vg0j","shares":"1000000.000000000000000000","validator_address":"cosmosvaloper15jenkldw6348lpgdev3vjzw90zzknxa9c9carp"}],"exported":true,"last_total_power":"1","last_validator_powers":[{"address":"cosmosvaloper15jenkldw6348lpgdev3vjzw90zzknxa9c9carp","power":"1"}],"params":{"bond_denom":"stake","historical_entries":10000,"max_entries":7,"max_validators":100,"min_commission_rate":"0.000000000000000000","unbonding_time":"1814400s"},"redelegations":[],"unbonding_delegations":[],"validators":[{"commission":{"commission_rates":{"max_change_rate":"0.010000000000000000","max_rate":"0.200000000000000000","rate":"0.100000000000000000"},"update_time":"2023-02-20T11:08:30.588307671Z"},"consensus_pubkey":{"@type":"/cosmos.crypto.ed25519.PubKey","key":"tMZonPQYoooG/xbFVhHg95pTLxx7aO43/qgHFxDagWM="},"delegator_shares":"1000000.000000000000000000","description":{"details":"","identity":"","moniker":"test","security_contact":"","website":""},"jailed":false,"min_self_delegation":"1","operator_address":"cosmosvaloper15jenkldw6348lpgdev3vjzw90zzknxa9c9carp","status":"BOND_STATUS_BONDED","tokens":"1000000","unbonding_height":"0","unbonding_ids":[],"unbonding_on_hold_ref_count":"0","unbonding_time":"1970-01-01T00:00:00Z"}]},"upgrade":{},"vesting":{}},"consensus":{"validators":[{"address":"cosmosvalcons16vm0nx49eam4q0xasdnwdzsdl6ymgyjt757sgr","pub_key":{"type":"tendermint/PubKeyEd25519","value":"tMZonPQYoooG/xbFVhHg95pTLxx7aO43/qgHFxDagWM="},"power":"1","name":"test"}],"params":{"block":{"max_bytes":"22020096","max_gas":"-1"},"evidence":{"max_age_num_blocks":"100000","max_age_duration":"172800000000000","max_bytes":"1048576"},"validator":{"pub_key_types":["ed25519"]},"version":{"app":"0"},"synchrony":{"precision":"0","message_delay":"0"},"feature":{"vote_extensions_enable_height":"0","pbts_enable_height":"0"}}}} \ No newline at end of file diff --git a/x/genutil/utils.go b/x/genutil/utils.go index b20b7bfa6ec..ee7d11e39e8 100644 --- a/x/genutil/utils.go +++ b/x/genutil/utils.go @@ -14,11 +14,11 @@ import ( tmed25519 "github.com/cometbft/cometbft/crypto/ed25519" "github.com/cometbft/cometbft/p2p" "github.com/cometbft/cometbft/privval" - cmttypes "github.com/cometbft/cometbft/types" "github.com/cosmos/go-bip39" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/genutil/types" ) @@ -35,7 +35,7 @@ func ExportGenesisFile(genesis *types.AppGenesis, genFile string) error { // ExportGenesisFileWithTime creates and writes the genesis configuration to disk. // An error is returned if building or writing the configuration to file fails. func ExportGenesisFileWithTime( - genFile, chainID string, validators []cmttypes.GenesisValidator, appState json.RawMessage, genTime time.Time, + genFile, chainID string, validators []sdk.GenesisValidator, appState json.RawMessage, genTime time.Time, ) error { appGenesis := types.NewAppGenesisWithVersion(chainID, appState) appGenesis.GenesisTime = genTime diff --git a/x/genutil/v2/cli/export.go b/x/genutil/v2/cli/export.go index 318ad58d8a4..9b0d992bad0 100644 --- a/x/genutil/v2/cli/export.go +++ b/x/genutil/v2/cli/export.go @@ -76,6 +76,7 @@ func ExportCmd(appExporter v2.AppExporter) *cobra.Command { appGenesis.AppState = exported.AppState appGenesis.InitialHeight = exported.Height + appGenesis.Consensus.Validators = exported.Validators out, err := json.Marshal(appGenesis) if err != nil { diff --git a/x/genutil/v2/types.go b/x/genutil/v2/types.go index 3199a5a5afb..fbf288365a5 100644 --- a/x/genutil/v2/types.go +++ b/x/genutil/v2/types.go @@ -3,6 +3,8 @@ package v2 import ( "context" "encoding/json" + + sdk "github.com/cosmos/cosmos-sdk/types" ) // AppExporter is a function that dumps all app state to @@ -20,4 +22,6 @@ type ExportedApp struct { AppState json.RawMessage // Height is the app's latest block height. Height int64 + // Validators is the exported validator set. + Validators []sdk.GenesisValidator } diff --git a/x/gov/README.md b/x/gov/README.md index cda1af118b6..543639749f8 100644 --- a/x/gov/README.md +++ b/x/gov/README.md @@ -37,8 +37,6 @@ staking token of the chain. * [Parameters and base types](#parameters-and-base-types) * [Deposit](#deposit-1) * [ValidatorGovInfo](#validatorgovinfo) - * [Stores](#stores) - * [Proposal Processing Queue](#proposal-processing-queue) * [Legacy Proposal](#legacy-proposal) * [Messages](#messages) * [Proposal Submission](#proposal-submission-1) diff --git a/x/gov/go.mod b/x/gov/go.mod index f86ae5dcf8c..aa00aff3821 100644 --- a/x/gov/go.mod +++ b/x/gov/go.mod @@ -29,7 +29,7 @@ require ( github.com/stretchr/testify v1.9.0 google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 google.golang.org/grpc v1.67.1 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 ) require ( @@ -152,14 +152,14 @@ require ( go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect diff --git a/x/gov/go.sum b/x/gov/go.sum index 46e0295dc4b..44543b2fdd8 100644 --- a/x/gov/go.sum +++ b/x/gov/go.sum @@ -519,8 +519,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -603,19 +603,19 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -675,8 +675,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/x/group/go.mod b/x/group/go.mod index 6f83519f408..3bcfadb12db 100644 --- a/x/group/go.mod +++ b/x/group/go.mod @@ -30,7 +30,7 @@ require ( github.com/stretchr/testify v1.9.0 google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 google.golang.org/grpc v1.67.1 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 pgregory.net/rapid v1.1.0 ) @@ -159,14 +159,14 @@ require ( go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect diff --git a/x/group/go.sum b/x/group/go.sum index dbc625fc5f5..3a730ba7561 100644 --- a/x/group/go.sum +++ b/x/group/go.sum @@ -521,8 +521,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -605,19 +605,19 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -677,8 +677,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/x/group/internal/orm/prefixstore/prefixstore.go b/x/group/internal/orm/prefixstore/prefixstore.go index 5ac69775626..a121baf464d 100644 --- a/x/group/internal/orm/prefixstore/prefixstore.go +++ b/x/group/internal/orm/prefixstore/prefixstore.go @@ -21,7 +21,7 @@ func New(store store.KVStore, prefix []byte) store.KVStore { var _ store.KVStore = Store{} -// Store is similar with cometbft/cometbft/libs/db/prefix_db +// Store is similar with cometbft/cometbft-db/blob/v1.0.1/prefixdb.go // both gives access only to the limited subset of the store // for convenience or safety type Store struct { @@ -63,7 +63,7 @@ func (s Store) Set(key, value []byte) error { func (s Store) Delete(key []byte) error { return s.parent.Delete(s.key(key)) } // Implements KVStore -// Check https://github.com/cometbft/cometbft/blob/master/libs/db/prefix_db.go#L106 +// Check https://github.com/cometbft/cometbft-db/blob/v1.0.1/prefixdb.go#L109 func (s Store) Iterator(start, end []byte) (store.Iterator, error) { newstart := cloneAppend(s.prefix, start) @@ -83,7 +83,7 @@ func (s Store) Iterator(start, end []byte) (store.Iterator, error) { } // ReverseIterator implements KVStore -// Check https://github.com/cometbft/cometbft/blob/master/libs/db/prefix_db.go#L129 +// Check https://github.com/cometbft/cometbft-db/blob/v1.0.1/prefixdb.go#L132 func (s Store) ReverseIterator(start, end []byte) (store.Iterator, error) { newstart := cloneAppend(s.prefix, start) @@ -180,7 +180,7 @@ func (pi *prefixIterator) Error() error { return nil } -// copied from github.com/cometbft/cometbft/libs/db/prefix_db.go +// copied from github.com/cometbft/cometbft-db/blob/v1.0.1/prefixdb.go func stripPrefix(key, prefix []byte) []byte { if len(key) < len(prefix) || !bytes.Equal(key[:len(prefix)], prefix) { panic("should not happen") diff --git a/x/group/keeper/abci.go b/x/group/keeper/abci.go index 6de580de74b..c8d98037549 100644 --- a/x/group/keeper/abci.go +++ b/x/group/keeper/abci.go @@ -3,7 +3,7 @@ package keeper import ( "context" - "cosmossdk.io/x/gov/types" + "cosmossdk.io/x/group" "github.com/cosmos/cosmos-sdk/telemetry" ) @@ -12,7 +12,7 @@ import ( // prunes expired proposals. func (k Keeper) EndBlocker(ctx context.Context) error { start := telemetry.Now() - defer telemetry.ModuleMeasureSince(types.ModuleName, start, telemetry.MetricKeyEndBlocker) + defer telemetry.ModuleMeasureSince(group.ModuleName, start, telemetry.MetricKeyEndBlocker) if err := k.TallyProposalsAtVPEnd(ctx); err != nil { return err diff --git a/x/mint/README.md b/x/mint/README.md index 5a885208eca..db344931392 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. diff --git a/x/mint/go.mod b/x/mint/go.mod index b0343d16ef0..3fa424b75c6 100644 --- a/x/mint/go.mod +++ b/x/mint/go.mod @@ -146,18 +146,18 @@ require ( go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect pgregory.net/rapid v1.1.0 // indirect diff --git a/x/mint/go.sum b/x/mint/go.sum index c9df997e10e..32bdeed756c 100644 --- a/x/mint/go.sum +++ b/x/mint/go.sum @@ -513,8 +513,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -595,19 +595,19 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -667,8 +667,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/x/nft/README.md b/x/nft/README.md index 34c1d40660b..3bf1b6b41b4 100644 --- a/x/nft/README.md +++ b/x/nft/README.md @@ -22,6 +22,8 @@ sidebar_position: 1 * [Messages](#messages) * [MsgSend](#msgsend) * [Events](#events) +* [Queries](#queries) +* [Keeper Functions](#keeper-functions) ## Concepts @@ -86,4 +88,30 @@ The message handling should fail if: ## Events -The nft module emits proto events defined in [the Protobuf reference](https://buf.build/cosmos/cosmos-sdk/docs/main:cosmos.nft.v1beta1). +The NFT module emits proto events defined in [the Protobuf reference](https://buf.build/cosmos/cosmos-sdk/docs/main:cosmos.nft.v1beta1). + +## Queries + +The `x/nft` module provides several queries to retrieve information about NFTs and classes: + +* `Balance`: Returns the number of NFTs of a given class owned by the owner. +* `Owner`: Returns the owner of an NFT based on its class and ID. +* `Supply`: Returns the number of NFTs from the given class. +* `NFTs`: Queries all NFTs of a given class or owner. +* `NFT`: Returns an NFT based on its class and ID. +* `Class`: Returns an NFT class based on its ID. +* `Classes`: Returns all NFT classes. + +## Keeper Functions + +The Keeper of the `x/nft` module provides several functions to manage NFTs: + +* `Mint`: Mints a new NFT. +* `Burn`: Burns an existing NFT. +* `Update`: Updates an existing NFT. +* `Transfer`: Transfers an NFT from one owner to another. +* `GetNFT`: Retrieves information about a specific NFT. +* `GetNFTsOfClass`: Retrieves all NFTs of a specific class. +* `GetNFTsOfClassByOwner`: Retrieves all NFTs of a specific class belonging to an owner. +* `GetBalance`: Retrieves the balance of NFTs of a specific class for an owner. +* `GetTotalSupply`: Retrieves the total supply of NFTs of a specific class. diff --git a/x/nft/go.mod b/x/nft/go.mod index baca50bed95..218cf068c65 100644 --- a/x/nft/go.mod +++ b/x/nft/go.mod @@ -148,18 +148,18 @@ require ( go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.1 // indirect diff --git a/x/nft/go.sum b/x/nft/go.sum index c9df997e10e..32bdeed756c 100644 --- a/x/nft/go.sum +++ b/x/nft/go.sum @@ -513,8 +513,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -595,19 +595,19 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -667,8 +667,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/x/params/go.mod b/x/params/go.mod index f072f3b0256..3c17e2ee24e 100644 --- a/x/params/go.mod +++ b/x/params/go.mod @@ -140,18 +140,18 @@ require ( go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.1 // indirect diff --git a/x/params/go.sum b/x/params/go.sum index 048c9a1067e..b4a09a3147f 100644 --- a/x/params/go.sum +++ b/x/params/go.sum @@ -487,8 +487,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -568,19 +568,19 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -640,8 +640,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/x/protocolpool/go.mod b/x/protocolpool/go.mod index 5454bc67d0d..68a32e3a3a0 100644 --- a/x/protocolpool/go.mod +++ b/x/protocolpool/go.mod @@ -20,7 +20,7 @@ require ( github.com/stretchr/testify v1.9.0 google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 google.golang.org/grpc v1.67.1 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 ) require ( @@ -149,14 +149,14 @@ require ( go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect diff --git a/x/protocolpool/go.sum b/x/protocolpool/go.sum index c9df997e10e..32bdeed756c 100644 --- a/x/protocolpool/go.sum +++ b/x/protocolpool/go.sum @@ -513,8 +513,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -595,19 +595,19 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -667,8 +667,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/x/slashing/README.md b/x/slashing/README.md index c6da11e0ce2..942b3afcc05 100644 --- a/x/slashing/README.md +++ b/x/slashing/README.md @@ -143,7 +143,7 @@ bonded validator. The `SignedBlocksWindow` parameter defines the size The information stored for tracking validator liveness is as follows: ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/release/v0.52.x/x/slashing/proto/cosmos/slashing/v1beta1/slashing.proto#L13-L35 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/slashing/proto/cosmos/slashing/v1beta1/slashing.proto#L13-L35 ``` ### Params @@ -154,7 +154,7 @@ it can be updated with governance or the address with authority. * Params: `0x00 | ProtocolBuffer(Params)` ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/release/v0.52.x/x/slashing/proto/cosmos/slashing/v1beta1/slashing.proto#L37-L62 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/slashing/proto/cosmos/slashing/v1beta1/slashing.proto#L37-L62 ``` ## Messages @@ -312,6 +312,7 @@ The following hooks impact the slashing state: * `AfterValidatorBonded` creates a `ValidatorSigningInfo` instance as described in the following section. * `AfterValidatorCreated` stores a validator's consensus key. * `AfterValidatorRemoved` removes a validator's consensus key. +* `AfterConsensusPubKeyUpdate` handles the rotation of signing info and updates the address-pubkey relation after a consensus key update. ### Validator Bonded @@ -633,6 +634,20 @@ Example: simd tx slashing unjail --from mykey ``` +#### update-params-proposal + +The `update-params-proposal` command allows users to submit a governance proposal to update the slashing module parameters: + +```bash +simd tx slashing update-params-proposal [flags] +``` + +Example: + +```bash +simd tx slashing update-params-proposal '{ "signed_blocks_window": "100" }' +``` + ### gRPC A user can query the `slashing` module using gRPC endpoints. diff --git a/x/slashing/go.mod b/x/slashing/go.mod index c207b1c5317..c68f85babc2 100644 --- a/x/slashing/go.mod +++ b/x/slashing/go.mod @@ -22,7 +22,7 @@ require ( github.com/stretchr/testify v1.9.0 google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 google.golang.org/grpc v1.67.1 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 gotest.tools/v3 v3.5.1 ) @@ -151,14 +151,14 @@ require ( go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect diff --git a/x/slashing/go.sum b/x/slashing/go.sum index 9600563cd53..63c75b76d33 100644 --- a/x/slashing/go.sum +++ b/x/slashing/go.sum @@ -515,8 +515,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -597,19 +597,19 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -669,8 +669,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/x/staking/genesis.go b/x/staking/genesis.go index d09ada842f4..938dc466656 100644 --- a/x/staking/genesis.go +++ b/x/staking/genesis.go @@ -9,23 +9,12 @@ import ( "cosmossdk.io/x/staking/keeper" "cosmossdk.io/x/staking/types" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" sdk "github.com/cosmos/cosmos-sdk/types" ) -// TODO: move this to sdk types and use this instead of comet types GenesisValidator -// then we can do pubkey conversion in ToGenesisDoc -// -// this is a temporary work around to avoid import comet directly in staking -type GenesisValidator struct { - Address sdk.ConsAddress - PubKey cryptotypes.PubKey - Power int64 - Name string -} - // WriteValidators returns a slice of bonded genesis validators. -func WriteValidators(ctx context.Context, keeper *keeper.Keeper) (vals []GenesisValidator, returnErr error) { +func WriteValidators(ctx context.Context, keeper *keeper.Keeper) (vals []sdk.GenesisValidator, returnErr error) { err := keeper.LastValidatorPower.Walk(ctx, nil, func(key []byte, _ gogotypes.Int64Value) (bool, error) { validator, err := keeper.GetValidator(ctx, key) if err != nil { @@ -37,10 +26,14 @@ func WriteValidators(ctx context.Context, keeper *keeper.Keeper) (vals []Genesis returnErr = err return true, err } + jsonPk, err := cryptocodec.PubKeyFromProto(pk) + if err != nil { + return true, err + } - vals = append(vals, GenesisValidator{ - Address: sdk.ConsAddress(pk.Address()), - PubKey: pk, + vals = append(vals, sdk.GenesisValidator{ + Address: pk.Address().Bytes(), + PubKey: jsonPk, Power: validator.GetConsensusPower(keeper.PowerReduction(ctx)), Name: validator.GetMoniker(), }) diff --git a/x/staking/go.mod b/x/staking/go.mod index 9c4cdacb125..b6cd0cf1a17 100644 --- a/x/staking/go.mod +++ b/x/staking/go.mod @@ -25,7 +25,7 @@ require ( github.com/stretchr/testify v1.9.0 google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 google.golang.org/grpc v1.67.1 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 ) require ( @@ -134,13 +134,13 @@ require ( gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect diff --git a/x/staking/go.sum b/x/staking/go.sum index 7588b565a0d..4d7d4939fa7 100644 --- a/x/staking/go.sum +++ b/x/staking/go.sum @@ -511,8 +511,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -593,19 +593,19 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -665,8 +665,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/x/tx/CHANGELOG.md b/x/tx/CHANGELOG.md index da689199910..9fc07fac5ac 100644 --- a/x/tx/CHANGELOG.md +++ b/x/tx/CHANGELOG.md @@ -33,8 +33,7 @@ Since v0.13.0, x/tx follows Cosmos SDK semver: https://github.com/cosmos/cosmos- ## [Unreleased] -### Improvements - +* [#21825](https://github.com/cosmos/cosmos-sdk/pull/21825) Fix decimal encoding and field ordering in Amino JSON encoder. * [#21850](https://github.com/cosmos/cosmos-sdk/pull/21850) Support bytes field as signer. ## [v0.13.5](https://github.com/cosmos/cosmos-sdk/releases/tag/x/tx/v0.13.5) - 2024-09-18 diff --git a/x/tx/decode/decode.go b/x/tx/decode/decode.go index 994d54cf488..bf4f3a54f31 100644 --- a/x/tx/decode/decode.go +++ b/x/tx/decode/decode.go @@ -84,7 +84,7 @@ func (d *Decoder) Decode(txBytes []byte) (*DecodedTx, error) { err = proto.Unmarshal(txBytes, &raw) if err != nil { - return nil, err + return nil, errorsmod.Wrap(ErrTxDecode, err.Error()) } var body v1beta1.TxBody @@ -136,7 +136,7 @@ func (d *Decoder) Decode(txBytes []byte) (*DecodedTx, error) { dynamicMsg := dynamicpb.NewMessageType(msgDesc.(protoreflect.MessageDescriptor)).New().Interface() err = anyMsg.UnmarshalTo(dynamicMsg) if err != nil { - return nil, err + return nil, errorsmod.Wrap(ErrTxDecode, fmt.Sprintf("cannot unmarshal Any message: %v", err)) } dynamicMsgs = append(dynamicMsgs, dynamicMsg) @@ -148,7 +148,7 @@ func (d *Decoder) Decode(txBytes []byte) (*DecodedTx, error) { msg := reflect.New(gogoType.Elem()).Interface().(gogoproto.Message) err = d.codec.Unmarshal(anyMsg.Value, msg) if err != nil { - return nil, err + return nil, errorsmod.Wrap(ErrTxDecode, err.Error()) } msgs = append(msgs, msg) diff --git a/x/tx/go.mod b/x/tx/go.mod index 9d1bab0a955..d430694f1dc 100644 --- a/x/tx/go.mod +++ b/x/tx/go.mod @@ -4,7 +4,7 @@ go 1.23 require ( cosmossdk.io/api v0.7.6 - cosmossdk.io/core v1.0.0-alpha.3 + cosmossdk.io/core v1.0.0-alpha.4 cosmossdk.io/errors v1.0.1 cosmossdk.io/math v1.3.0 github.com/cosmos/cosmos-proto v1.0.0-beta.5 @@ -15,7 +15,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.9.0 github.com/tendermint/go-amino v0.16.0 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 gotest.tools/v3 v3.5.1 pgregory.net/rapid v1.1.0 ) diff --git a/x/tx/go.sum b/x/tx/go.sum index 86e1a66db15..0a507668b54 100644 --- a/x/tx/go.sum +++ b/x/tx/go.sum @@ -1,7 +1,7 @@ cosmossdk.io/api v0.7.6 h1:PC20PcXy1xYKH2KU4RMurVoFjjKkCgYRbVAD4PdqUuY= cosmossdk.io/api v0.7.6/go.mod h1:IcxpYS5fMemZGqyYtErK7OqvdM0C8kdW3dq8Q/XIG38= -cosmossdk.io/core v1.0.0-alpha.3 h1:pnxaYAas7llXgVz1lM7X6De74nWrhNKnB3yMKe4OUUA= -cosmossdk.io/core v1.0.0-alpha.3/go.mod h1:3u9cWq1FAVtiiCrDPpo4LhR+9V6k/ycSG4/Y/tREWCY= +cosmossdk.io/core v1.0.0-alpha.4 h1:9iuroT9ejDYETCsGkzkvs/wAY/5UFl7nCIINFRxyMJY= +cosmossdk.io/core v1.0.0-alpha.4/go.mod h1:3u9cWq1FAVtiiCrDPpo4LhR+9V6k/ycSG4/Y/tREWCY= cosmossdk.io/errors v1.0.1 h1:bzu+Kcr0kS/1DuPBtUFdWjzLqyUuCiyHjyJB6srBV/0= cosmossdk.io/errors v1.0.1/go.mod h1:MeelVSZThMi4bEakzhhhE/CKqVv3nOJDA25bIqRDu/U= cosmossdk.io/math v1.3.0 h1:RC+jryuKeytIiictDslBP9i1fhkVm6ZDmZEoNP316zE= @@ -59,8 +59,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/x/tx/signing/aminojson/encoder.go b/x/tx/signing/aminojson/encoder.go index 9fe589c0e54..fa327d5c650 100644 --- a/x/tx/signing/aminojson/encoder.go +++ b/x/tx/signing/aminojson/encoder.go @@ -43,7 +43,7 @@ func cosmosIntEncoder(_ *Encoder, v protoreflect.Value, w io.Writer) error { } } -// cosmosDecEncoder provides legacy compatible encoding for cosmos.Dec and cosmos.Int types. These are sometimes +// cosmosDecEncoder provides legacy compatible encoding for cosmos.Dec types. These are sometimes // represented as strings in pulsar messages and sometimes as bytes. This encoder handles both cases. func cosmosDecEncoder(_ *Encoder, v protoreflect.Value, w io.Writer) error { switch val := v.Interface().(type) { @@ -51,7 +51,12 @@ func cosmosDecEncoder(_ *Encoder, v protoreflect.Value, w io.Writer) error { if val == "" { return jsonMarshal(w, "0") } - return jsonMarshal(w, val) + var dec math.LegacyDec + err := dec.Unmarshal([]byte(val)) + if err != nil { + return fmt.Errorf("failed to unmarshal for Amino JSON encoding; string %q into Dec: %w", val, err) + } + return jsonMarshal(w, dec.String()) case []byte: if len(val) == 0 { return jsonMarshal(w, "0") @@ -125,27 +130,40 @@ func keyFieldEncoder(_ *Encoder, msg protoreflect.Message, w io.Writer) error { } type moduleAccountPretty struct { - Address string `json:"address"` - PubKey string `json:"public_key"` AccountNumber uint64 `json:"account_number"` - Sequence uint64 `json:"sequence"` + Address string `json:"address"` Name string `json:"name"` Permissions []string `json:"permissions"` + PubKey string `json:"public_key"` + Sequence uint64 `json:"sequence"` } // moduleAccountEncoder replicates the behavior in // https://github.com/cosmos/cosmos-sdk/blob/41a3dfeced2953beba3a7d11ec798d17ee19f506/x/auth/types/account.go#L230-L254 func moduleAccountEncoder(_ *Encoder, msg protoreflect.Message, w io.Writer) error { - ma := msg.Interface().(*authapi.ModuleAccount) + ma := &authapi.ModuleAccount{} + msgDesc := msg.Descriptor() + if msgDesc.FullName() != ma.ProtoReflect().Descriptor().FullName() { + return errors.New("moduleAccountEncoder: msg not a auth.ModuleAccount") + } + fields := msgDesc.Fields() + pretty := moduleAccountPretty{ - PubKey: "", - Name: ma.Name, - Permissions: ma.Permissions, - } - if ma.BaseAccount != nil { - pretty.Address = ma.BaseAccount.Address - pretty.AccountNumber = ma.BaseAccount.AccountNumber - pretty.Sequence = ma.BaseAccount.Sequence + PubKey: "", + Name: msg.Get(fields.ByName("name")).String(), + } + permissions := msg.Get(fields.ByName("permissions")).List() + for i := 0; i < permissions.Len(); i++ { + pretty.Permissions = append(pretty.Permissions, permissions.Get(i).String()) + } + + if msg.Has(fields.ByName("base_account")) { + baseAccount := msg.Get(fields.ByName("base_account")) + baMsg := baseAccount.Message() + bamdFields := baMsg.Descriptor().Fields() + pretty.Address = baMsg.Get(bamdFields.ByName("address")).String() + pretty.AccountNumber = baMsg.Get(bamdFields.ByName("account_number")).Uint() + pretty.Sequence = baMsg.Get(bamdFields.ByName("sequence")).Uint() } else { pretty.Address = "" pretty.AccountNumber = 0 @@ -166,29 +184,34 @@ func moduleAccountEncoder(_ *Encoder, msg protoreflect.Message, w io.Writer) err // also see: // https://github.com/cosmos/cosmos-sdk/blob/b49f948b36bc991db5be431607b475633aed697e/proto/cosmos/crypto/multisig/keys.proto#L15/ func thresholdStringEncoder(enc *Encoder, msg protoreflect.Message, w io.Writer) error { - pk, ok := msg.Interface().(*multisig.LegacyAminoPubKey) - if !ok { + pk := &multisig.LegacyAminoPubKey{} + msgDesc := msg.Descriptor() + fields := msgDesc.Fields() + if msgDesc.FullName() != pk.ProtoReflect().Descriptor().FullName() { return errors.New("thresholdStringEncoder: msg not a multisig.LegacyAminoPubKey") } - _, err := fmt.Fprintf(w, `{"threshold":"%d","pubkeys":`, pk.Threshold) - if err != nil { - return err - } - - if len(pk.PublicKeys) == 0 { - _, err = io.WriteString(w, `[]}`) - return err - } - fields := msg.Descriptor().Fields() pubkeysField := fields.ByName("public_keys") pubkeys := msg.Get(pubkeysField).List() - err = enc.marshalList(pubkeys, pubkeysField, w) + _, err := io.WriteString(w, `{"pubkeys":`) if err != nil { return err } - _, err = io.WriteString(w, `}`) + if pubkeys.Len() == 0 { + _, err := io.WriteString(w, `[]`) + if err != nil { + return err + } + } else { + err := enc.marshalList(pubkeys, pubkeysField, w) + if err != nil { + return err + } + } + + threshold := fields.ByName("threshold") + _, err = fmt.Fprintf(w, `,"threshold":"%d"}`, msg.Get(threshold).Uint()) return err } diff --git a/x/tx/signing/aminojson/json_marshal.go b/x/tx/signing/aminojson/json_marshal.go index 52defcca635..6629fb35050 100644 --- a/x/tx/signing/aminojson/json_marshal.go +++ b/x/tx/signing/aminojson/json_marshal.go @@ -16,6 +16,8 @@ import ( "cosmossdk.io/x/tx/signing" ) +const cosmosDecType = "cosmos.Dec" + // MessageEncoder is a function that can encode a protobuf protoreflect.Message to JSON. type MessageEncoder func(*Encoder, protoreflect.Message, io.Writer) error @@ -68,8 +70,8 @@ func NewEncoder(options EncoderOptions) Encoder { } enc := Encoder{ cosmosProtoScalarEncoders: map[string]FieldEncoder{ - "cosmos.Dec": cosmosDecEncoder, - "cosmos.Int": cosmosIntEncoder, + cosmosDecType: cosmosDecEncoder, + "cosmos.Int": cosmosIntEncoder, }, aminoMessageEncoders: map[string]MessageEncoder{ "key_field": keyFieldEncoder, @@ -366,7 +368,7 @@ func (enc Encoder) marshalMessage(msg protoreflect.Message, writer io.Writer) er } // encode value - if encoder := enc.getFieldEncoding(f); encoder != nil { + if encoder := enc.getFieldEncoder(f); encoder != nil { err = encoder(&enc, v, writer) if err != nil { return err diff --git a/x/tx/signing/aminojson/options.go b/x/tx/signing/aminojson/options.go index cf9110aef3a..c5c9462ef10 100644 --- a/x/tx/signing/aminojson/options.go +++ b/x/tx/signing/aminojson/options.go @@ -2,11 +2,14 @@ package aminojson import ( cosmos_proto "github.com/cosmos/cosmos-proto" + gogo "github.com/cosmos/gogoproto/gogoproto" gogoproto "github.com/cosmos/gogoproto/proto" "github.com/iancoleman/strcase" "github.com/pkg/errors" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/runtime/protoimpl" + "google.golang.org/protobuf/types/descriptorpb" "cosmossdk.io/api/amino" ) @@ -100,7 +103,16 @@ func (enc Encoder) getMessageEncoder(message protoreflect.Message) MessageEncode return nil } -func (enc Encoder) getFieldEncoding(field protoreflect.FieldDescriptor) FieldEncoder { +var customTypeExtension = protoimpl.ExtensionInfo{ + ExtendedType: (*descriptorpb.FieldOptions)(nil), + ExtensionType: gogo.E_Customtype.ExtensionType, + Field: gogo.E_Customtype.Field, + Name: gogo.E_Customtype.Name, + Tag: gogo.E_Customtype.Tag, + Filename: gogo.E_Customtype.Filename, +} + +func (enc Encoder) getFieldEncoder(field protoreflect.FieldDescriptor) FieldEncoder { opts := field.Options() if proto.HasExtension(opts, amino.E_Encoding) { encoding := proto.GetExtension(opts, amino.E_Encoding).(string) @@ -110,6 +122,18 @@ func (enc Encoder) getFieldEncoding(field protoreflect.FieldDescriptor) FieldEnc } if proto.HasExtension(opts, cosmos_proto.E_Scalar) { scalar := proto.GetExtension(opts, cosmos_proto.E_Scalar).(string) + // do not handle encoding of fields tagged only with scalar which are not backed by a + // LegacyDec custom type. This types are handled by the default encoding, as they are + // expected to already be encoded as their human readable string representation + // containing a radix, i.e. "1.2345". + // For example: + // https://github.com/cosmos/cosmos-sdk/blob/9076487d035e43d39fe54e8498da1ce31b9c845c/x/gov/proto/cosmos/gov/v1/gov.proto#L274 + if scalar == cosmosDecType { + customType := proto.GetExtension(opts, &customTypeExtension) + if customType != "cosmossdk.io/math.LegacyDec" { + return nil + } + } if fn, ok := enc.cosmosProtoScalarEncoders[scalar]; ok { return fn } diff --git a/x/upgrade/README.md b/x/upgrade/README.md index 81534d42269..9e9c41e8ccc 100644 --- a/x/upgrade/README.md +++ b/x/upgrade/README.md @@ -106,7 +106,7 @@ the `Plan`, which targets a specific `Handler`, is persisted and scheduled. The upgrade can be delayed or hastened by updating the `Plan.Height` in a new proposal. ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/upgrade/v1beta1/tx.proto#L29-L41 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/upgrade/proto/cosmos/upgrade/v1beta1/tx.proto#L29-L40 ``` #### Cancelling Upgrade Proposals @@ -118,7 +118,7 @@ Of course this requires that the upgrade was known to be a bad idea well before upgrade itself, to allow time for a vote. ```protobuf reference -https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/upgrade/v1beta1/tx.proto#L48-L57 +https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.1/x/upgrade/proto/cosmos/upgrade/v1beta1/tx.proto#L47-L55 ``` If such a possibility is desired, the upgrade height is to be @@ -315,6 +315,28 @@ time: "0001-01-01T00:00:00Z" upgraded_client_state: null ``` +##### authority + +The `authority` command allows users to query the address that is authorized to submit upgrade proposals. + +```bash +simd query upgrade authority [flags] +``` + +This command returns the bech32-encoded address of the account that has the authority to submit upgrade proposals. + +Example: + +```bash +simd query upgrade authority +``` + +Example Output: + +```bash +cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn +``` + #### Transactions The upgrade module supports the following transactions: @@ -326,10 +348,10 @@ simd tx upgrade software-upgrade v2 --title="Test Proposal" --summary="testing" --upgrade-info '{ "binaries": { "linux/amd64":"https://example.com/simd.zip?checksum=sha256:aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f" } }' --from cosmos1.. ``` -* `cancel-software-upgrade` - cancels a previously submitted upgrade proposal: +* `cancel-upgrade-proposal` - cancels a previously submitted upgrade proposal: ```bash -simd tx upgrade cancel-software-upgrade --title="Test Proposal" --summary="testing" --deposit="100000000stake" --from cosmos1.. +simd tx upgrade cancel-upgrade-proposal --title="Test Proposal" --summary="testing" --deposit="100000000stake" --from cosmos1.. ``` ### REST @@ -467,6 +489,28 @@ Example Output: } ``` +#### Authority + +`Authority` queries the address that is authorized to submit upgrade proposals. + +```bash +/cosmos/upgrade/v1beta1/authority +``` + +Example: + +```bash +curl -X GET "http://localhost:1317/cosmos/upgrade/v1beta1/authority" -H "accept: application/json" +``` + +Example Output: + +```json +{ +"address": "cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn" +} +``` + ### gRPC A user can query the `upgrade` module using gRPC endpoints. @@ -507,7 +551,7 @@ cosmos.upgrade.v1beta1.Query/CurrentPlan Example: ```bash -grpcurl -plaintext localhost:9090 cosmos.slashing.v1beta1.Query/CurrentPlan +grpcurl -plaintext localhost:9090 cosmos.upgrade.v1beta1.Query/CurrentPlan ``` Example Output: @@ -529,7 +573,7 @@ cosmos.upgrade.v1beta1.Query/ModuleVersions Example: ```bash -grpcurl -plaintext localhost:9090 cosmos.slashing.v1beta1.Query/ModuleVersions +grpcurl -plaintext localhost:9090 cosmos.upgrade.v1beta1.Query/ModuleVersions ``` Example Output: @@ -605,6 +649,28 @@ Example Output: } ``` +#### Authority + +`Authority` queries the address that is authorized to submit upgrade proposals. + +```bash +cosmos.upgrade.v1beta1.Query/Authority +``` + +Example: + +```bash +grpcurl -plaintext localhost:9090 cosmos.upgrade.v1beta1.Query/Authority +``` + +Example Output: + +```json +{ + "address": "cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn" +} +``` + ## Resources A list of (external) resources to learn more about the `x/upgrade` module. diff --git a/x/upgrade/go.mod b/x/upgrade/go.mod index 6505f6b4b02..7f9a2eafcd9 100644 --- a/x/upgrade/go.mod +++ b/x/upgrade/go.mod @@ -28,7 +28,7 @@ require ( github.com/stretchr/testify v1.9.0 google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 google.golang.org/grpc v1.67.1 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 ) require ( @@ -177,15 +177,15 @@ require ( go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.6.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/api v0.192.0 // indirect diff --git a/x/upgrade/go.sum b/x/upgrade/go.sum index 257f52f8aca..026ac0a983f 100644 --- a/x/upgrade/go.sum +++ b/x/upgrade/go.sum @@ -851,8 +851,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1071,13 +1071,13 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1088,8 +1088,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1382,8 +1382,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=