Skip to content

Commit

Permalink
test(systemtests): fix gRPC tests for v1 & v2 (#22774)
Browse files Browse the repository at this point in the history
(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
julienrbrt authored and mergify[bot] committed Dec 6, 2024
1 parent b37887e commit b070d29
Show file tree
Hide file tree
Showing 8 changed files with 595 additions and 146 deletions.
243 changes: 243 additions & 0 deletions server/v2/appmanager/appmanager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
package appmanager

import (
"bytes"

Check failure on line 4 in server/v2/appmanager/appmanager.go

View workflow job for this annotation

GitHub Actions / dependency-review

"bytes" imported and not used
"context"
"encoding/json"

Check failure on line 6 in server/v2/appmanager/appmanager.go

View workflow job for this annotation

GitHub Actions / dependency-review

"encoding/json" imported and not used
"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

Check failure on line 73 in server/v2/appmanager/appmanager.go

View workflow job for this annotation

GitHub Actions / dependency-review

undefined: 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

Check failure on line 76 in server/v2/appmanager/appmanager.go

View workflow job for this annotation

GitHub Actions / dependency-review

undefined: 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

Check failure on line 79 in server/v2/appmanager/appmanager.go

View workflow job for this annotation

GitHub Actions / dependency-review

undefined: ExportGenesis
// The database for storing application data.
db Store
// The state transition function for processing transactions.
stf StateTransitionFunction[T]

Check failure on line 83 in server/v2/appmanager/appmanager.go

View workflow job for this annotation

GitHub Actions / dependency-review

undefined: StateTransitionFunction
}

func New[T transaction.Tx](
config Config,

Check failure on line 87 in server/v2/appmanager/appmanager.go

View workflow job for this annotation

GitHub Actions / dependency-review

undefined: Config
db Store,
stf StateTransitionFunction[T],

Check failure on line 89 in server/v2/appmanager/appmanager.go

View workflow job for this annotation

GitHub Actions / dependency-review

undefined: StateTransitionFunction
initGenesisImpl InitGenesis,

Check failure on line 90 in server/v2/appmanager/appmanager.go

View workflow job for this annotation

GitHub Actions / dependency-review

undefined: InitGenesis
exportGenesisImpl ExportGenesis,

Check failure on line 91 in server/v2/appmanager/appmanager.go

View workflow job for this annotation

GitHub Actions / dependency-review

undefined: 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)
}
49 changes: 49 additions & 0 deletions systemtests/CHANGELOG.md
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
Loading

0 comments on commit b070d29

Please sign in to comment.