Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: state verifier #766

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import (
"time"

dynamicfeestypes "github.com/neutron-org/neutron/v5/x/dynamicfees/types"
state_verifier "github.com/neutron-org/neutron/v5/x/state-verifier"
NeverHappened marked this conversation as resolved.
Show resolved Hide resolved
svkeeper "github.com/neutron-org/neutron/v5/x/state-verifier/keeper"
stateverifiertypes "github.com/neutron-org/neutron/v5/x/state-verifier/types"

"github.com/skip-mev/feemarket/x/feemarket"
feemarketkeeper "github.com/skip-mev/feemarket/x/feemarket/keeper"
Expand Down Expand Up @@ -392,6 +395,8 @@ type App struct {
InterchainTxsKeeper interchaintxskeeper.Keeper
ContractManagerKeeper contractmanagermodulekeeper.Keeper

StateVerifierKeeper *svkeeper.Keeper

ConsensusParamsKeeper consensusparamkeeper.Keeper

WasmKeeper wasmkeeper.Keeper
Expand Down Expand Up @@ -487,7 +492,7 @@ func New(
interchainqueriesmoduletypes.StoreKey, contractmanagermoduletypes.StoreKey, interchaintxstypes.StoreKey, wasmtypes.StoreKey, feetypes.StoreKey,
feeburnertypes.StoreKey, adminmoduletypes.StoreKey, ccvconsumertypes.StoreKey, tokenfactorytypes.StoreKey, pfmtypes.StoreKey,
crontypes.StoreKey, ibcratelimittypes.ModuleName, ibchookstypes.StoreKey, consensusparamtypes.StoreKey, crisistypes.StoreKey, dextypes.StoreKey, auctiontypes.StoreKey,
oracletypes.StoreKey, marketmaptypes.StoreKey, feemarkettypes.StoreKey, dynamicfeestypes.StoreKey, globalfeetypes.StoreKey,
oracletypes.StoreKey, marketmaptypes.StoreKey, feemarkettypes.StoreKey, dynamicfeestypes.StoreKey, globalfeetypes.StoreKey, stateverifiertypes.StoreKey,
)
tkeys := storetypes.NewTransientStoreKeys(paramstypes.TStoreKey, dextypes.TStoreKey)
memKeys := storetypes.NewMemoryStoreKeys(capabilitytypes.MemStoreKey, feetypes.MemStoreKey)
Expand Down Expand Up @@ -651,6 +656,8 @@ func New(

app.GlobalFeeKeeper = globalfeekeeper.NewKeeper(appCodec, keys[globalfeetypes.StoreKey], authtypes.NewModuleAddress(adminmoduletypes.ModuleName).String())

app.StateVerifierKeeper = svkeeper.NewKeeper(appCodec, keys[stateverifiertypes.StoreKey], runtime.ProvideCometInfoService(), runtime.ProvideHeaderInfoService(nil), authtypes.NewModuleAddress(adminmoduletypes.ModuleName).String())

// Create evidence Keeper for to register the IBC light client misbehaviour evidence route
evidenceKeeper := evidencekeeper.NewKeeper(
appCodec, runtime.NewKVStoreService(keys[evidencetypes.StoreKey]), &app.ConsumerKeeper, app.SlashingKeeper,
Expand Down Expand Up @@ -929,6 +936,7 @@ func New(
consensus.NewAppModule(appCodec, app.ConsensusParamsKeeper),
// always be last to make sure that it checks for all invariants and not only part of them
crisis.NewAppModule(&app.CrisisKeeper, skipGenesisInvariants, app.GetSubspace(crisistypes.ModuleName)),
state_verifier.NewAppModule(appCodec, app.StateVerifierKeeper),
)

app.mm.SetOrderPreBlockers(
Expand Down Expand Up @@ -974,6 +982,7 @@ func New(
feemarkettypes.ModuleName,
dextypes.ModuleName,
consensusparamtypes.ModuleName,
stateverifiertypes.ModuleName,
)

app.mm.SetOrderEndBlockers(
Expand Down Expand Up @@ -1011,6 +1020,7 @@ func New(
feemarkettypes.ModuleName,
dextypes.ModuleName,
consensusparamtypes.ModuleName,
stateverifiertypes.ModuleName,
)

// NOTE: The genutils module must occur after staking so that pools are
Expand Down Expand Up @@ -1054,6 +1064,7 @@ func New(
dextypes.ModuleName,
dynamicfeestypes.ModuleName,
consensusparamtypes.ModuleName,
stateverifiertypes.ModuleName,
)

app.mm.RegisterInvariants(&app.CrisisKeeper)
Expand Down
46 changes: 46 additions & 0 deletions docs/static/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19635,6 +19635,11 @@ definitions:
type: array
type: object
type: object
neutron.state_verifier.v1.QueryVerifyStateValuesResponse:
properties:
valid:
type: boolean
type: object
osmosis.tokenfactory.Params:
description: Params defines the parameters for the tokenfactory module.
properties:
Expand Down Expand Up @@ -45443,6 +45448,47 @@ paths:
type: object
tags:
- Query
/neutron/state-verifier/verify_state_values:
get:
operationId: VerifyStateValues
parameters:
- format: uint64
in: query
name: height
required: false
type: string
responses:
'200':
description: A successful response.
schema:
properties:
valid:
type: boolean
type: object
default:
description: An unexpected error response.
schema:
properties:
code:
format: int32
type: integer
details:
items:
properties:
type_url:
type: string
value:
format: byte
type: string
type: object
type: array
error:
type: string
message:
type: string
type: object
tags:
- Query
/osmosis/tokenfactory/v1beta1/denoms/factory/{creator}/{subdenom}/authority_metadata:
get:
operationId: DenomAuthorityMetadata
Expand Down
16 changes: 16 additions & 0 deletions proto/neutron/state_verifier/v1/genesis.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
syntax = "proto3";
package neutron.state_verifier.v1;

import "ibc/lightclients/tendermint/v1/tendermint.proto";

option go_package = "github.com/neutron-org/neutron/v5/x/state-verifier/types";

// ConsensusState describes a "light" consensus state of Neutron at a particular height
message ConsensusState {
int64 height = 1;
ibc.lightclients.tendermint.v1.ConsensusState cs = 2;
}

message GenesisState {
repeated ConsensusState states = 1;
}
23 changes: 23 additions & 0 deletions proto/neutron/state_verifier/v1/query.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
syntax = "proto3";
package neutron.state_verifier.v1;

import "google/api/annotations.proto";
import "neutron/interchainqueries/tx.proto";

option go_package = "github.com/neutron-org/neutron/v5/x/state-verifier/types";

service Query {
rpc VerifyStateValues(QueryVerifyStateValuesRequest) returns (QueryVerifyStateValuesResponse) {
option (google.api.http).get = "/neutron/state-verifier/verify_state_values";
}
}

// QueryVerifyStateValuesRequest describes a structure to verify storage values from Neutron state from a particular height in the past
message QueryVerifyStateValuesRequest {
uint64 height = 1;
repeated neutron.interchainqueries.StorageValue storage_values = 2;
}

message QueryVerifyStateValuesResponse {
bool valid = 1;
NeverHappened marked this conversation as resolved.
Show resolved Hide resolved
}
101 changes: 101 additions & 0 deletions third_party/proto/ibc/lightclients/tendermint/v1/tendermint.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
syntax = "proto3";

package ibc.lightclients.tendermint.v1;

option go_package = "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint;tendermint";
NeverHappened marked this conversation as resolved.
Show resolved Hide resolved

import "tendermint/types/validator.proto";
import "tendermint/types/types.proto";
import "cosmos/ics23/v1/proofs.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/timestamp.proto";
import "ibc/core/client/v1/client.proto";
import "ibc/core/commitment/v1/commitment.proto";
import "gogoproto/gogo.proto";

// ClientState from Tendermint tracks the current validator set, latest height,
// and a possible frozen height.
message ClientState {
option (gogoproto.goproto_getters) = false;

string chain_id = 1;
Fraction trust_level = 2 [(gogoproto.nullable) = false];
// duration of the period since the LastestTimestamp during which the
// submitted headers are valid for upgrade
google.protobuf.Duration trusting_period = 3 [(gogoproto.nullable) = false, (gogoproto.stdduration) = true];
// duration of the staking unbonding period
google.protobuf.Duration unbonding_period = 4 [(gogoproto.nullable) = false, (gogoproto.stdduration) = true];
// defines how much new (untrusted) header's Time can drift into the future.
google.protobuf.Duration max_clock_drift = 5 [(gogoproto.nullable) = false, (gogoproto.stdduration) = true];
// Block height when the client was frozen due to a misbehaviour
ibc.core.client.v1.Height frozen_height = 6 [(gogoproto.nullable) = false];
// Latest height the client was updated to
ibc.core.client.v1.Height latest_height = 7 [(gogoproto.nullable) = false];

// Proof specifications used in verifying counterparty state
repeated cosmos.ics23.v1.ProofSpec proof_specs = 8;

// Path at which next upgraded client will be committed.
// Each element corresponds to the key for a single CommitmentProof in the
// chained proof. NOTE: ClientState must stored under
// `{upgradePath}/{upgradeHeight}/clientState` ConsensusState must be stored
// under `{upgradepath}/{upgradeHeight}/consensusState` For SDK chains using
// the default upgrade module, upgrade_path should be []string{"upgrade",
// "upgradedIBCState"}`
repeated string upgrade_path = 9;

// allow_update_after_expiry is deprecated
bool allow_update_after_expiry = 10 [deprecated = true];
// allow_update_after_misbehaviour is deprecated
bool allow_update_after_misbehaviour = 11 [deprecated = true];
}

// ConsensusState defines the consensus state from Tendermint.
message ConsensusState {
option (gogoproto.goproto_getters) = false;

// timestamp that corresponds to the block height in which the ConsensusState
// was stored.
google.protobuf.Timestamp timestamp = 1 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
// commitment root (i.e app hash)
ibc.core.commitment.v1.MerkleRoot root = 2 [(gogoproto.nullable) = false];
bytes next_validators_hash = 3 [(gogoproto.casttype) = "github.com/cometbft/cometbft/libs/bytes.HexBytes"];
}

// Misbehaviour is a wrapper over two conflicting Headers
// that implements Misbehaviour interface expected by ICS-02
message Misbehaviour {
option (gogoproto.goproto_getters) = false;

// ClientID is deprecated
string client_id = 1 [deprecated = true];
Header header_1 = 2 [(gogoproto.customname) = "Header1"];
Header header_2 = 3 [(gogoproto.customname) = "Header2"];
}

// Header defines the Tendermint client consensus Header.
// It encapsulates all the information necessary to update from a trusted
// Tendermint ConsensusState. The inclusion of TrustedHeight and
// TrustedValidators allows this update to process correctly, so long as the
// ConsensusState for the TrustedHeight exists, this removes race conditions
// among relayers The SignedHeader and ValidatorSet are the new untrusted update
// fields for the client. The TrustedHeight is the height of a stored
// ConsensusState on the client that will be used to verify the new untrusted
// header. The Trusted ConsensusState must be within the unbonding period of
// current time in order to correctly verify, and the TrustedValidators must
// hash to TrustedConsensusState.NextValidatorsHash since that is the last
// trusted validator set at the TrustedHeight.
message Header {
.tendermint.types.SignedHeader signed_header = 1 [(gogoproto.embed) = true];

.tendermint.types.ValidatorSet validator_set = 2;
ibc.core.client.v1.Height trusted_height = 3 [(gogoproto.nullable) = false];
.tendermint.types.ValidatorSet trusted_validators = 4;
}

// Fraction defines the protobuf message type for tmmath.Fraction that only
// supports positive values.
message Fraction {
uint64 numerator = 1;
uint64 denominator = 2;
}
7 changes: 5 additions & 2 deletions wasmbinding/stargate_allowlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ import (
marketmaptypes "github.com/skip-mev/slinky/x/marketmap/types"
oracletypes "github.com/skip-mev/slinky/x/oracle/types"

dynamicfeestypes "github.com/neutron-org/neutron/v5/x/dynamicfees/types"

crontypes "github.com/neutron-org/neutron/v5/x/cron/types"
dextypes "github.com/neutron-org/neutron/v5/x/dex/types"
dynamicfeestypes "github.com/neutron-org/neutron/v5/x/dynamicfees/types"
feeburnertypes "github.com/neutron-org/neutron/v5/x/feeburner/types"
interchainqueriestypes "github.com/neutron-org/neutron/v5/x/interchainqueries/types"
interchaintxstypes "github.com/neutron-org/neutron/v5/x/interchaintxs/types"
stateverifiertypes "github.com/neutron-org/neutron/v5/x/state-verifier/types"
tokenfactorytypes "github.com/neutron-org/neutron/v5/x/tokenfactory/types"
)

Expand Down Expand Up @@ -118,5 +118,8 @@ func AcceptedStargateQueries() wasmkeeper.AcceptedQueries {

// dynamicfees
"neutron.dynamicfees.v1.Query/Params": &dynamicfeestypes.QueryParamsResponse{},

// state verifier
"/neutron.state_verifier.v1.Query/VerifyStateValues": &stateverifiertypes.QueryVerifyStateValuesResponse{},
}
}
6 changes: 6 additions & 0 deletions x/dex/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"time"

abci "github.com/cometbft/cometbft/abci/types"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/stretchr/testify/require"

sdkmath "cosmossdk.io/math"
Expand Down Expand Up @@ -1520,6 +1521,11 @@ func (s *DexTestSuite) nextBlockWithTime(blockTime time.Time) {

func (s *DexTestSuite) beginBlockWithTime(blockTime time.Time) {
s.Ctx = s.Ctx.WithBlockTime(blockTime)
// fill in empty CometBFT info just to avoid nil pointer panics (we don't care about validity of the info in these tests)
s.Ctx = s.Ctx.WithCometInfo(baseapp.NewBlockInfo(nil, nil, nil, abci.CommitInfo{
Round: 0,
Votes: nil,
}))
_, err := s.App.BeginBlocker(s.Ctx)
s.NoError(err)
}
Expand Down
33 changes: 33 additions & 0 deletions x/state-verifier/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Overview

The State Verifier module allows to verify that some `storage values` were indeed present on a particular `block height` in the chain.

The idea is the similar how Neutron's KV ICQ works: each `StorageValue` in Cosmos SDK is stored in [KV-IAVL storage](https://github.com/cosmos/iavl).
And to be more precise it's stored in a structure called [`MerkleTree`](https://github.com/cosmos/cosmos-sdk/blob/ae77f0080a724b159233bd9b289b2e91c0de21b5/docs/interfaces/lite/specification.md).
The tree allows to compose `Proof` for `key` and `value` pairs that can prove two things using `RootHash` of the tree:
* `key` and `value` are present in the tree;
* `key` is not present in the tree.

Cosmos blockchain's storage is stored as a different tree for each block.
That means we can prove that a particular `KV` pair is really present (or not present) in the storage at a particular block height.

See [Neutron's ICQ relayer implementation](https://github.com/neutron-org/neutron-query-relayer/blob/4542045ab24d2735890e70d4dc525677d5f30c8a/internal/proof/proof_impl/get_storage_values.go#L11) if you want to know how to query KV-proofs

# Implementation

### BeginBlocker
In each block the module's `BeginBlocker` is being called, and it saves `ConsensusState` of the current block height in the storage to use it for verification of storage values later:

```go
consensusState := tendermint.ConsensusState{
Timestamp: ctx.BlockTime(), // current block time
Root: ibccommitmenttypes.NewMerkleRoot(headerInfo.AppHash), // .AppHash for the previous block
NextValidatorsHash: cometInfo.GetValidatorsHash(), // hash of the validator set for the next block
}
```

For verification only `.Root` (`header.AppHash`) is used, but it's good to save all the values just in case and do not leave them empty.

### VerifyStateValues query
The main query of the module that accepts slice of `[]StorageValue` structures and `blockHeight` on which those `StorageValues` are present.
The module verifies the values and returns an error if values cannot be verified `{valid: true}` structure if values are valid.
32 changes: 32 additions & 0 deletions x/state-verifier/genesis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package stateverifier

import (
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/neutron-org/neutron/v5/x/state-verifier/keeper"
"github.com/neutron-org/neutron/v5/x/state-verifier/types"
)

// InitGenesis initializes the module's state from a provided genesis state.
func InitGenesis(ctx sdk.Context, k *keeper.Keeper, genState types.GenesisState) {
// this line is used by starport scaffolding # genesis/module/init
for _, state := range genState.States {
if err := k.WriteConsensusState(ctx, state.Height, *state.Cs); err != nil {
panic(err)
}
}
}

// ExportGenesis returns the module's exported genesis
func ExportGenesis(ctx sdk.Context, k *keeper.Keeper) *types.GenesisState {
genesis := types.DefaultGenesis()

allCs, err := k.GetAllConsensusStates(ctx)
if err != nil {
panic(err)
}

genesis.States = append(genesis.States, allCs...)

return genesis
}
Loading
Loading