-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
1 parent
b37887e
commit b070d29
Showing
8 changed files
with
595 additions
and
146 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,243 @@ | ||
package appmanager | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
|
||
"cosmossdk.io/core/server" | ||
corestore "cosmossdk.io/core/store" | ||
"cosmossdk.io/core/transaction" | ||
) | ||
|
||
// AppManager is a coordinator for all things related to an application | ||
// It is responsible for interacting with stf and store. | ||
// Runtime/v2 is an extension of this interface. | ||
type AppManager[T transaction.Tx] interface { | ||
// InitGenesis initializes the genesis state of the application. | ||
InitGenesis( | ||
ctx context.Context, | ||
blockRequest *server.BlockRequest[T], | ||
initGenesisJSON []byte, | ||
txDecoder transaction.Codec[T], | ||
) (*server.BlockResponse, corestore.WriterMap, error) | ||
|
||
// ExportGenesis exports the genesis state of the application. | ||
ExportGenesis(ctx context.Context, version uint64) ([]byte, error) | ||
|
||
// DeliverBlock executes a block of transactions. | ||
DeliverBlock( | ||
ctx context.Context, | ||
block *server.BlockRequest[T], | ||
) (*server.BlockResponse, corestore.WriterMap, error) | ||
|
||
// ValidateTx will validate the tx against the latest storage state. This means that | ||
// only the stateful validation will be run, not the execution portion of the tx. | ||
// If full execution is needed, Simulate must be used. | ||
ValidateTx(ctx context.Context, tx T) (server.TxResult, error) | ||
|
||
// Simulate runs validation and execution flow of a Tx. | ||
Simulate(ctx context.Context, tx T) (server.TxResult, corestore.WriterMap, error) | ||
|
||
// SimulateWithState runs validation and execution flow of a Tx, | ||
// using the provided state instead of loading the latest state from the underlying database. | ||
SimulateWithState(ctx context.Context, state corestore.ReaderMap, tx T) (server.TxResult, corestore.WriterMap, error) | ||
|
||
// Query queries the application at the provided version. | ||
// CONTRACT: Version must always be provided, if 0, get latest | ||
Query(ctx context.Context, version uint64, request transaction.Msg) (transaction.Msg, error) | ||
|
||
// QueryWithState executes a query with the provided state. This allows to process a query | ||
// independently of the db state. For example, it can be used to process a query with temporary | ||
// and uncommitted state | ||
QueryWithState(ctx context.Context, state corestore.ReaderMap, request transaction.Msg) (transaction.Msg, error) | ||
} | ||
|
||
// Store defines the underlying storage behavior needed by AppManager. | ||
type Store interface { | ||
// StateLatest returns a readonly view over the latest | ||
// committed state of the store. Alongside the version | ||
// associated with it. | ||
StateLatest() (uint64, corestore.ReaderMap, error) | ||
|
||
// StateAt returns a readonly view over the provided | ||
// state. Must error when the version does not exist. | ||
StateAt(version uint64) (corestore.ReaderMap, error) | ||
} | ||
|
||
// appManager is a coordinator for all things related to an application | ||
type appManager[T transaction.Tx] struct { | ||
// Gas limits for validating, querying, and simulating transactions. | ||
config Config | ||
// InitGenesis is a function that initializes the application state from a genesis file. | ||
// It takes a context, a source reader for the genesis file, and a transaction handler function. | ||
initGenesis InitGenesis | ||
// ExportGenesis is a function that exports the application state to a genesis file. | ||
// It takes a context and a version number for the genesis file. | ||
exportGenesis ExportGenesis | ||
// The database for storing application data. | ||
db Store | ||
// The state transition function for processing transactions. | ||
stf StateTransitionFunction[T] | ||
} | ||
|
||
func New[T transaction.Tx]( | ||
config Config, | ||
db Store, | ||
stf StateTransitionFunction[T], | ||
initGenesisImpl InitGenesis, | ||
exportGenesisImpl ExportGenesis, | ||
) AppManager[T] { | ||
return &appManager[T]{ | ||
config: config, | ||
db: db, | ||
stf: stf, | ||
initGenesis: initGenesisImpl, | ||
exportGenesis: exportGenesisImpl, | ||
} | ||
} | ||
|
||
// InitGenesis initializes the genesis state of the application. | ||
func (a appManager[T]) InitGenesis( | ||
ctx context.Context, | ||
blockRequest *server.BlockRequest[T], | ||
initGenesisJSON []byte, | ||
txDecoder transaction.Codec[T], | ||
) (*server.BlockResponse, corestore.WriterMap, error) { | ||
var genTxs []T | ||
genesisState, valUpdates, err := a.initGenesis( | ||
ctx, | ||
bytes.NewBuffer(initGenesisJSON), | ||
func(jsonTx json.RawMessage) error { | ||
genTx, err := txDecoder.DecodeJSON(jsonTx) | ||
if err != nil { | ||
return fmt.Errorf("failed to decode genesis transaction: %w", err) | ||
} | ||
genTxs = append(genTxs, genTx) | ||
return nil | ||
}, | ||
) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("failed to import genesis state: %w", err) | ||
} | ||
|
||
// run block | ||
blockRequest.Txs = genTxs | ||
|
||
blockResponse, blockZeroState, err := a.stf.DeliverBlock(ctx, blockRequest, genesisState) | ||
if err != nil { | ||
return blockResponse, nil, fmt.Errorf("failed to deliver block %d: %w", blockRequest.Height, err) | ||
} | ||
|
||
// after executing block 0, we extract the changes and apply them to the genesis state. | ||
stateChanges, err := blockZeroState.GetStateChanges() | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("failed to get block zero state changes: %w", err) | ||
} | ||
|
||
err = genesisState.ApplyStateChanges(stateChanges) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("failed to apply block zero state changes to genesis state: %w", err) | ||
} | ||
|
||
// override validator updates with the ones from the init genesis state | ||
// this triggers only when x/staking or another module that returns validator updates in InitGenesis | ||
// otherwise, genutil validator updates takes precedence (returned from executing the genesis txs (as it implements appmodule.GenesisDecoder) in the end block) | ||
if len(valUpdates) > 0 && len(blockResponse.ValidatorUpdates) > 0 { | ||
return nil, nil, errors.New("validator updates returned from InitGenesis and genesis transactions, only one can be used") | ||
} | ||
|
||
if len(valUpdates) > 0 { | ||
blockResponse.ValidatorUpdates = valUpdates | ||
} | ||
|
||
return blockResponse, genesisState, err | ||
} | ||
|
||
// ExportGenesis exports the genesis state of the application. | ||
func (a appManager[T]) ExportGenesis(ctx context.Context, version uint64) ([]byte, error) { | ||
if a.exportGenesis == nil { | ||
return nil, errors.New("export genesis function not set") | ||
} | ||
|
||
return a.exportGenesis(ctx, version) | ||
} | ||
|
||
// DeliverBlock executes a block of transactions. | ||
func (a appManager[T]) DeliverBlock( | ||
ctx context.Context, | ||
block *server.BlockRequest[T], | ||
) (*server.BlockResponse, corestore.WriterMap, error) { | ||
latestVersion, currentState, err := a.db.StateLatest() | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("unable to create new state for height %d: %w", block.Height, err) | ||
} | ||
|
||
if latestVersion+1 != block.Height { | ||
return nil, nil, fmt.Errorf("invalid DeliverBlock height wanted %d, got %d", latestVersion+1, block.Height) | ||
} | ||
|
||
blockResponse, newState, err := a.stf.DeliverBlock(ctx, block, currentState) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("block delivery failed: %w", err) | ||
} | ||
|
||
return blockResponse, newState, nil | ||
} | ||
|
||
// ValidateTx will validate the tx against the latest storage state. This means that | ||
// only the stateful validation will be run, not the execution portion of the tx. | ||
// If full execution is needed, Simulate must be used. | ||
func (a appManager[T]) ValidateTx(ctx context.Context, tx T) (server.TxResult, error) { | ||
_, latestState, err := a.db.StateLatest() | ||
if err != nil { | ||
return server.TxResult{}, err | ||
} | ||
res := a.stf.ValidateTx(ctx, latestState, a.config.ValidateTxGasLimit, tx) | ||
return res, res.Error | ||
} | ||
|
||
// Simulate runs validation and execution flow of a Tx. | ||
func (a appManager[T]) Simulate(ctx context.Context, tx T) (server.TxResult, corestore.WriterMap, error) { | ||
_, state, err := a.db.StateLatest() | ||
if err != nil { | ||
return server.TxResult{}, nil, err | ||
} | ||
result, cs := a.stf.Simulate(ctx, state, a.config.SimulationGasLimit, tx) // TODO: check if this is done in the antehandler | ||
return result, cs, nil | ||
} | ||
|
||
// SimulateWithState runs validation and execution flow of a Tx, | ||
// using the provided state instead of loading the latest state from the underlying database. | ||
func (a appManager[T]) SimulateWithState(ctx context.Context, state corestore.ReaderMap, tx T) (server.TxResult, corestore.WriterMap, error) { | ||
result, cs := a.stf.Simulate(ctx, state, a.config.SimulationGasLimit, tx) // TODO: check if this is done in the antehandler | ||
return result, cs, nil | ||
} | ||
|
||
// Query queries the application at the provided version. | ||
// CONTRACT: Version must always be provided, if 0, get latest | ||
func (a appManager[T]) Query(ctx context.Context, version uint64, request transaction.Msg) (transaction.Msg, error) { | ||
var ( | ||
queryState corestore.ReaderMap | ||
err error | ||
) | ||
// if version is provided attempt to do a height query. | ||
if version != 0 { | ||
queryState, err = a.db.StateAt(version) | ||
} else { // otherwise rely on latest available state. | ||
_, queryState, err = a.db.StateLatest() | ||
} | ||
if err != nil { | ||
return nil, fmt.Errorf("invalid height: %w", err) | ||
} | ||
return a.stf.Query(ctx, queryState, a.config.QueryGasLimit, request) | ||
} | ||
|
||
// QueryWithState executes a query with the provided state. This allows to process a query | ||
// independently of the db state. For example, it can be used to process a query with temporary | ||
// and uncommitted state | ||
func (a appManager[T]) QueryWithState(ctx context.Context, state corestore.ReaderMap, request transaction.Msg) (transaction.Msg, error) { | ||
return a.stf.Query(ctx, state, a.config.QueryGasLimit, request) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<!-- | ||
Guiding Principles: | ||
Changelogs are for humans, not machines. | ||
There should be an entry for every single version. | ||
The same types of changes should be grouped. | ||
Versions and sections should be linkable. | ||
The latest version comes first. | ||
The release date of each version is displayed. | ||
Mention whether you follow Semantic Versioning. | ||
Usage: | ||
Changelog entries are generated by git cliff ref: https://github.com/orhun/git-cliff | ||
Each commit should be conventional, the following message groups are supported. | ||
* feat: A new feature | ||
* fix: A bug fix | ||
* docs: Documentation only changes | ||
* style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) | ||
* refactor: A code change that neither fixes a bug nor adds a feature | ||
* perf: A code change that improves performance | ||
* test: Adding missing tests or correcting existing tests | ||
* build: Changes that affect the build system or external dependencies (example scopes: go, npm) | ||
* ci: Changes to our CI configuration files and scripts (example scopes: GH Actions) | ||
* chore: Other changes that don't modify src or test files | ||
* revert: Reverts a previous commit | ||
When a change is made that affects the API or state machine, the commit message prefix should be suffixed with `!`. | ||
Ref: https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json | ||
--> | ||
|
||
# Changelog | ||
|
||
## [Unreleased] | ||
|
||
## [v1.0.0-rc.3] - 2024-12-05 | ||
|
||
* [#22774](https://github.com/cosmos/cosmos-sdk/pull/22774) Add greater than or equal support in Rest test suite | ||
|
||
## [v1.0.0-rc.2] - 2024-11-26 | ||
|
||
* [#22577](https://github.com/cosmos/cosmos-sdk/pull/22577) Support invalid RPC response for CometBFT v1 | ||
|
||
## [v1.0.0-rc.1] - 2024-11-26 | ||
|
||
* [#22578](https://github.com/cosmos/cosmos-sdk/pull/22578) Extract system test framework |
Oops, something went wrong.