Skip to content

Commit b070d29

Browse files
julienrbrtmergify[bot]
authored andcommitted
test(systemtests): fix gRPC tests for v1 & v2 (#22774)
(cherry picked from commit 4caac04) # Conflicts: # server/v2/appmanager/appmanager.go # systemtests/CHANGELOG.md # systemtests/rest_support.go # tests/systemtests/go.mod # tests/systemtests/go.sum
1 parent b37887e commit b070d29

File tree

8 files changed

+595
-146
lines changed

8 files changed

+595
-146
lines changed

server/v2/appmanager/appmanager.go

+243
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
package appmanager
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"errors"
8+
"fmt"
9+
10+
"cosmossdk.io/core/server"
11+
corestore "cosmossdk.io/core/store"
12+
"cosmossdk.io/core/transaction"
13+
)
14+
15+
// AppManager is a coordinator for all things related to an application
16+
// It is responsible for interacting with stf and store.
17+
// Runtime/v2 is an extension of this interface.
18+
type AppManager[T transaction.Tx] interface {
19+
// InitGenesis initializes the genesis state of the application.
20+
InitGenesis(
21+
ctx context.Context,
22+
blockRequest *server.BlockRequest[T],
23+
initGenesisJSON []byte,
24+
txDecoder transaction.Codec[T],
25+
) (*server.BlockResponse, corestore.WriterMap, error)
26+
27+
// ExportGenesis exports the genesis state of the application.
28+
ExportGenesis(ctx context.Context, version uint64) ([]byte, error)
29+
30+
// DeliverBlock executes a block of transactions.
31+
DeliverBlock(
32+
ctx context.Context,
33+
block *server.BlockRequest[T],
34+
) (*server.BlockResponse, corestore.WriterMap, error)
35+
36+
// ValidateTx will validate the tx against the latest storage state. This means that
37+
// only the stateful validation will be run, not the execution portion of the tx.
38+
// If full execution is needed, Simulate must be used.
39+
ValidateTx(ctx context.Context, tx T) (server.TxResult, error)
40+
41+
// Simulate runs validation and execution flow of a Tx.
42+
Simulate(ctx context.Context, tx T) (server.TxResult, corestore.WriterMap, error)
43+
44+
// SimulateWithState runs validation and execution flow of a Tx,
45+
// using the provided state instead of loading the latest state from the underlying database.
46+
SimulateWithState(ctx context.Context, state corestore.ReaderMap, tx T) (server.TxResult, corestore.WriterMap, error)
47+
48+
// Query queries the application at the provided version.
49+
// CONTRACT: Version must always be provided, if 0, get latest
50+
Query(ctx context.Context, version uint64, request transaction.Msg) (transaction.Msg, error)
51+
52+
// QueryWithState executes a query with the provided state. This allows to process a query
53+
// independently of the db state. For example, it can be used to process a query with temporary
54+
// and uncommitted state
55+
QueryWithState(ctx context.Context, state corestore.ReaderMap, request transaction.Msg) (transaction.Msg, error)
56+
}
57+
58+
// Store defines the underlying storage behavior needed by AppManager.
59+
type Store interface {
60+
// StateLatest returns a readonly view over the latest
61+
// committed state of the store. Alongside the version
62+
// associated with it.
63+
StateLatest() (uint64, corestore.ReaderMap, error)
64+
65+
// StateAt returns a readonly view over the provided
66+
// state. Must error when the version does not exist.
67+
StateAt(version uint64) (corestore.ReaderMap, error)
68+
}
69+
70+
// appManager is a coordinator for all things related to an application
71+
type appManager[T transaction.Tx] struct {
72+
// Gas limits for validating, querying, and simulating transactions.
73+
config Config
74+
// InitGenesis is a function that initializes the application state from a genesis file.
75+
// It takes a context, a source reader for the genesis file, and a transaction handler function.
76+
initGenesis InitGenesis
77+
// ExportGenesis is a function that exports the application state to a genesis file.
78+
// It takes a context and a version number for the genesis file.
79+
exportGenesis ExportGenesis
80+
// The database for storing application data.
81+
db Store
82+
// The state transition function for processing transactions.
83+
stf StateTransitionFunction[T]
84+
}
85+
86+
func New[T transaction.Tx](
87+
config Config,
88+
db Store,
89+
stf StateTransitionFunction[T],
90+
initGenesisImpl InitGenesis,
91+
exportGenesisImpl ExportGenesis,
92+
) AppManager[T] {
93+
return &appManager[T]{
94+
config: config,
95+
db: db,
96+
stf: stf,
97+
initGenesis: initGenesisImpl,
98+
exportGenesis: exportGenesisImpl,
99+
}
100+
}
101+
102+
// InitGenesis initializes the genesis state of the application.
103+
func (a appManager[T]) InitGenesis(
104+
ctx context.Context,
105+
blockRequest *server.BlockRequest[T],
106+
initGenesisJSON []byte,
107+
txDecoder transaction.Codec[T],
108+
) (*server.BlockResponse, corestore.WriterMap, error) {
109+
var genTxs []T
110+
genesisState, valUpdates, err := a.initGenesis(
111+
ctx,
112+
bytes.NewBuffer(initGenesisJSON),
113+
func(jsonTx json.RawMessage) error {
114+
genTx, err := txDecoder.DecodeJSON(jsonTx)
115+
if err != nil {
116+
return fmt.Errorf("failed to decode genesis transaction: %w", err)
117+
}
118+
genTxs = append(genTxs, genTx)
119+
return nil
120+
},
121+
)
122+
if err != nil {
123+
return nil, nil, fmt.Errorf("failed to import genesis state: %w", err)
124+
}
125+
126+
// run block
127+
blockRequest.Txs = genTxs
128+
129+
blockResponse, blockZeroState, err := a.stf.DeliverBlock(ctx, blockRequest, genesisState)
130+
if err != nil {
131+
return blockResponse, nil, fmt.Errorf("failed to deliver block %d: %w", blockRequest.Height, err)
132+
}
133+
134+
// after executing block 0, we extract the changes and apply them to the genesis state.
135+
stateChanges, err := blockZeroState.GetStateChanges()
136+
if err != nil {
137+
return nil, nil, fmt.Errorf("failed to get block zero state changes: %w", err)
138+
}
139+
140+
err = genesisState.ApplyStateChanges(stateChanges)
141+
if err != nil {
142+
return nil, nil, fmt.Errorf("failed to apply block zero state changes to genesis state: %w", err)
143+
}
144+
145+
// override validator updates with the ones from the init genesis state
146+
// this triggers only when x/staking or another module that returns validator updates in InitGenesis
147+
// otherwise, genutil validator updates takes precedence (returned from executing the genesis txs (as it implements appmodule.GenesisDecoder) in the end block)
148+
if len(valUpdates) > 0 && len(blockResponse.ValidatorUpdates) > 0 {
149+
return nil, nil, errors.New("validator updates returned from InitGenesis and genesis transactions, only one can be used")
150+
}
151+
152+
if len(valUpdates) > 0 {
153+
blockResponse.ValidatorUpdates = valUpdates
154+
}
155+
156+
return blockResponse, genesisState, err
157+
}
158+
159+
// ExportGenesis exports the genesis state of the application.
160+
func (a appManager[T]) ExportGenesis(ctx context.Context, version uint64) ([]byte, error) {
161+
if a.exportGenesis == nil {
162+
return nil, errors.New("export genesis function not set")
163+
}
164+
165+
return a.exportGenesis(ctx, version)
166+
}
167+
168+
// DeliverBlock executes a block of transactions.
169+
func (a appManager[T]) DeliverBlock(
170+
ctx context.Context,
171+
block *server.BlockRequest[T],
172+
) (*server.BlockResponse, corestore.WriterMap, error) {
173+
latestVersion, currentState, err := a.db.StateLatest()
174+
if err != nil {
175+
return nil, nil, fmt.Errorf("unable to create new state for height %d: %w", block.Height, err)
176+
}
177+
178+
if latestVersion+1 != block.Height {
179+
return nil, nil, fmt.Errorf("invalid DeliverBlock height wanted %d, got %d", latestVersion+1, block.Height)
180+
}
181+
182+
blockResponse, newState, err := a.stf.DeliverBlock(ctx, block, currentState)
183+
if err != nil {
184+
return nil, nil, fmt.Errorf("block delivery failed: %w", err)
185+
}
186+
187+
return blockResponse, newState, nil
188+
}
189+
190+
// ValidateTx will validate the tx against the latest storage state. This means that
191+
// only the stateful validation will be run, not the execution portion of the tx.
192+
// If full execution is needed, Simulate must be used.
193+
func (a appManager[T]) ValidateTx(ctx context.Context, tx T) (server.TxResult, error) {
194+
_, latestState, err := a.db.StateLatest()
195+
if err != nil {
196+
return server.TxResult{}, err
197+
}
198+
res := a.stf.ValidateTx(ctx, latestState, a.config.ValidateTxGasLimit, tx)
199+
return res, res.Error
200+
}
201+
202+
// Simulate runs validation and execution flow of a Tx.
203+
func (a appManager[T]) Simulate(ctx context.Context, tx T) (server.TxResult, corestore.WriterMap, error) {
204+
_, state, err := a.db.StateLatest()
205+
if err != nil {
206+
return server.TxResult{}, nil, err
207+
}
208+
result, cs := a.stf.Simulate(ctx, state, a.config.SimulationGasLimit, tx) // TODO: check if this is done in the antehandler
209+
return result, cs, nil
210+
}
211+
212+
// SimulateWithState runs validation and execution flow of a Tx,
213+
// using the provided state instead of loading the latest state from the underlying database.
214+
func (a appManager[T]) SimulateWithState(ctx context.Context, state corestore.ReaderMap, tx T) (server.TxResult, corestore.WriterMap, error) {
215+
result, cs := a.stf.Simulate(ctx, state, a.config.SimulationGasLimit, tx) // TODO: check if this is done in the antehandler
216+
return result, cs, nil
217+
}
218+
219+
// Query queries the application at the provided version.
220+
// CONTRACT: Version must always be provided, if 0, get latest
221+
func (a appManager[T]) Query(ctx context.Context, version uint64, request transaction.Msg) (transaction.Msg, error) {
222+
var (
223+
queryState corestore.ReaderMap
224+
err error
225+
)
226+
// if version is provided attempt to do a height query.
227+
if version != 0 {
228+
queryState, err = a.db.StateAt(version)
229+
} else { // otherwise rely on latest available state.
230+
_, queryState, err = a.db.StateLatest()
231+
}
232+
if err != nil {
233+
return nil, fmt.Errorf("invalid height: %w", err)
234+
}
235+
return a.stf.Query(ctx, queryState, a.config.QueryGasLimit, request)
236+
}
237+
238+
// QueryWithState executes a query with the provided state. This allows to process a query
239+
// independently of the db state. For example, it can be used to process a query with temporary
240+
// and uncommitted state
241+
func (a appManager[T]) QueryWithState(ctx context.Context, state corestore.ReaderMap, request transaction.Msg) (transaction.Msg, error) {
242+
return a.stf.Query(ctx, state, a.config.QueryGasLimit, request)
243+
}

systemtests/CHANGELOG.md

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<!--
2+
Guiding Principles:
3+
4+
Changelogs are for humans, not machines.
5+
There should be an entry for every single version.
6+
The same types of changes should be grouped.
7+
Versions and sections should be linkable.
8+
The latest version comes first.
9+
The release date of each version is displayed.
10+
Mention whether you follow Semantic Versioning.
11+
12+
Usage:
13+
14+
Changelog entries are generated by git cliff ref: https://github.com/orhun/git-cliff
15+
16+
Each commit should be conventional, the following message groups are supported.
17+
18+
* feat: A new feature
19+
* fix: A bug fix
20+
* docs: Documentation only changes
21+
* style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
22+
* refactor: A code change that neither fixes a bug nor adds a feature
23+
* perf: A code change that improves performance
24+
* test: Adding missing tests or correcting existing tests
25+
* build: Changes that affect the build system or external dependencies (example scopes: go, npm)
26+
* ci: Changes to our CI configuration files and scripts (example scopes: GH Actions)
27+
* chore: Other changes that don't modify src or test files
28+
* revert: Reverts a previous commit
29+
30+
When a change is made that affects the API or state machine, the commit message prefix should be suffixed with `!`.
31+
32+
Ref: https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json
33+
-->
34+
35+
# Changelog
36+
37+
## [Unreleased]
38+
39+
## [v1.0.0-rc.3] - 2024-12-05
40+
41+
* [#22774](https://github.com/cosmos/cosmos-sdk/pull/22774) Add greater than or equal support in Rest test suite
42+
43+
## [v1.0.0-rc.2] - 2024-11-26
44+
45+
* [#22577](https://github.com/cosmos/cosmos-sdk/pull/22577) Support invalid RPC response for CometBFT v1
46+
47+
## [v1.0.0-rc.1] - 2024-11-26
48+
49+
* [#22578](https://github.com/cosmos/cosmos-sdk/pull/22578) Extract system test framework

0 commit comments

Comments
 (0)