Description
Is there an existing issue for this?
- I have searched the existing issues
What happened?
In general, functions making network requests should be cancelled when the context they receive is cancelled, but the functions in the client and client/* packages do not handle context cancellation.
Here is a simple example:
ctx, cancel := context.WithCancel(context.Background())
cancel()
height, err := rpc.GetChainHeight(clientCtx.WithCmdContext(ctx))
if err == nil {
fmt.Println("Height:", height)
} else {
fmt.Fprintln(os.Stderr, "Failed to get chain height:", err)
}
In this example, I expect rpc.GetChainHeight
to fail immediately because CmdContext
is cancelled before it is called, but it returns a height without an error.
It seems that the functions in the client and client/* packages should use CmdContext
in the same way github.com/cosmos/cosmos-sdk/x/auth/client
package does.
cf. https://github.com/cosmos/cosmos-sdk/blob/v0.50.13/x/auth/client/tx.go#L65
Specifically, the following changes will make some of the functions handle context cancellation:
diff --git a/client/query.go b/client/query.go
index 29b99bb918..1933f768b5 100644
--- a/client/query.go
+++ b/client/query.go
@@ -1,7 +1,6 @@
package client
import (
- "context"
"fmt"
"strings"
@@ -95,7 +94,7 @@ func (ctx Context) queryABCI(req abci.RequestQuery) (abci.ResponseQuery, error)
Prove: req.Prove,
}
- result, err := node.ABCIQueryWithOptions(context.Background(), req.Path, req.Data, opts)
+ result, err := node.ABCIQueryWithOptions(ctx.CmdContext, req.Path, req.Data, opts)
if err != nil {
return abci.ResponseQuery{}, err
}
diff --git a/client/rpc/block.go b/client/rpc/block.go
index d1b99d7229..93e5d166fe 100644
--- a/client/rpc/block.go
+++ b/client/rpc/block.go
@@ -1,7 +1,6 @@
package rpc
import (
- "context"
"encoding/hex"
"fmt"
"time"
@@ -20,7 +19,7 @@ func GetChainHeight(clientCtx client.Context) (int64, error) {
return -1, err
}
- status, err := node.Status(context.Background())
+ status, err := node.Status(clientCtx.CmdContext)
if err != nil {
return -1, err
}
@@ -53,7 +52,7 @@ func QueryBlocks(clientCtx client.Context, page, limit int, query, orderBy strin
return nil, err
}
- resBlocks, err := node.BlockSearch(context.Background(), query, &page, &limit, orderBy)
+ resBlocks, err := node.BlockSearch(clientCtx.CmdContext, query, &page, &limit, orderBy)
if err != nil {
return nil, err
}
@@ -79,7 +78,7 @@ func GetBlockByHeight(clientCtx client.Context, height *int64) (*cmt.Block, erro
// header -> BlockchainInfo
// header, tx -> Block
// results -> BlockResults
- resBlock, err := node.Block(context.Background(), height)
+ resBlock, err := node.Block(clientCtx.CmdContext, height)
if err != nil {
return nil, err
}
@@ -104,7 +103,7 @@ func GetBlockByHash(clientCtx client.Context, hashHexString string) (*cmt.Block,
return nil, err
}
- resBlock, err := node.BlockByHash(context.Background(), hash)
+ resBlock, err := node.BlockByHash(clientCtx.CmdContext, hash)
if err != nil {
return nil, err
Cosmos SDK Version
0.50.13
How to reproduce?
package main
import (
"context"
"fmt"
"log"
"os"
"cosmossdk.io/x/tx/signing"
rpchttp "github.com/cometbft/cometbft/rpc/client/http"
jsonrpcclient "github.com/cometbft/cometbft/rpc/jsonrpc/client"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/rpc"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/address"
"github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/std"
sdk "github.com/cosmos/cosmos-sdk/types"
querytypes "github.com/cosmos/cosmos-sdk/types/query"
"github.com/cosmos/cosmos-sdk/x/auth/tx"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/cosmos/gogoproto/proto"
)
func createClient(nodeURI string) (*rpchttp.HTTP, error) {
httpClient, err := jsonrpcclient.DefaultHTTPClient(nodeURI)
if err != nil {
return nil, err
}
return rpchttp.NewWithClient(nodeURI, "/websocket", httpClient)
}
func createClientContext(c *rpchttp.HTTP) (client.Context, error) {
interfaceRegistry, _ := types.NewInterfaceRegistryWithOptions(types.InterfaceRegistryOptions{
ProtoFiles: proto.HybridResolver,
SigningOptions: signing.Options{
AddressCodec: address.Bech32Codec{
Bech32Prefix: sdk.GetConfig().GetBech32AccountAddrPrefix(),
},
ValidatorAddressCodec: address.Bech32Codec{
Bech32Prefix: sdk.GetConfig().GetBech32ValidatorAddrPrefix(),
},
},
})
appCodec := codec.NewProtoCodec(interfaceRegistry)
legacyAmino := codec.NewLegacyAmino()
txConfig := tx.NewTxConfig(appCodec, tx.DefaultSignModes)
if err := interfaceRegistry.SigningContext().Validate(); err != nil {
return client.Context{}, err
}
std.RegisterLegacyAminoCodec(legacyAmino)
std.RegisterInterfaces(interfaceRegistry)
clientCtx := client.Context{}.
WithCodec(appCodec).
WithInterfaceRegistry(interfaceRegistry).
WithTxConfig(txConfig).
WithLegacyAmino(legacyAmino).
WithAccountRetriever(authtypes.AccountRetriever{}).
WithClient(c)
return clientCtx, nil
}
func getBalances(ctx client.Context, addr sdk.AccAddress) (sdk.Coins, error) {
params := banktypes.NewQueryAllBalancesRequest(addr, &querytypes.PageRequest{
Key: []byte(""),
Offset: 0,
Limit: 1000,
CountTotal: true,
}, true)
queryClient := banktypes.NewQueryClient(ctx)
res, err := queryClient.AllBalances(ctx.CmdContext, params)
if err != nil {
return nil, err
}
return res.Balances, nil
}
func main() {
nodeURI := "tcp://localhost:26657"
rpcClient, err := createClient(nodeURI)
if err != nil {
log.Fatal(err)
}
clientCtx, err := createClientContext(rpcClient)
if err != nil {
log.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
cancel()
clientCtx = clientCtx.WithCmdContext(ctx)
height, err := rpc.GetChainHeight(clientCtx)
if err == nil {
fmt.Println("Height:", height)
} else {
fmt.Fprintln(os.Stderr, "Failed to get chain height:", err)
}
block, err := rpc.GetBlockByHeight(clientCtx, &height)
if err == nil {
clientCtx.PrintProto(block)
} else {
fmt.Fprintln(os.Stderr, "Failed to get block:", err)
}
balances, err := getBalances(clientCtx, sdk.MustAccAddressFromBech32("cosmos1g8hssmdd8uwp7vy4xahrgd0hary9x4m0963zma"))
if err == nil {
fmt.Println("Balances:", balances)
} else {
fmt.Fprintln(os.Stderr, "Failed to get balances:", err)
}
}
Here is the actual output:
Height: 5959
{"header":{"version":{"block":"11","app":"0"},"chain_id":"testchain","height":"5959","time":"2025-04-13T14:11:10.483716Z","last_block_id":{"hash":"rucetZznDC6FBJsHoOXl1v6Pa4eUptrn5uqa3ACb9I8=","part_set_header":{"total":1,"hash":"V21mZVCXDh4nIl4wRLo3j94whpm759/+1rDUfT7/QCE="}},"last_commit_hash":"jI7oxlEclPe6Z8AkkdMJeuiGGuDQlG3aNvwlpqbG7p0=","data_hash":"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=","validators_hash":"qXYhUdL+h20+ohzS8RXMJMhLXxeaWMPxoKV/fAlt88E=","next_validators_hash":"qXYhUdL+h20+ohzS8RXMJMhLXxeaWMPxoKV/fAlt88E=","consensus_hash":"BICRvH3cKD93v7+R1zxE2ljD34qcvIZ0Bdi389qtoi8=","app_hash":"NA6nF7eHR/GFarIecl8iHfkUgbMHXi/JZVxRAc9/Ia0=","last_results_hash":"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=","evidence_hash":"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=","proposer_address":"9L5eHsYA7jWAx6dsSF32fSwCY7M="},"data":{"txs":[]},"evidence":{"evidence":[]},"last_commit":{"height":"5958","round":0,"block_id":{"hash":"rucetZznDC6FBJsHoOXl1v6Pa4eUptrn5uqa3ACb9I8=","part_set_header":{"total":1,"hash":"V21mZVCXDh4nIl4wRLo3j94whpm759/+1rDUfT7/QCE="}},"signatures":[{"block_id_flag":"BLOCK_ID_FLAG_COMMIT","validator_address":"9L5eHsYA7jWAx6dsSF32fSwCY7M=","timestamp":"2025-04-13T14:11:10.483716Z","signature":"FirufuiIUKfo0pHPEaJ9JN1wuDagzBz90KJQdpmuZ3LTl1eQhCBpL9nN8A6SSZ1xYkVX/U/oAjMXvQCxBRjTDA=="}]}}
Balances: 9999999999999990000000000stake
And here is the expected output:
Failed to get chain height: post failed: Post "http://localhost:26657": context canceled
Failed to get block: post failed: Post "http://localhost:26657": context canceled
Failed to get balances: post failed: Post "http://localhost:26657": context canceled