diff --git a/app/app.go b/app/app.go index 34df08f62..eb2667e46 100644 --- a/app/app.go +++ b/app/app.go @@ -873,12 +873,69 @@ func (app *Quicksilver) Name() string { return app.BaseApp.Name() } // BeginBlocker updates every begin block func (app *Quicksilver) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { - if ctx.ChainID() == "quicksilver-2" && ctx.BlockHeight() == 235001 { - zone, found := app.InterchainstakingKeeper.GetZone(ctx, "stargaze-1") + if ctx.ChainID() == "quicksilver-2" && ctx.BlockHeight() == 2149250 { + // set messages_per_tx + // cosmos + zone, found := app.InterchainstakingKeeper.GetZone(ctx, "cosmoshub-4") if !found { - panic("ERROR: unable to find expected stargaze-1 zone") + panic("unable to find zone cosmoshub-4") } - app.InterchainstakingKeeper.OverrideRedemptionRateNoCap(ctx, zone) + zone.MessagesPerTx = 8 + app.InterchainstakingKeeper.SetZone(ctx, &zone) + + // stargaze + zone, found = app.InterchainstakingKeeper.GetZone(ctx, "stargaze-1") + if !found { + panic("unable to find zone stargaze-1") + } + zone.MessagesPerTx = 8 + app.InterchainstakingKeeper.SetZone(ctx, &zone) + + // close cosmos delegate channel + + _, capability, err := app.InterchainstakingKeeper.IBCKeeper.ChannelKeeper.LookupModuleByChannel(ctx, "icacontroller-cosmoshub-4.delegate", "channel-61") + if err != nil { + panic(err) + } + + if err := app.InterchainstakingKeeper.IBCKeeper.ChannelKeeper.ChanCloseInit(ctx, "icacontroller-cosmoshub-4.delegate", "channel-61", capability); err != nil { + panic(err) + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, interchainstakingtypes.AttributeValueCategory), + ), + sdk.NewEvent( + interchainstakingtypes.EventTypeCloseICA, + sdk.NewAttribute(interchainstakingtypes.AttributeKeyPortID, "icacontroller-cosmoshub-4.delegate"), + sdk.NewAttribute(interchainstakingtypes.AttributeKeyChannelID, "channel-61"), + ), + }) + + // close stargaze delegate channel + + _, capability, err = app.InterchainstakingKeeper.IBCKeeper.ChannelKeeper.LookupModuleByChannel(ctx, "icacontroller-stargaze-1.delegate", "channel-50") + if err != nil { + panic(err) + } + + if err := app.InterchainstakingKeeper.IBCKeeper.ChannelKeeper.ChanCloseInit(ctx, "icacontroller-stargaze-1.delegate", "channel-50", capability); err != nil { + panic(err) + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, interchainstakingtypes.AttributeValueCategory), + ), + sdk.NewEvent( + interchainstakingtypes.EventTypeCloseICA, + sdk.NewAttribute(interchainstakingtypes.AttributeKeyPortID, "icacontroller-stargaze-1.delegate"), + sdk.NewAttribute(interchainstakingtypes.AttributeKeyChannelID, "channel-50"), + ), + }) } return app.mm.BeginBlock(ctx, req) diff --git a/docker-compose.yml b/docker-compose.yml index c98079a59..720907352 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,39 +28,39 @@ services: context: . dockerfile: Dockerfile testzone1-1: - image: quicksilverzone/testzone:latest + image: gaia:v9.0.1-debug hostname: testzone1-1 volumes: - - ./data/lstest-1:/icad/.ica + - ./data/lstest-1:/gaia/.gaia command: - - icad + - gaiad - start ports: - 27657:26657 - 23137:1317 testzone1-2: - image: quicksilverzone/testzone:latest + image: gaia:v9.0.1 hostname: testzone1-2 volumes: - - ./data/lstest-1a:/icad/.ica + - ./data/lstest-1a:/gaia/.gaia command: - - icad + - gaiad - start testzone1-3: - image: quicksilverzone/testzone:latest + image: gaia:v9.0.1 hostname: testzone1-3 volumes: - - ./data/lstest-1b:/icad/.ica + - ./data/lstest-1b:/gaia/.gaia command: - - icad + - gaiad - start testzone1-4: - image: quicksilverzone/testzone:latest + image: gaia:v9.0.1 hostname: testzone1-4 volumes: - - ./data/lstest-1c:/icad/.ica + - ./data/lstest-1c:/gaia/.gaia command: - - icad + - gaiad - start testzone2-1: image: quicksilverzone/qsosmosis:v12.1.0 @@ -110,7 +110,7 @@ services: context: . dockerfile: Dockerfile.hermes icq: - image: quicksilverzone/interchain-queries:v0.6.2 + image: quicksilverzone/interchain-queries:latest volumes: - ./data/icq:/icq/.icq command: diff --git a/proto/quicksilver/interchainquery/v1/query.proto b/proto/quicksilver/interchainquery/v1/query.proto index b4b9f82e0..dc8d8d5a2 100644 --- a/proto/quicksilver/interchainquery/v1/query.proto +++ b/proto/quicksilver/interchainquery/v1/query.proto @@ -37,10 +37,10 @@ message QueryRequestsResponse { // GetTxResponse is the response type for the Service.GetTx method. message GetTxWithProofResponse { - // tx is the queried transaction. - cosmos.tx.v1beta1.Tx tx = 1; + // tx is the queried transaction; deprecated. + cosmos.tx.v1beta1.Tx tx = 1; // deprecated, v1.2.13 // tx_response is the queried TxResponses. - cosmos.base.abci.v1beta1.TxResponse tx_response = 2; + cosmos.base.abci.v1beta1.TxResponse tx_response = 2; // deprecated, v1.2.13 // proof is the tmproto.TxProof for the queried tx tendermint.types.TxProof proof = 3; // ibc-go header to validate txs diff --git a/scripts/config/hermes.toml b/scripts/config/hermes.toml index 40248933f..7e90c8140 100644 --- a/scripts/config/hermes.toml +++ b/scripts/config/hermes.toml @@ -57,7 +57,7 @@ key_name = 'testkey' store_prefix = 'ibc' default_gas = 100000 max_gas = 3000000 -gas_price = { price = 0.000, denom = 'uqck' } +gas_price = { price = 0.000, denom = 'uatom' } gas_multiplier = 1.5 max_msg_num = 30 max_tx_size = 2097152 diff --git a/scripts/vars.sh b/scripts/vars.sh index 6b4f7f76c..affabb8df 100755 --- a/scripts/vars.sh +++ b/scripts/vars.sh @@ -38,10 +38,10 @@ QS3_RUN="docker-compose $DC --ansi never run --rm -T quicksilver3 quicksilverd" GAIA1_RUN="docker-compose $DC --ansi never run --rm -T gaia gaiad" GAIA2_RUN="docker-compose $DC --ansi never run --rm -T gaia2 gaiad" GAIA3_RUN="docker-compose $DC --ansi never run --rm -T gaia3 gaiad" -TZ1_1_RUN="docker-compose $DC --ansi never run --rm -T testzone1-1 icad" -TZ1_2_RUN="docker-compose $DC --ansi never run --rm -T testzone1-2 icad" -TZ1_3_RUN="docker-compose $DC --ansi never run --rm -T testzone1-3 icad" -TZ1_4_RUN="docker-compose $DC --ansi never run --rm -T testzone1-4 icad" +TZ1_1_RUN="docker-compose $DC --ansi never run --rm -T testzone1-1 gaiad" +TZ1_2_RUN="docker-compose $DC --ansi never run --rm -T testzone1-2 gaiad" +TZ1_3_RUN="docker-compose $DC --ansi never run --rm -T testzone1-3 gaiad" +TZ1_4_RUN="docker-compose $DC --ansi never run --rm -T testzone1-4 gaiad" TZ2_1_RUN="docker-compose $DC --ansi never run --rm -T testzone2-1 osmosisd" TZ2_2_RUN="docker-compose $DC --ansi never run --rm -T testzone2-2 osmosisd" TZ2_3_RUN="docker-compose $DC --ansi never run --rm -T testzone2-3 osmosisd" @@ -55,10 +55,10 @@ QS3_EXEC="docker-compose $DC --ansi never exec -T quicksilver3 quicksilverd" GAIA1_EXEC="docker-compose $DC --ansi never exec -T gaia gaiad" GAIA2_EXEC="docker-compose $DC --ansi never exec -T gaia2 gaiad" GAIA3_EXEC="docker-compose $DC --ansi never exec -T gaia3 gaiad" -TZ1_1_EXEC="docker-compose $DC --ansi never exec -T testzone1-1 icad" -TZ1_2_EXEC="docker-compose $DC --ansi never exec -T testzone1-2 icad" -TZ1_3_EXEC="docker-compose $DC --ansi never exec -T testzone1-3 icad" -TZ1_4_EXEC="docker-compose $DC --ansi never exec -T testzone1-4 icad" +TZ1_1_EXEC="docker-compose $DC --ansi never exec -T testzone1-1 gaiad" +TZ1_2_EXEC="docker-compose $DC --ansi never exec -T testzone1-2 gaiad" +TZ1_3_EXEC="docker-compose $DC --ansi never exec -T testzone1-3 gaiad" +TZ1_4_EXEC="docker-compose $DC --ansi never exec -T testzone1-4 gaiad" TZ2_1_EXEC="docker-compose $DC --ansi never exec -T testzone2-1 osmosisd" TZ2_2_EXEC="docker-compose $DC --ansi never exec -T testzone2-2 osmosisd" TZ2_3_EXEC="docker-compose $DC --ansi never exec -T testzone2-3 osmosisd" diff --git a/x/interchainquery/types/query.pb.go b/x/interchainquery/types/query.pb.go index 17b8f9774..7869f599f 100644 --- a/x/interchainquery/types/query.pb.go +++ b/x/interchainquery/types/query.pb.go @@ -143,7 +143,7 @@ func (m *QueryRequestsResponse) GetPagination() *query.PageResponse { // GetTxResponse is the response type for the Service.GetTx method. type GetTxWithProofResponse struct { - // tx is the queried transaction. + // tx is the queried transaction; deprecated. Tx *tx.Tx `protobuf:"bytes,1,opt,name=tx,proto3" json:"tx,omitempty"` // tx_response is the queried TxResponses. TxResponse *types.TxResponse `protobuf:"bytes,2,opt,name=tx_response,json=txResponse,proto3" json:"tx_response,omitempty"` diff --git a/x/interchainstaking/keeper/callbacks.go b/x/interchainstaking/keeper/callbacks.go index 94ca7d256..dd757f8f4 100644 --- a/x/interchainstaking/keeper/callbacks.go +++ b/x/interchainstaking/keeper/callbacks.go @@ -2,20 +2,27 @@ package keeper import ( "bytes" + "encoding/hex" "errors" "fmt" + "strings" "time" sdkioerrors "cosmossdk.io/errors" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/bech32" "github.com/cosmos/cosmos-sdk/types/tx" + "google.golang.org/protobuf/encoding/protowire" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" tmclienttypes "github.com/cosmos/ibc-go/v5/modules/light-clients/07-tendermint/types" + "github.com/tendermint/tendermint/crypto/tmhash" tmtypes "github.com/tendermint/tendermint/types" "github.com/ingenuity-build/quicksilver/utils" @@ -208,6 +215,8 @@ func PerfBalanceCallback(k Keeper, ctx sdk.Context, response []byte, query icqty return nil } +// DepositIntervalCallback will issue a tendermint.Tx query for any hash that is reported and +// for which a receipt has not yet been issued. func DepositIntervalCallback(k Keeper, ctx sdk.Context, args []byte, query icqtypes.Query) error { zone, found := k.GetZone(ctx, query.GetChainId()) if !found { @@ -344,6 +353,8 @@ func checkValidity( return nil } +// DepositTx handles an individual deposit transaction given a Tendermint Tx and +// associated block inclusion proof. func DepositTx(k Keeper, ctx sdk.Context, args []byte, query icqtypes.Query) error { zone, found := k.GetZone(ctx, query.GetChainId()) if !found { @@ -361,9 +372,24 @@ func DepositTx(k Keeper, ctx sdk.Context, args []byte, query icqtypes.Query) err return err } - _, found = k.GetReceipt(ctx, GetReceiptKey(zone.ChainId, res.GetTxResponse().TxHash)) + // check tx is valid for hash. + hash := tmhash.Sum(res.Proof.Data) + hashStr := hex.EncodeToString(hash) + + queryRequest := tx.GetTxRequest{} + err = k.cdc.Unmarshal(query.Request, &queryRequest) + if err != nil { + return err + } + + // check hash matches query + if !strings.EqualFold(hashStr, queryRequest.Hash) { + return fmt.Errorf("invalid tx for query - expected %s, got %s", queryRequest.Hash, hashStr) + } + + _, found = k.GetReceipt(ctx, GetReceiptKey(zone.ChainId, hashStr)) if found { - k.Logger(ctx).Info("Found previously handled tx. Ignoring.", "txhash", res.GetTxResponse().TxHash) + k.Logger(ctx).Info("Found previously handled tx. Ignoring.", "txhash", hashStr) return nil } @@ -408,7 +434,16 @@ func DepositTx(k Keeper, ctx sdk.Context, args []byte, query icqtypes.Query) err return fmt.Errorf("unable to validate proof: %w", err) } - return k.HandleReceiptTransaction(ctx, res.GetTxResponse(), res.GetTx(), zone) + sdkTx, err := TxDecoder(k.cdc)(res.Proof.Data) + if err != nil { + return err + } + + authTx, ok := sdkTx.(*tx.Tx) + if !ok { + return errors.New("cannot assert type of tx") + } + return k.HandleReceiptTransaction(ctx, authTx, hashStr, zone) } // AccountBalanceCallback is a callback handler for Balance queries. @@ -498,6 +533,9 @@ func DelegationAccountBalanceCallback(k Keeper, ctx sdk.Context, args []byte, qu return err } + zone.WithdrawalWaitgroup-- + k.SetZone(ctx, &zone) + return k.FlushOutstandingDelegations(ctx, &zone, coin) } @@ -545,3 +583,118 @@ func AllBalancesCallback(k Keeper, ctx sdk.Context, args []byte, query icqtypes. return k.SetAccountBalance(ctx, zone, balanceQuery.Address, args) } + +// TxDecoder returns a function that converts transactions from +// []byte to an sdk.Tx. This logic is largely copied from +// x/auth/tx/decoder.go, without some of the checking of unknown fields +// that a) require a codec.ProtoCodecMarshaler; and b) are unrequired +// given the finite scope of the result. + +func TxDecoder(cdc codec.Codec) sdk.TxDecoder { + return func(txBytes []byte) (sdk.Tx, error) { + // Make sure txBytes follow ADR-027. + err := rejectNonADR027TxRaw(txBytes) + if err != nil { + return nil, sdkioerrors.Wrap(sdkerrors.ErrTxDecode, err.Error()) + } + + var raw tx.TxRaw + + err = cdc.Unmarshal(txBytes, &raw) + if err != nil { + return nil, err + } + + var body tx.TxBody + + err = cdc.Unmarshal(raw.BodyBytes, &body) + if err != nil { + return nil, sdkioerrors.Wrap(sdkerrors.ErrTxDecode, err.Error()) + } + + var authInfo tx.AuthInfo + + err = cdc.Unmarshal(raw.AuthInfoBytes, &authInfo) + if err != nil { + return nil, sdkioerrors.Wrap(sdkerrors.ErrTxDecode, err.Error()) + } + + return &tx.Tx{ + Body: &body, + AuthInfo: &authInfo, + Signatures: raw.Signatures, + }, nil + } +} + +func rejectNonADR027TxRaw(txBytes []byte) error { + // Make sure all fields are ordered in ascending order with this variable. + prevTagNum := protowire.Number(0) + + for len(txBytes) > 0 { + tagNum, wireType, m := protowire.ConsumeTag(txBytes) + if m < 0 { + return fmt.Errorf("invalid length; %w", protowire.ParseError(m)) + } + // TxRaw only has bytes fields. + if wireType != protowire.BytesType { + return fmt.Errorf("expected %d wire type, got %d", protowire.BytesType, wireType) + } + // Make sure fields are ordered in ascending order. + if tagNum < prevTagNum { + return fmt.Errorf("txRaw must follow ADR-027, got tagNum %d after tagNum %d", tagNum, prevTagNum) + } + prevTagNum = tagNum + + // All 3 fields of TxRaw have wireType == 2, so their next component + // is a varint, so we can safely call ConsumeVarint here. + // Byte structure: + // Inner fields are verified in `DefaultTxDecoder` + lengthPrefix, m := protowire.ConsumeVarint(txBytes[m:]) + if m < 0 { + return fmt.Errorf("invalid length; %w", protowire.ParseError(m)) + } + // We make sure that this varint is as short as possible. + n := varintMinLength(lengthPrefix) + if n != m { + return fmt.Errorf("length prefix varint for tagNum %d is not as short as possible, read %d, only need %d", tagNum, m, n) + } + + // Skip over the bytes that store fieldNumber and wireType bytes. + _, _, m = protowire.ConsumeField(txBytes) + if m < 0 { + return fmt.Errorf("invalid length; %w", protowire.ParseError(m)) + } + txBytes = txBytes[m:] + } + + return nil +} + +// varintMinLength returns the minimum number of bytes necessary to encode an +// uint using varint encoding. +func varintMinLength(n uint64) int { + switch { + // Note: 1<