Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ignite
GitHub Repository: ignite/cli
Path: blob/main/integration/relayer/cmd_relayer_test.go
1007 views
1
//go:build !relayer
2
3
package relayer_test
4
5
import (
6
"bytes"
7
"context"
8
"encoding/json"
9
"fmt"
10
"os"
11
"path/filepath"
12
"strings"
13
"testing"
14
"time"
15
16
"github.com/cosmos/cosmos-sdk/crypto/keyring"
17
sdk "github.com/cosmos/cosmos-sdk/types"
18
"github.com/stretchr/testify/require"
19
"gopkg.in/yaml.v3"
20
21
"github.com/ignite/cli/v29/ignite/config/chain"
22
"github.com/ignite/cli/v29/ignite/config/chain/base"
23
v1 "github.com/ignite/cli/v29/ignite/config/chain/v1"
24
"github.com/ignite/cli/v29/ignite/pkg/availableport"
25
"github.com/ignite/cli/v29/ignite/pkg/cmdrunner"
26
"github.com/ignite/cli/v29/ignite/pkg/cmdrunner/step"
27
"github.com/ignite/cli/v29/ignite/pkg/errors"
28
"github.com/ignite/cli/v29/ignite/pkg/goanalysis"
29
"github.com/ignite/cli/v29/ignite/pkg/randstr"
30
"github.com/ignite/cli/v29/ignite/pkg/xyaml"
31
envtest "github.com/ignite/cli/v29/integration"
32
)
33
34
const (
35
relayerMnemonic = "great immense still pill defense fetch pencil slow purchase symptom speed arm shoot fence have divorce cigar rapid hen vehicle pear evolve correct nerve"
36
)
37
38
var (
39
bobName = "bob"
40
refChainConfig = v1.Config{
41
Config: base.Config{
42
Version: 1,
43
Accounts: []base.Account{
44
{
45
Name: "alice",
46
Coins: []string{"100000000000token", "10000000000000000000stake"},
47
Mnemonic: "slide moment original seven milk crawl help text kick fluid boring awkward doll wonder sure fragile plate grid hard next casual expire okay body",
48
},
49
{
50
Name: bobName,
51
Coins: []string{"100000000000token", "10000000000000000000stake"},
52
Mnemonic: "trap possible liquid elite embody host segment fantasy swim cable digital eager tiny broom burden diary earn hen grow engine pigeon fringe claim program",
53
},
54
{
55
Name: "relayer",
56
Coins: []string{"100000000000token", "1000000000000000000000stake"},
57
Mnemonic: relayerMnemonic,
58
},
59
},
60
Faucet: base.Faucet{
61
Name: &bobName,
62
Coins: []string{"500token", "100000000stake"},
63
Host: ":4501",
64
},
65
Genesis: xyaml.Map{"chain_id": randstr.Runes(12)},
66
},
67
Validators: []v1.Validator{
68
{
69
Name: "alice",
70
Bonded: "100000000stake",
71
Client: xyaml.Map{"keyring-backend": keyring.BackendTest},
72
App: xyaml.Map{
73
"api": xyaml.Map{"address": ":1318"},
74
"grpc": xyaml.Map{"address": ":9092"},
75
"grpc-web": xyaml.Map{"address": ":9093"},
76
},
77
Config: xyaml.Map{
78
"p2p": xyaml.Map{"laddr": ":26658"},
79
"rpc": xyaml.Map{"laddr": ":26658", "pprof_laddr": ":6061"},
80
},
81
Home: filepath.Join(os.TempDir(), randstr.Runes(5)),
82
},
83
},
84
}
85
hostChainConfig = v1.Config{
86
Config: base.Config{
87
Version: 1,
88
Accounts: []base.Account{
89
{
90
Name: "alice",
91
Coins: []string{"100000000000token", "10000000000000000000stake"},
92
Mnemonic: "slide moment original seven milk crawl help text kick fluid boring awkward doll wonder sure fragile plate grid hard next casual expire okay body",
93
},
94
{
95
Name: bobName,
96
Coins: []string{"100000000000token", "10000000000000000000stake"},
97
Mnemonic: "trap possible liquid elite embody host segment fantasy swim cable digital eager tiny broom burden diary earn hen grow engine pigeon fringe claim program",
98
},
99
{
100
Name: "relayer",
101
Coins: []string{"100000000000token", "1000000000000000000000stake"},
102
Mnemonic: relayerMnemonic,
103
},
104
},
105
Faucet: base.Faucet{
106
Name: &bobName,
107
Coins: []string{"500token", "100000000stake"},
108
Host: ":4500",
109
},
110
Genesis: xyaml.Map{"chain_id": randstr.Runes(12)},
111
},
112
Validators: []v1.Validator{
113
{
114
Name: "alice",
115
Bonded: "100000000stake",
116
Client: xyaml.Map{"keyring-backend": keyring.BackendTest},
117
App: xyaml.Map{
118
"api": xyaml.Map{"address": ":1317"},
119
"grpc": xyaml.Map{"address": ":9090"},
120
"grpc-web": xyaml.Map{"address": ":9091"},
121
},
122
Config: xyaml.Map{
123
"p2p": xyaml.Map{"laddr": ":26656"},
124
"rpc": xyaml.Map{"laddr": ":26656", "pprof_laddr": ":6060"},
125
},
126
Home: filepath.Join(os.TempDir(), randstr.Runes(5)),
127
},
128
},
129
}
130
131
nameOnRecvIbcPostPacket = "OnRecvIbcPostPacket"
132
funcOnRecvIbcPostPacket = `package keeper
133
func (k Keeper) OnRecvIbcPostPacket(ctx context.Context, packet channeltypes.Packet, data types.IbcPostPacketData) (packetAck types.IbcPostPacketAck, err error) {
134
packetAck.PostId, err = k.PostSeq.Next(ctx)
135
if err != nil {
136
return packetAck, err
137
}
138
return packetAck, k.Post.Set(ctx, packetAck.PostId, types.Post{Title: data.Title, Content: data.Content})
139
}`
140
141
nameOnAcknowledgementIbcPostPacket = "OnAcknowledgementIbcPostPacket"
142
funcOnAcknowledgementIbcPostPacket = `package keeper
143
func (k Keeper) OnAcknowledgementIbcPostPacket(ctx context.Context, packet channeltypes.Packet, data types.IbcPostPacketData, ack channeltypes.Acknowledgement) error {
144
switch dispatchedAck := ack.Response.(type) {
145
case *channeltypes.Acknowledgement_Error:
146
// We will not treat acknowledgment error in this tutorial
147
return nil
148
case *channeltypes.Acknowledgement_Result:
149
// Decode the packet acknowledgment
150
var packetAck types.IbcPostPacketAck
151
if err := k.cdc.UnmarshalJSON(dispatchedAck.Result, &packetAck); err != nil {
152
// The counter-party module doesn't implement the correct acknowledgment format
153
return errors.New("cannot unmarshal acknowledgment")
154
}
155
156
seq, err := k.SentPostSeq.Next(ctx)
157
if err != nil {
158
return err
159
}
160
161
return k.SentPost.Set(ctx, seq,
162
types.SentPost{
163
PostId: packetAck.PostId,
164
Title: data.Title,
165
Chain: packet.DestinationPort + "-" + packet.DestinationChannel,
166
},
167
)
168
default:
169
return errors.New("the counter-party module does not implement the correct acknowledgment format")
170
}
171
}`
172
173
nameOnTimeoutIbcPostPacket = "OnTimeoutIbcPostPacket"
174
funcOnTimeoutIbcPostPacket = `package keeper
175
func (k Keeper) OnTimeoutIbcPostPacket(ctx context.Context, packet channeltypes.Packet, data types.IbcPostPacketData) error {
176
seq, err := k.TimeoutPostSeq.Next(ctx)
177
if err != nil {
178
return err
179
}
180
181
return k.TimeoutPost.Set(ctx, seq,
182
types.TimeoutPost{
183
Title: data.Title,
184
Chain: packet.DestinationPort + "-" + packet.DestinationChannel,
185
},
186
)
187
}`
188
)
189
190
type (
191
QueryChannels struct {
192
Channels []struct {
193
ChannelID string `json:"channel_id"`
194
ConnectionHops []string `json:"connection_hops"`
195
Counterparty struct {
196
ChannelID string `json:"channel_id"`
197
PortID string `json:"port_id"`
198
} `json:"counterparty"`
199
Ordering string `json:"ordering"`
200
PortID string `json:"port_id"`
201
State string `json:"state"`
202
Version string `json:"version"`
203
} `json:"channels"`
204
}
205
206
QueryBalances struct {
207
Balances sdk.Coins `json:"balances"`
208
}
209
)
210
211
func runChain(
212
ctx context.Context,
213
t *testing.T,
214
app envtest.App,
215
cfg v1.Config,
216
tmpDir string,
217
ports []uint,
218
) (api, rpc, grpc, faucet string) {
219
t.Helper()
220
if len(ports) < 7 {
221
t.Fatalf("invalid number of ports %d", len(ports))
222
}
223
224
var (
225
chainID = cfg.Genesis["chain_id"].(string)
226
chainPath = filepath.Join(tmpDir, chainID)
227
homePath = filepath.Join(chainPath, "home")
228
cfgPath = filepath.Join(chainPath, chain.ConfigFilenames[0])
229
)
230
require.NoError(t, os.MkdirAll(chainPath, os.ModePerm))
231
232
genAddr := func(port uint) string {
233
return fmt.Sprintf(":%d", port)
234
}
235
236
cfg.Validators[0].Home = homePath
237
238
cfg.Faucet.Host = genAddr(ports[0])
239
cfg.Validators[0].App["api"] = xyaml.Map{"address": genAddr(ports[1])}
240
cfg.Validators[0].App["grpc"] = xyaml.Map{"address": genAddr(ports[2])}
241
cfg.Validators[0].App["grpc-web"] = xyaml.Map{"address": genAddr(ports[3])}
242
cfg.Validators[0].Config["p2p"] = xyaml.Map{"laddr": genAddr(ports[4])}
243
cfg.Validators[0].Config["rpc"] = xyaml.Map{
244
"laddr": genAddr(ports[5]),
245
"pprof_laddr": genAddr(ports[6]),
246
}
247
248
file, err := os.Create(cfgPath)
249
require.NoError(t, err)
250
require.NoError(t, yaml.NewEncoder(file).Encode(cfg))
251
require.NoError(t, file.Close())
252
253
app.SetConfigPath(cfgPath)
254
app.SetHomePath(homePath)
255
go func() {
256
app.MustServe(ctx)
257
}()
258
259
genHTTPAddr := func(port uint) string {
260
return fmt.Sprintf("http://127.0.0.1:%d", port)
261
}
262
return genHTTPAddr(ports[1]), genHTTPAddr(ports[5]), genHTTPAddr(ports[2]), genHTTPAddr(ports[0])
263
}
264
265
func TestBlogIBC(t *testing.T) {
266
var (
267
env = envtest.New(t)
268
app = env.ScaffoldApp("github.com/apps/blog", "--no-module")
269
tmpDir = t.TempDir()
270
ctx, cancel = context.WithCancel(env.Ctx())
271
)
272
t.Cleanup(func() {
273
cancel()
274
time.Sleep(5 * time.Second)
275
require.NoError(t, os.RemoveAll(tmpDir))
276
})
277
278
app.Scaffold(
279
"create an IBC module",
280
false,
281
"module",
282
"blog",
283
"--ibc",
284
"--require-registration",
285
)
286
287
app.Scaffold(
288
"create a post type list in an IBC module",
289
false,
290
"list",
291
"post",
292
"title",
293
"content",
294
"--no-message",
295
"--module",
296
"blog",
297
)
298
299
app.Scaffold(
300
"create a sentPost type list in an IBC module",
301
false,
302
"list",
303
"sentPost",
304
"postID:uint",
305
"title",
306
"chain",
307
"--no-message",
308
"--module",
309
"blog",
310
)
311
312
app.Scaffold(
313
"create a timeoutPost type list in an IBC module",
314
false,
315
"list",
316
"timeoutPost",
317
"title",
318
"chain",
319
"--no-message",
320
"--module",
321
"blog",
322
)
323
324
app.Scaffold(
325
"create a ibcPost package in an IBC module",
326
false,
327
"packet",
328
"ibcPost",
329
"title",
330
"content",
331
"--ack",
332
"postID:uint",
333
"--module",
334
"blog",
335
)
336
337
blogKeeperPath := filepath.Join(app.SourcePath(), "x/blog/keeper")
338
require.NoError(t, goanalysis.ReplaceCode(
339
blogKeeperPath,
340
nameOnRecvIbcPostPacket,
341
funcOnRecvIbcPostPacket,
342
))
343
require.NoError(t, goanalysis.ReplaceCode(
344
blogKeeperPath,
345
nameOnAcknowledgementIbcPostPacket,
346
funcOnAcknowledgementIbcPostPacket,
347
))
348
require.NoError(t, goanalysis.ReplaceCode(
349
blogKeeperPath,
350
nameOnTimeoutIbcPostPacket,
351
funcOnTimeoutIbcPostPacket,
352
))
353
354
// serve both chains.
355
ports, err := availableport.Find(
356
14,
357
availableport.WithMinPort(4000),
358
availableport.WithMaxPort(5000),
359
)
360
require.NoError(t, err)
361
hostChainAPI, hostChainRPC, hostChainGRPC, hostChainFaucet := runChain(ctx, t, app, hostChainConfig, tmpDir, ports[:7])
362
hostChainChainID := hostChainConfig.Genesis["chain_id"].(string)
363
hostChainHome := hostChainConfig.Validators[0].Home
364
refChainAPI, refChainRPC, refChainGRPC, refChainFaucet := runChain(ctx, t, app, refChainConfig, tmpDir, ports[7:])
365
refChainChainID := refChainConfig.Genesis["chain_id"].(string)
366
refChainHome := refChainConfig.Validators[0].Home
367
368
// check the chains is up
369
app.WaitChainUp(ctx, hostChainAPI)
370
app.WaitChainUp(ctx, refChainAPI)
371
372
// ibc relayer.
373
env.Must(env.Exec("install the hermes relayer app",
374
step.NewSteps(step.New(
375
step.Exec(envtest.IgniteApp,
376
"app",
377
"install",
378
"-g",
379
// filepath.Join(goenv.GoPath(), "src/github.com/ignite/apps/hermes"), // Local path for test proposals
380
"github.com/ignite/apps/hermes@hermes/v0.2.8",
381
),
382
)),
383
))
384
385
env.Must(env.Exec("configure the hermes relayer app",
386
step.NewSteps(step.New(
387
step.Exec(envtest.IgniteApp,
388
"relayer",
389
"hermes",
390
"configure",
391
hostChainChainID,
392
hostChainRPC,
393
hostChainGRPC,
394
refChainChainID,
395
refChainRPC,
396
refChainGRPC,
397
"--chain-a-faucet", hostChainFaucet,
398
"--chain-b-faucet", refChainFaucet,
399
"--generate-wallets",
400
"--overwrite-config",
401
),
402
step.Workdir(app.SourcePath()),
403
)),
404
))
405
406
go func() {
407
env.Must(env.Exec("run the hermes relayer",
408
step.NewSteps(step.New(
409
step.Exec(envtest.IgniteApp,
410
"relayer",
411
"hermes",
412
"start",
413
hostChainChainID,
414
refChainChainID,
415
),
416
step.Workdir(app.SourcePath()),
417
)),
418
envtest.ExecCtx(ctx),
419
))
420
}()
421
time.Sleep(3 * time.Second)
422
423
var (
424
queryOutput = &bytes.Buffer{}
425
queryResponse QueryChannels
426
)
427
env.Must(env.Exec("verify if the channel was created", step.NewSteps(
428
step.New(
429
step.Stdout(queryOutput),
430
step.Stderr(queryOutput),
431
step.Exec(
432
app.Binary(),
433
"q",
434
"ibc",
435
"channel",
436
"channels",
437
"--node", hostChainRPC,
438
"--log_format", "json",
439
"--output", "json",
440
),
441
step.PostExec(func(execErr error) error {
442
if execErr != nil {
443
return execErr
444
}
445
if err := json.Unmarshal(queryOutput.Bytes(), &queryResponse); err != nil {
446
return errors.Errorf("unmarshling tx response: %w", err)
447
}
448
if len(queryResponse.Channels) == 0 ||
449
len(queryResponse.Channels[0].ConnectionHops) == 0 {
450
return errors.Errorf("channel not found")
451
}
452
if queryResponse.Channels[0].State != "STATE_OPEN" {
453
return errors.Errorf("channel is not open")
454
}
455
return nil
456
}),
457
),
458
)))
459
460
var (
461
sender = "alice"
462
receiverAddr = "cosmos1nrksk5swk6lnmlq670a8kwxmsjnu0ezqts39sa"
463
txOutput = &bytes.Buffer{}
464
txResponse struct {
465
Code int `json:"code"`
466
RawLog string `json:"raw_log"`
467
TxHash string `json:"txhash"`
468
}
469
)
470
471
stepsTx := step.NewSteps(
472
step.New(
473
step.Stdout(txOutput),
474
step.Stderr(txOutput),
475
step.PreExec(func() error {
476
txOutput.Reset()
477
return nil
478
}),
479
step.Exec(
480
app.Binary(),
481
"tx",
482
"ibc-transfer",
483
"transfer",
484
"transfer",
485
"channel-0",
486
receiverAddr,
487
"100000stake",
488
"--from", sender,
489
"--node", hostChainRPC,
490
"--home", hostChainHome,
491
"--chain-id", hostChainChainID,
492
"--output", "json",
493
"--log_format", "json",
494
"--keyring-backend", "test",
495
"--yes",
496
),
497
step.PostExec(func(execErr error) error {
498
if execErr != nil {
499
return execErr
500
}
501
output := txOutput.Bytes()
502
if err := json.Unmarshal(txOutput.Bytes(), &txResponse); err != nil {
503
return errors.Errorf("unmarshalling tx response error: %w, response: %s", err, string(output))
504
}
505
506
time.Sleep(4 * time.Second)
507
508
return cmdrunner.New().Run(ctx, step.New(
509
step.Exec(
510
app.Binary(),
511
"q",
512
"tx",
513
txResponse.TxHash,
514
"--node", hostChainRPC,
515
"--home", hostChainHome,
516
"--output", "json",
517
"--log_format", "json",
518
),
519
step.Stdout(txOutput),
520
step.Stderr(txOutput),
521
step.PreExec(func() error {
522
txOutput.Reset()
523
return nil
524
}),
525
step.PostExec(func(execErr error) error {
526
if execErr != nil {
527
return execErr
528
}
529
output := txOutput.Bytes()
530
if err := json.Unmarshal(output, &txResponse); err != nil {
531
return errors.Errorf("unmarshalling tx response error: %w, response: %s", err, string(output))
532
}
533
return nil
534
}),
535
))
536
}),
537
),
538
)
539
if !env.Exec("send an IBC transfer", stepsTx, envtest.ExecRetry()) {
540
t.FailNow()
541
}
542
require.Equal(t, 0, txResponse.Code,
543
"tx failed code=%d log=%s", txResponse.Code, txResponse.RawLog)
544
545
var (
546
balanceOutput = &bytes.Buffer{}
547
balanceResponse QueryBalances
548
)
549
steps := step.NewSteps(
550
step.New(
551
step.Stdout(balanceOutput),
552
step.Stderr(balanceOutput),
553
step.Exec(
554
app.Binary(),
555
"q",
556
"bank",
557
"balances",
558
receiverAddr,
559
"--node", refChainRPC,
560
"--home", refChainHome,
561
"--log_format", "json",
562
"--output", "json",
563
),
564
step.PreExec(func() error {
565
balanceOutput.Reset()
566
return nil
567
}),
568
step.PostExec(func(execErr error) error {
569
if execErr != nil {
570
return execErr
571
}
572
573
output := balanceOutput.Bytes()
574
if err := json.Unmarshal(output, &balanceResponse); err != nil {
575
return errors.Errorf("unmarshalling query response error: %w, response: %s", err, string(output))
576
}
577
if balanceResponse.Balances.Empty() {
578
return errors.Errorf("empty balances")
579
}
580
if !strings.HasPrefix(balanceResponse.Balances[0].Denom, "ibc/") {
581
return errors.Errorf("invalid ibc balance: %v", balanceResponse.Balances[0])
582
}
583
584
return nil
585
}),
586
),
587
)
588
env.Must(env.Exec("check ibc balance", steps, envtest.ExecRetry()))
589
}
590
591