Skip to content

Commit d6364b8

Browse files
authored
refactor(stf): remove RunWithCtx (#21739)
1 parent 98eb0b7 commit d6364b8

File tree

16 files changed

+218
-85
lines changed

16 files changed

+218
-85
lines changed

core/header/service.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func (i *Info) Bytes() ([]byte, error) {
3535

3636
// Encode Hash
3737
if len(i.Hash) != hashSize {
38-
return nil, errors.New("invalid hash size")
38+
return nil, errors.New("invalid Hash size")
3939
}
4040

4141
buf = append(buf, i.Hash...)
@@ -47,7 +47,7 @@ func (i *Info) Bytes() ([]byte, error) {
4747

4848
// Encode AppHash
4949
if len(i.AppHash) != hashSize {
50-
return nil, errors.New("invalid hash size")
50+
return nil, errors.New("invalid AppHash size")
5151
}
5252
buf = append(buf, i.AppHash...)
5353

core/store/service.go

+5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ type KVStoreService interface {
1010
OpenKVStore(context.Context) KVStore
1111
}
1212

13+
// KVStoreServiceFactory is a function that creates a new KVStoreService.
14+
// It can be used to override the default KVStoreService bindings for cases
15+
// where an application must supply a custom stateful backend.
16+
type KVStoreServiceFactory func([]byte) KVStoreService
17+
1318
// MemoryStoreService represents a unique, non-forgeable handle to a memory-backed
1419
// KVStore. It should be provided as a module-scoped dependency by the runtime
1520
// module being used to build the app.

runtime/v2/builder.go

+39-9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package runtime
33
import (
44
"context"
55
"encoding/json"
6+
"errors"
67
"fmt"
78
"io"
89
"path/filepath"
@@ -12,6 +13,7 @@ import (
1213
"cosmossdk.io/core/server"
1314
"cosmossdk.io/core/store"
1415
"cosmossdk.io/core/transaction"
16+
"cosmossdk.io/runtime/v2/services"
1517
"cosmossdk.io/server/v2/appmanager"
1618
"cosmossdk.io/server/v2/stf"
1719
"cosmossdk.io/server/v2/stf/branch"
@@ -157,23 +159,51 @@ func (a *AppBuilder[T]) Build(opts ...AppBuilderOption[T]) (*App[T], error) {
157159
ValidateTxGasLimit: a.app.config.GasConfig.ValidateTxGasLimit,
158160
QueryGasLimit: a.app.config.GasConfig.QueryGasLimit,
159161
SimulationGasLimit: a.app.config.GasConfig.SimulationGasLimit,
160-
InitGenesis: func(ctx context.Context, src io.Reader, txHandler func(json.RawMessage) error) error {
162+
InitGenesis: func(
163+
ctx context.Context,
164+
src io.Reader,
165+
txHandler func(json.RawMessage) error,
166+
) (store.WriterMap, error) {
161167
// this implementation assumes that the state is a JSON object
162168
bz, err := io.ReadAll(src)
163169
if err != nil {
164-
return fmt.Errorf("failed to read import state: %w", err)
170+
return nil, fmt.Errorf("failed to read import state: %w", err)
165171
}
166-
var genesisState map[string]json.RawMessage
167-
if err = json.Unmarshal(bz, &genesisState); err != nil {
168-
return err
172+
var genesisJSON map[string]json.RawMessage
173+
if err = json.Unmarshal(bz, &genesisJSON); err != nil {
174+
return nil, err
169175
}
170-
if err = a.app.moduleManager.InitGenesisJSON(ctx, genesisState, txHandler); err != nil {
171-
return fmt.Errorf("failed to init genesis: %w", err)
176+
177+
v, zeroState, err := a.app.db.StateLatest()
178+
if err != nil {
179+
return nil, fmt.Errorf("unable to get latest state: %w", err)
172180
}
173-
return nil
181+
if v != 0 { // TODO: genesis state may be > 0, we need to set version on store
182+
return nil, errors.New("cannot init genesis on non-zero state")
183+
}
184+
genesisCtx := services.NewGenesisContext(a.branch(zeroState))
185+
genesisState, err := genesisCtx.Run(ctx, func(ctx context.Context) error {
186+
err = a.app.moduleManager.InitGenesisJSON(ctx, genesisJSON, txHandler)
187+
if err != nil {
188+
return fmt.Errorf("failed to init genesis: %w", err)
189+
}
190+
return nil
191+
})
192+
193+
return genesisState, err
174194
},
175195
ExportGenesis: func(ctx context.Context, version uint64) ([]byte, error) {
176-
genesisJson, err := a.app.moduleManager.ExportGenesisForModules(ctx)
196+
_, state, err := a.app.db.StateLatest()
197+
if err != nil {
198+
return nil, fmt.Errorf("unable to get latest state: %w", err)
199+
}
200+
genesisCtx := services.NewGenesisContext(a.branch(state))
201+
202+
var genesisJson map[string]json.RawMessage
203+
_, err = genesisCtx.Run(ctx, func(ctx context.Context) error {
204+
genesisJson, err = a.app.moduleManager.ExportGenesisForModules(ctx)
205+
return err
206+
})
177207
if err != nil {
178208
return nil, fmt.Errorf("failed to export genesis: %w", err)
179209
}

runtime/v2/go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.23
55
// server v2 integration
66
replace (
77
cosmossdk.io/api => ../../api
8+
cosmossdk.io/core => ../../core
89
cosmossdk.io/core/testing => ../../core/testing
910
cosmossdk.io/server/v2/appmanager => ../../server/v2/appmanager
1011
cosmossdk.io/server/v2/stf => ../../server/v2/stf

runtime/v2/go.sum

-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ buf.build/gen/go/cometbft/cometbft/protocolbuffers/go v1.34.2-20240701160653-fed
22
buf.build/gen/go/cometbft/cometbft/protocolbuffers/go v1.34.2-20240701160653-fedbb9acfd2f.2/go.mod h1:1+3gJj2NvZ1mTLAtHu+lMhOjGgQPiCKCeo+9MBww0Eo=
33
buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.34.2-20240130113600-88ef6483f90f.2 h1:b7EEYTUHmWSBEyISHlHvXbJPqtKiHRuUignL1tsHnNQ=
44
buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.34.2-20240130113600-88ef6483f90f.2/go.mod h1:HqcXMSa5qnNuakaMUo+hWhF51mKbcrZxGl9Vp5EeJXc=
5-
cosmossdk.io/core v1.0.0-alpha.3 h1:pnxaYAas7llXgVz1lM7X6De74nWrhNKnB3yMKe4OUUA=
6-
cosmossdk.io/core v1.0.0-alpha.3/go.mod h1:3u9cWq1FAVtiiCrDPpo4LhR+9V6k/ycSG4/Y/tREWCY=
75
cosmossdk.io/depinject v1.0.0 h1:dQaTu6+O6askNXO06+jyeUAnF2/ssKwrrszP9t5q050=
86
cosmossdk.io/depinject v1.0.0/go.mod h1:zxK/h3HgHoA/eJVtiSsoaRaRA2D5U4cJ5thIG4ssbB8=
97
cosmossdk.io/errors/v2 v2.0.0-20240731132947-df72853b3ca5 h1:IQNdY2kB+k+1OM2DvqFG1+UgeU1JzZrWtwuWzI3ZfwA=

runtime/v2/module.go

+31-7
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1"
1717
appmodulev2 "cosmossdk.io/core/appmodule/v2"
1818
"cosmossdk.io/core/comet"
19+
"cosmossdk.io/core/header"
1920
"cosmossdk.io/core/registry"
2021
"cosmossdk.io/core/server"
2122
"cosmossdk.io/core/store"
@@ -96,7 +97,6 @@ func init() {
9697
ProvideAppBuilder[transaction.Tx],
9798
ProvideEnvironment[transaction.Tx],
9899
ProvideModuleManager[transaction.Tx],
99-
ProvideCometService,
100100
),
101101
appconfig.Invoke(SetupAppBuilder),
102102
)
@@ -176,6 +176,8 @@ func ProvideEnvironment[T transaction.Tx](
176176
config *runtimev2.Module,
177177
key depinject.ModuleKey,
178178
appBuilder *AppBuilder[T],
179+
kvFactory store.KVStoreServiceFactory,
180+
headerService header.Service,
179181
) (
180182
appmodulev2.Environment,
181183
store.KVStoreService,
@@ -197,7 +199,7 @@ func ProvideEnvironment[T transaction.Tx](
197199
}
198200

199201
registerStoreKey(appBuilder, kvStoreKey)
200-
kvService = stf.NewKVStoreService([]byte(kvStoreKey))
202+
kvService = kvFactory([]byte(kvStoreKey))
201203

202204
memStoreKey := fmt.Sprintf("memory:%s", key.Name())
203205
registerStoreKey(appBuilder, memStoreKey)
@@ -209,7 +211,7 @@ func ProvideEnvironment[T transaction.Tx](
209211
BranchService: stf.BranchService{},
210212
EventService: stf.NewEventService(),
211213
GasService: stf.NewGasMeterService(),
212-
HeaderService: stf.HeaderService{},
214+
HeaderService: headerService,
213215
QueryRouterService: stf.NewQueryRouterService(),
214216
MsgRouterService: stf.NewMsgRouterService([]byte(key.Name())),
215217
TransactionService: services.NewContextAwareTransactionService(),
@@ -220,8 +222,8 @@ func ProvideEnvironment[T transaction.Tx](
220222
return env, kvService, memKvService
221223
}
222224

223-
func registerStoreKey[T transaction.Tx](wrapper *AppBuilder[T], key string) {
224-
wrapper.app.storeKeys = append(wrapper.app.storeKeys, key)
225+
func registerStoreKey[T transaction.Tx](builder *AppBuilder[T], key string) {
226+
builder.app.storeKeys = append(builder.app.storeKeys, key)
225227
}
226228

227229
func storeKeyOverride(config *runtimev2.Module, moduleName string) *runtimev2.StoreKeyConfig {
@@ -234,6 +236,28 @@ func storeKeyOverride(config *runtimev2.Module, moduleName string) *runtimev2.St
234236
return nil
235237
}
236238

237-
func ProvideCometService() comet.Service {
238-
return &services.ContextAwareCometInfoService{}
239+
// DefaultServiceBindings provides default services for the following service interfaces:
240+
// - store.KVStoreServiceFactory
241+
// - header.Service
242+
// - comet.Service
243+
//
244+
// They are all required. For most use cases these default services bindings should be sufficient.
245+
// Power users (or tests) may wish to provide their own services bindings, in which case they must
246+
// supply implementations for each of the above interfaces.
247+
func DefaultServiceBindings() depinject.Config {
248+
var (
249+
kvServiceFactory store.KVStoreServiceFactory = func(actor []byte) store.KVStoreService {
250+
return services.NewGenesisKVService(
251+
actor,
252+
stf.NewKVStoreService(actor),
253+
)
254+
}
255+
headerService header.Service = services.NewGenesisHeaderService(stf.HeaderService{})
256+
cometService comet.Service = &services.ContextAwareCometInfoService{}
257+
)
258+
return depinject.Supply(
259+
kvServiceFactory,
260+
headerService,
261+
cometService,
262+
)
239263
}

runtime/v2/services/genesis.go

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package services
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"cosmossdk.io/core/header"
8+
"cosmossdk.io/core/store"
9+
)
10+
11+
var (
12+
_ store.KVStoreService = (*GenesisKVStoreService)(nil)
13+
_ header.Service = (*GenesisHeaderService)(nil)
14+
)
15+
16+
type genesisContextKeyType struct{}
17+
18+
var genesisContextKey = genesisContextKeyType{}
19+
20+
// genesisContext is a context that is used during genesis initialization.
21+
// it backs the store.KVStoreService and header.Service interface implementations
22+
// defined in this file.
23+
type genesisContext struct {
24+
state store.WriterMap
25+
}
26+
27+
// NewGenesisContext creates a new genesis context.
28+
func NewGenesisContext(state store.WriterMap) genesisContext {
29+
return genesisContext{
30+
state: state,
31+
}
32+
}
33+
34+
// Run runs the provided function within the genesis context and returns an
35+
// updated store.WriterMap containing the state modifications made during InitGenesis.
36+
func (g *genesisContext) Run(
37+
ctx context.Context,
38+
fn func(ctx context.Context) error,
39+
) (store.WriterMap, error) {
40+
ctx = context.WithValue(ctx, genesisContextKey, g)
41+
err := fn(ctx)
42+
if err != nil {
43+
return nil, err
44+
}
45+
return g.state, nil
46+
}
47+
48+
// GenesisKVStoreService is a store.KVStoreService implementation that is used during
49+
// genesis initialization. It wraps an inner execution context store.KVStoreService.
50+
type GenesisKVStoreService struct {
51+
actor []byte
52+
executionService store.KVStoreService
53+
}
54+
55+
// NewGenesisKVService creates a new GenesisKVStoreService.
56+
// - actor is the module store key.
57+
// - executionService is the store.KVStoreService to use when the genesis context is not active.
58+
func NewGenesisKVService(
59+
actor []byte,
60+
executionService store.KVStoreService,
61+
) *GenesisKVStoreService {
62+
return &GenesisKVStoreService{
63+
actor: actor,
64+
executionService: executionService,
65+
}
66+
}
67+
68+
// OpenKVStore implements store.KVStoreService.
69+
func (g *GenesisKVStoreService) OpenKVStore(ctx context.Context) store.KVStore {
70+
v := ctx.Value(genesisContextKey)
71+
if v == nil {
72+
return g.executionService.OpenKVStore(ctx)
73+
}
74+
genCtx, ok := v.(*genesisContext)
75+
if !ok {
76+
panic(fmt.Errorf("unexpected genesis context type: %T", v))
77+
}
78+
state, err := genCtx.state.GetWriter(g.actor)
79+
if err != nil {
80+
panic(err)
81+
}
82+
return state
83+
}
84+
85+
// GenesisHeaderService is a header.Service implementation that is used during
86+
// genesis initialization. It wraps an inner execution context header.Service.
87+
type GenesisHeaderService struct {
88+
executionService header.Service
89+
}
90+
91+
// HeaderInfo implements header.Service.
92+
// During genesis initialization, it returns an empty header.Info.
93+
func (g *GenesisHeaderService) HeaderInfo(ctx context.Context) header.Info {
94+
v := ctx.Value(genesisContextKey)
95+
if v == nil {
96+
return g.executionService.HeaderInfo(ctx)
97+
}
98+
return header.Info{}
99+
}
100+
101+
// NewGenesisHeaderService creates a new GenesisHeaderService.
102+
// - executionService is the header.Service to use when the genesis context is not active.
103+
func NewGenesisHeaderService(executionService header.Service) *GenesisHeaderService {
104+
return &GenesisHeaderService{
105+
executionService: executionService,
106+
}
107+
}

server/v2/appmanager/appmanager.go

+10-38
Original file line numberDiff line numberDiff line change
@@ -43,25 +43,19 @@ func (a AppManager[T]) InitGenesis(
4343
initGenesisJSON []byte,
4444
txDecoder transaction.Codec[T],
4545
) (*server.BlockResponse, corestore.WriterMap, error) {
46-
v, zeroState, err := a.db.StateLatest()
47-
if err != nil {
48-
return nil, nil, fmt.Errorf("unable to get latest state: %w", err)
49-
}
50-
if v != 0 { // TODO: genesis state may be > 0, we need to set version on store
51-
return nil, nil, errors.New("cannot init genesis on non-zero state")
52-
}
53-
5446
var genTxs []T
55-
genesisState, err := a.stf.RunWithCtx(ctx, zeroState, func(ctx context.Context) error {
56-
return a.initGenesis(ctx, bytes.NewBuffer(initGenesisJSON), func(jsonTx json.RawMessage) error {
47+
genesisState, err := a.initGenesis(
48+
ctx,
49+
bytes.NewBuffer(initGenesisJSON),
50+
func(jsonTx json.RawMessage) error {
5751
genTx, err := txDecoder.DecodeJSON(jsonTx)
5852
if err != nil {
5953
return fmt.Errorf("failed to decode genesis transaction: %w", err)
6054
}
6155
genTxs = append(genTxs, genTx)
6256
return nil
63-
})
64-
})
57+
},
58+
)
6559
if err != nil {
6660
return nil, nil, fmt.Errorf("failed to import genesis state: %w", err)
6761
}
@@ -89,29 +83,11 @@ func (a AppManager[T]) InitGenesis(
8983

9084
// ExportGenesis exports the genesis state of the application.
9185
func (a AppManager[T]) ExportGenesis(ctx context.Context, version uint64) ([]byte, error) {
92-
zeroState, err := a.db.StateAt(version)
93-
if err != nil {
94-
return nil, fmt.Errorf("unable to get latest state: %w", err)
95-
}
96-
97-
bz := make([]byte, 0)
98-
_, err = a.stf.RunWithCtx(ctx, zeroState, func(ctx context.Context) error {
99-
if a.exportGenesis == nil {
100-
return errors.New("export genesis function not set")
101-
}
102-
103-
bz, err = a.exportGenesis(ctx, version)
104-
if err != nil {
105-
return fmt.Errorf("failed to export genesis state: %w", err)
106-
}
107-
108-
return nil
109-
})
110-
if err != nil {
111-
return nil, fmt.Errorf("failed to export genesis state: %w", err)
86+
if a.exportGenesis == nil {
87+
return nil, errors.New("export genesis function not set")
11288
}
11389

114-
return bz, nil
90+
return a.exportGenesis(ctx, version)
11591
}
11692

11793
func (a AppManager[T]) DeliverBlock(
@@ -180,10 +156,6 @@ func (a AppManager[T]) Query(ctx context.Context, version uint64, request transa
180156
// QueryWithState executes a query with the provided state. This allows to process a query
181157
// independently of the db state. For example, it can be used to process a query with temporary
182158
// and uncommitted state
183-
func (a AppManager[T]) QueryWithState(
184-
ctx context.Context,
185-
state corestore.ReaderMap,
186-
request transaction.Msg,
187-
) (transaction.Msg, error) {
159+
func (a AppManager[T]) QueryWithState(ctx context.Context, state corestore.ReaderMap, request transaction.Msg) (transaction.Msg, error) {
188160
return a.stf.Query(ctx, state, a.config.QueryGasLimit, request)
189161
}

0 commit comments

Comments
 (0)