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 all 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 @@ -12,6 +12,9 @@ import (

v502 "github.com/neutron-org/neutron/v5/app/upgrades/v5.0.2"
dynamicfeestypes "github.com/neutron-org/neutron/v5/x/dynamicfees/types"
stateverifier "github.com/neutron-org/neutron/v5/x/state-verifier"
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 @@ -394,6 +397,8 @@ type App struct {
InterchainTxsKeeper interchaintxskeeper.Keeper
ContractManagerKeeper contractmanagermodulekeeper.Keeper

StateVerifierKeeper *svkeeper.Keeper

ConsensusParamsKeeper consensusparamkeeper.Keeper

WasmKeeper wasmkeeper.Keeper
Expand Down Expand Up @@ -489,7 +494,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 @@ -653,6 +658,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 @@ -931,6 +938,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)),
stateverifier.NewAppModule(appCodec, app.StateVerifierKeeper),
)

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

app.mm.SetOrderEndBlockers(
Expand Down Expand Up @@ -1013,6 +1022,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 @@ -1056,6 +1066,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;
}
11 changes: 11 additions & 0 deletions utils/storageverification/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package storageverification

import "cosmossdk.io/errors"

const StateVerificationCodespace = "state_verification"

var (
ErrInvalidType = errors.Register(StateVerificationCodespace, 1, "invalid type")
ErrInvalidStorageValue = errors.Register(StateVerificationCodespace, 2, "failed to check storage value")
ErrInvalidProof = errors.Register(StateVerificationCodespace, 3, "merkle proof is invalid")
)
50 changes: 50 additions & 0 deletions utils/storageverification/verify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package storageverification

import (
"cosmossdk.io/errors"
ibccommitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types"
"github.com/cosmos/ibc-go/v8/modules/core/exported"
ics23 "github.com/cosmos/ics23/go"

"github.com/neutron-org/neutron/v5/x/interchainqueries/types"
)

type VerifyCallback func(index int) error

// VerifyStorageValues verifies stValues slice against proof using proofSpecs
// A caller can provide verifyCallback method that will be called for each storage value from the slice with an index of the value in the slice
// to do any additional user-defined checks of storage values
func VerifyStorageValues(stValues []*types.StorageValue, root exported.Root, proofSpecs []*ics23.ProofSpec, verifyCallback VerifyCallback) error {
for index, value := range stValues {
proof, err := ibccommitmenttypes.ConvertProofs(value.Proof)
if err != nil {
return errors.Wrapf(ErrInvalidType, "failed to convert crypto.ProofOps to MerkleProof: %v", err)
}

if verifyCallback != nil {
if err := verifyCallback(index); err != nil {
return errors.Wrapf(ErrInvalidStorageValue, err.Error())
}
}

path := ibccommitmenttypes.NewMerklePath(value.StoragePrefix, string(value.Key))
// identify what kind proofs (non-existence proof always has *ics23.CommitmentProof_Nonexist as the first item) we got
// and call corresponding method to verify it
switch proof.GetProofs()[0].GetProof().(type) {
// we can get non-existence proof if someone queried some key which is not exists in the storage on remote chain
case *ics23.CommitmentProof_Nonexist:
if err := proof.VerifyNonMembership(proofSpecs, root, path); err != nil {
return errors.Wrapf(ErrInvalidProof, "failed to verify proof: %v", err)
}
value.Value = nil
case *ics23.CommitmentProof_Exist:
if err := proof.VerifyMembership(proofSpecs, root, path, value.Value); err != nil {
return errors.Wrapf(ErrInvalidProof, "failed to verify proof: %v", err)
}
default:
return errors.Wrapf(ErrInvalidProof, "unknown proof type %T", proof.GetProofs()[0].GetProof())
}
}

return nil
}
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
4 changes: 2 additions & 2 deletions x/interchainqueries/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -989,7 +989,7 @@ func (suite *KeeperTestSuite) TestSubmitInterchainQueryResult() {
},
}
},
iqtypes.ErrInvalidType,
iqtypes.ErrInvalidSubmittedResult,
},
{
"non-registered key in KV result",
Expand Down Expand Up @@ -1239,7 +1239,7 @@ func (suite *KeeperTestSuite) TestSubmitInterchainQueryResult() {
},
}
},
iqtypes.ErrInvalidProof,
iqtypes.ErrInvalidSubmittedResult,
},
{
"query result height is too old",
Expand Down
Loading
Loading