Skip to content

Commit a170ee9

Browse files
gRPC endpoint for CosmWasm app (#43)
* grpc for wasm app * wip rpc client * rpc client * cleanup * network name * block signature * logs * logs * fix error `codespace sdk code 2: tx parse error: unable to resolve type URL /cosmwasm.wasm.v1.MsgStoreCode`. interfaceRegistry in ClientContext should be taken from Wasm Application --------- Co-authored-by: ramil <ramilexe@gmail.com>
1 parent 1cf70ef commit a170ee9

File tree

4 files changed

+250
-8
lines changed

4 files changed

+250
-8
lines changed

example/wasm/main.go

Lines changed: 166 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,36 @@ package main
22

33
import (
44
"context"
5+
"encoding/json"
56
"fmt"
67
"os"
8+
"os/signal"
9+
"syscall"
710

811
"cosmossdk.io/log"
912
"github.com/CosmWasm/wasmd/app"
1013
"github.com/CosmWasm/wasmd/x/wasm/keeper"
1114
wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
15+
rpchttp "github.com/cometbft/cometbft/rpc/client/http"
1216
dbm "github.com/cosmos/cosmos-db"
1317
"github.com/cosmos/cosmos-sdk/baseapp"
18+
"github.com/cosmos/cosmos-sdk/client"
19+
"github.com/cosmos/cosmos-sdk/codec"
20+
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
1421
"github.com/cosmos/cosmos-sdk/server"
22+
srvconfig "github.com/cosmos/cosmos-sdk/server/config"
23+
servergrpc "github.com/cosmos/cosmos-sdk/server/grpc"
1524
"github.com/cosmos/cosmos-sdk/testutil/sims"
1625
sdk "github.com/cosmos/cosmos-sdk/types"
26+
"github.com/cosmos/cosmos-sdk/x/auth/tx"
27+
"golang.org/x/sync/errgroup"
28+
"google.golang.org/grpc"
29+
"google.golang.org/grpc/credentials/insecure"
1730

1831
"github.com/consideritdone/landslidevm"
32+
"github.com/consideritdone/landslidevm/utils/ids"
1933
"github.com/consideritdone/landslidevm/vm"
34+
vmtypes "github.com/consideritdone/landslidevm/vm/types"
2035
)
2136

2237
func main() {
@@ -40,8 +55,158 @@ func WasmCreator() vm.AppCreator {
4055
cfg.SetBech32PrefixForConsensusNode(app.Bech32PrefixConsAddr, app.Bech32PrefixConsPub)
4156
cfg.SetAddressVerifier(wasmtypes.VerifyAddressLen())
4257
cfg.Seal()
43-
wasmApp := app.NewWasmApp(logger, db, nil, true, sims.NewAppOptionsWithFlagHome(os.TempDir()), []keeper.Option{}, baseapp.SetChainID("landslide-test"))
58+
59+
srvCfg := *srvconfig.DefaultConfig()
60+
grpcCfg := srvCfg.GRPC
61+
var vmCfg vmtypes.VmConfig
62+
vmCfg.SetDefaults()
63+
if len(config.ConfigBytes) > 0 {
64+
if err := json.Unmarshal(config.ConfigBytes, &vmCfg); err != nil {
65+
return nil, fmt.Errorf("failed to unmarshal config %s: %w", string(config.ConfigBytes), err)
66+
}
67+
// set the grpc port, if it is set to 0, disable gRPC
68+
if vmCfg.GRPCPort > 0 {
69+
grpcCfg.Address = fmt.Sprintf("127.0.0.1:%d", vmCfg.GRPCPort)
70+
} else {
71+
grpcCfg.Enable = false
72+
}
73+
}
74+
75+
if err := vmCfg.Validate(); err != nil {
76+
return nil, err
77+
}
78+
chainID := vmCfg.NetworkName
79+
80+
var wasmApp = app.NewWasmApp(
81+
logger,
82+
db,
83+
nil,
84+
true,
85+
sims.NewAppOptionsWithFlagHome(os.TempDir()),
86+
[]keeper.Option{},
87+
baseapp.SetChainID(chainID),
88+
)
89+
90+
// early return if gRPC is disabled
91+
if !grpcCfg.Enable {
92+
return server.NewCometABCIWrapper(wasmApp), nil
93+
}
94+
95+
interfaceRegistry := wasmApp.InterfaceRegistry()
96+
marshaller := codec.NewProtoCodec(interfaceRegistry)
97+
clientCtx := client.Context{}.
98+
WithCodec(marshaller).
99+
WithLegacyAmino(makeCodec()).
100+
WithTxConfig(tx.NewTxConfig(marshaller, tx.DefaultSignModes)).
101+
WithInterfaceRegistry(interfaceRegistry).
102+
WithChainID(chainID)
103+
104+
avaChainID, err := ids.ToID(config.ChainId)
105+
if err != nil {
106+
return nil, err
107+
}
108+
109+
rpcURI := fmt.Sprintf(
110+
"http://127.0.0.1:%d/ext/bc/%s/rpc",
111+
vmCfg.RPCPort,
112+
avaChainID,
113+
)
114+
115+
clientCtx = clientCtx.WithNodeURI(rpcURI)
116+
rpcclient, err := rpchttp.New(rpcURI, "/websocket")
117+
if err != nil {
118+
return nil, err
119+
}
120+
clientCtx = clientCtx.WithClient(rpcclient)
121+
122+
// use the provided clientCtx to register the services
123+
wasmApp.RegisterTxService(clientCtx)
124+
wasmApp.RegisterTendermintService(clientCtx)
125+
wasmApp.RegisterNodeService(clientCtx, srvconfig.Config{})
126+
127+
maxSendMsgSize := grpcCfg.MaxSendMsgSize
128+
if maxSendMsgSize == 0 {
129+
maxSendMsgSize = srvconfig.DefaultGRPCMaxSendMsgSize
130+
}
131+
132+
maxRecvMsgSize := grpcCfg.MaxRecvMsgSize
133+
if maxRecvMsgSize == 0 {
134+
maxRecvMsgSize = srvconfig.DefaultGRPCMaxRecvMsgSize
135+
}
136+
137+
// if gRPC is enabled, configure gRPC client for gRPC gateway
138+
grpcClient, err := grpc.Dial(
139+
grpcCfg.Address,
140+
grpc.WithTransportCredentials(insecure.NewCredentials()),
141+
grpc.WithDefaultCallOptions(
142+
grpc.ForceCodec(codec.NewProtoCodec(clientCtx.InterfaceRegistry).GRPCCodec()),
143+
grpc.MaxCallRecvMsgSize(maxRecvMsgSize),
144+
grpc.MaxCallSendMsgSize(maxSendMsgSize),
145+
),
146+
)
147+
if err != nil {
148+
return nil, err
149+
}
150+
151+
clientCtx = clientCtx.WithGRPCClient(grpcClient)
152+
logger.Debug("gRPC client assigned to client context", "target", grpcCfg.Address)
153+
154+
g, ctx := getCtx(logger, false)
155+
156+
grpcSrv, err := servergrpc.NewGRPCServer(clientCtx, wasmApp, grpcCfg)
157+
if err != nil {
158+
return nil, err
159+
}
160+
161+
// Start the gRPC server in a goroutine. Note, the provided ctx will ensure
162+
// that the server is gracefully shut down.
163+
g.Go(func() error {
164+
return servergrpc.StartGRPCServer(ctx, logger.With("module", "grpc-server"), grpcCfg, grpcSrv)
165+
})
44166

45167
return server.NewCometABCIWrapper(wasmApp), nil
46168
}
47169
}
170+
171+
// custom tx codec
172+
func makeCodec() *codec.LegacyAmino {
173+
cdc := codec.NewLegacyAmino()
174+
sdk.RegisterLegacyAminoCodec(cdc)
175+
cryptocodec.RegisterCrypto(cdc)
176+
return cdc
177+
}
178+
179+
func getCtx(logger log.Logger, block bool) (*errgroup.Group, context.Context) {
180+
ctx, cancelFn := context.WithCancel(context.Background())
181+
g, ctx := errgroup.WithContext(ctx)
182+
// listen for quit signals so the calling parent process can gracefully exit
183+
listenForQuitSignals(g, block, cancelFn, logger)
184+
return g, ctx
185+
}
186+
187+
// listenForQuitSignals listens for SIGINT and SIGTERM. When a signal is received,
188+
// the cleanup function is called, indicating the caller can gracefully exit or
189+
// return.
190+
//
191+
// Note, the blocking behavior of this depends on the block argument.
192+
// The caller must ensure the corresponding context derived from the cancelFn is used correctly.
193+
func listenForQuitSignals(g *errgroup.Group, block bool, cancelFn context.CancelFunc, logger log.Logger) {
194+
sigCh := make(chan os.Signal, 1)
195+
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
196+
197+
f := func() {
198+
sig := <-sigCh
199+
cancelFn()
200+
201+
logger.Info("caught signal", "signal", sig.String())
202+
}
203+
204+
if block {
205+
g.Go(func() error {
206+
f()
207+
return nil
208+
})
209+
} else {
210+
go f()
211+
}
212+
}

vm/rpc.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ func (rpc *RPC) ABCIQuery(
139139
}
140140

141141
func (rpc *RPC) BroadcastTxCommit(ctx *rpctypes.Context, tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
142+
rpc.vm.logger.Info("BroadcastTxCommit called")
142143
subscriber := ctx.RemoteAddr()
143144

144145
// Subscribe to tx being committed in block.
@@ -208,7 +209,7 @@ func (rpc *RPC) BroadcastTxCommit(ctx *rpctypes.Context, tx types.Tx) (*ctypes.R
208209
Hash: tx.Hash(),
209210
}, err
210211
// TODO: use rpc.config.TimeoutBroadcastTxCommit for timeout
211-
case <-time.After(10 * time.Second):
212+
case <-time.After(30 * time.Second):
212213
err = errors.New("timed out waiting for tx to be included in a block")
213214
rpc.vm.logger.Error("Error on broadcastTxCommit", "err", err)
214215
return &ctypes.ResultBroadcastTxCommit{
@@ -221,22 +222,28 @@ func (rpc *RPC) BroadcastTxCommit(ctx *rpctypes.Context, tx types.Tx) (*ctypes.R
221222
}
222223

223224
func (rpc *RPC) BroadcastTxAsync(_ *rpctypes.Context, tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
225+
rpc.vm.logger.Info("BroadcastTxAsync called")
224226
err := rpc.vm.mempool.CheckTx(tx, nil, mempl.TxInfo{})
225227
if err != nil {
228+
rpc.vm.logger.Error("Error on broadcastTxAsync", "err", err)
226229
return nil, err
227230
}
228231
return &ctypes.ResultBroadcastTx{Hash: tx.Hash()}, nil
229232
}
230233

231234
func (rpc *RPC) BroadcastTxSync(_ *rpctypes.Context, tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
235+
rpc.vm.logger.Info("BroadcastTxSync called")
232236
resCh := make(chan *abci.ResponseCheckTx, 1)
233237
err := rpc.vm.mempool.CheckTx(tx, func(res *abci.ResponseCheckTx) {
234238
resCh <- res
235239
}, mempl.TxInfo{})
236240
if err != nil {
241+
rpc.vm.logger.Error("Error on BroadcastTxSync", "err", err)
237242
return nil, err
238243
}
239244
res := <-resCh
245+
246+
rpc.vm.logger.Info("BroadcastTxSync response", "Code", res.Code, "Log", res.Log, "Codespace", res.Codespace, "Hash", tx.Hash())
240247
return &ctypes.ResultBroadcastTx{
241248
Code: res.GetCode(),
242249
Data: res.GetData(),
@@ -397,8 +404,11 @@ func (rpc *RPC) Block(_ *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBloc
397404
blockMeta := rpc.vm.blockStore.LoadBlockMeta(height)
398405

399406
if blockMeta == nil {
407+
rpc.vm.logger.Info("Block not found", "height", height)
400408
return &ctypes.ResultBlock{BlockID: types.BlockID{}, Block: block}, nil
401409
}
410+
411+
rpc.vm.logger.Info("Block response", "height", height, "block", block, "blockMeta", blockMeta)
402412
return &ctypes.ResultBlock{BlockID: blockMeta.BlockID, Block: block}, nil
403413
}
404414

@@ -539,12 +549,15 @@ func (rpc *RPC) Validators(
539549
}
540550

541551
func (rpc *RPC) Tx(_ *rpctypes.Context, hash []byte, prove bool) (*ctypes.ResultTx, error) {
552+
rpc.vm.logger.Info("Tx called", "hash", hash)
542553
r, err := rpc.vm.txIndexer.Get(hash)
543554
if err != nil {
555+
rpc.vm.logger.Error("Error on Tx", "err", err)
544556
return nil, err
545557
}
546558

547559
if r == nil {
560+
rpc.vm.logger.Error("Error on Tx", "tx not found", hash)
548561
return nil, fmt.Errorf("tx (%X) not found", hash)
549562
}
550563

@@ -736,7 +749,7 @@ func (rpc *RPC) Status(_ *rpctypes.Context) (*ctypes.ResultStatus, error) {
736749
),
737750
DefaultNodeID: p2p.ID(rpc.vm.appOpts.NodeId),
738751
ListenAddr: "",
739-
Network: fmt.Sprintf("%d", rpc.vm.appOpts.NetworkId),
752+
Network: rpc.vm.networkName,
740753
Version: version.TMCoreSemVer,
741754
Channels: nil,
742755
Moniker: "",

vm/types/config.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package types
2+
3+
import (
4+
"fmt"
5+
"time"
6+
)
7+
8+
const (
9+
defaultRPCPort = 9752
10+
defaultGRPCPort = 9090
11+
defaultMaxOpenConnections = 0 // unlimited
12+
defaultTimeoutBroadcastTxCommit time.Duration = 30 * time.Second
13+
)
14+
15+
// VmConfig ...
16+
type VmConfig struct {
17+
RPCPort uint16 `json:"rpc_port"`
18+
GRPCPort uint16 `json:"grpc_port"`
19+
GRPCMaxOpenConnections int `json:"grpc_max_open_connections"`
20+
TimeoutBroadcastTxCommit time.Duration `json:"broadcast_commit_timeout"`
21+
NetworkName string `json:"network_name"`
22+
}
23+
24+
// SetDefaults sets the default values for the config.
25+
func (c *VmConfig) SetDefaults() {
26+
c.RPCPort = defaultRPCPort
27+
c.GRPCPort = defaultGRPCPort
28+
c.GRPCMaxOpenConnections = defaultMaxOpenConnections
29+
c.TimeoutBroadcastTxCommit = defaultTimeoutBroadcastTxCommit
30+
c.NetworkName = "landslide-test"
31+
}
32+
33+
// Validate returns an error if this is an invalid config.
34+
func (c *VmConfig) Validate() error {
35+
if c.GRPCMaxOpenConnections < 0 {
36+
return fmt.Errorf("grpc_max_open_connections can't be negative")
37+
}
38+
39+
if c.TimeoutBroadcastTxCommit < 0 {
40+
return fmt.Errorf("broadcast_tx_commit_timeout can't be negative")
41+
}
42+
43+
if len(c.NetworkName) == 0 {
44+
return fmt.Errorf("network_name can't be empty")
45+
}
46+
47+
return nil
48+
}

0 commit comments

Comments
 (0)