Skip to content

Commit 90a878f

Browse files
authored
Add multi-chain support for ledger key listing (#2984)
* Add multi-chain support for ledger key listing Extends ledger key query functionality to support P-Chain, X-Chain, C-Chain, and EVM-based chains instead of being limited to P-Chain only. Refactors getLedgerIndexInfo to query all available chain types for each ledger index. * address PR comments * fix e2e * fix e2e * fix e2e
1 parent ea46306 commit 90a878f

File tree

4 files changed

+190
-30
lines changed

4 files changed

+190
-30
lines changed

.github/workflows/e2e-test.yml

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ jobs:
5757
suite: "\\[Docker\\]"
5858
- os: macos-14
5959
suite: "\\[Public Subnet non SOV\\]"
60+
- os: macos-14
61+
suite: "\\[Key\\]"
6062
steps:
6163
- name: Checkout repository
6264
uses: actions/checkout@v4
@@ -90,14 +92,6 @@ jobs:
9092
npm install -g ts-node
9193
npm install -g tsx
9294
93-
- name: Install Docker on MacOS
94-
if: ${{ (matrix.os == 'macos-14') && (matrix.suite == '\[Public Subnet non SOV\]') }}
95-
run: |
96-
brew install docker
97-
brew install colima
98-
colima start --vm-type vz
99-
sudo ln -s ~/.colima/default/docker.sock /var/run/docker.sock
100-
10195
- name: Generate SSH token for E2E tests
10296
run: |
10397
mkdir -p ~/.ssh && ssh-keygen -b 2048 -t rsa -f ~/.ssh/runner-avalanche-cli-keypair -q -N ""

cmd/keycmd/list.go

Lines changed: 76 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -301,11 +301,6 @@ func listKeys(*cobra.Command, []string) error {
301301
cchain = false
302302
}
303303
queryLedger := len(ledgerIndices) > 0
304-
if queryLedger {
305-
pchain = true
306-
cchain = false
307-
xchain = false
308-
}
309304
if sdkUtils.Belongs(tokenAddresses, "Native") || sdkUtils.Belongs(tokenAddresses, "native") {
310305
showNativeToken = true
311306
}
@@ -319,7 +314,7 @@ func listKeys(*cobra.Command, []string) error {
319314
for _, index := range ledgerIndices {
320315
ledgerIndicesU32 = append(ledgerIndicesU32, uint32(index))
321316
}
322-
addrInfos, err = getLedgerIndicesInfo(clients.p, ledgerIndicesU32, networks)
317+
addrInfos, err = getLedgerIndicesInfo(clients, networks, ledgerIndicesU32)
323318
if err != nil {
324319
return err
325320
}
@@ -423,9 +418,9 @@ func getStoredKeyInfo(
423418
}
424419

425420
func getLedgerIndicesInfo(
426-
pClients map[models.Network]platformvm.Client,
427-
ledgerIndices []uint32,
421+
clients *Clients,
428422
networks []models.Network,
423+
ledgerIndices []uint32,
429424
) ([]addressInfo, error) {
430425
ledgerDevice, err := ledger.New()
431426
if err != nil {
@@ -441,7 +436,8 @@ func getLedgerIndicesInfo(
441436
addrInfos := []addressInfo{}
442437
for i, index := range ledgerIndices {
443438
addr := pubKeys[i].Address()
444-
ledgerAddrInfos, err := getLedgerIndexInfo(pClients, index, networks, addr)
439+
evmAddr := pubKeys[i].EthAddress()
440+
ledgerAddrInfos, err := getLedgerIndexInfo(clients, networks, index, addr, evmAddr)
445441
if err != nil {
446442
return []addressInfo{}, err
447443
}
@@ -451,28 +447,87 @@ func getLedgerIndicesInfo(
451447
}
452448

453449
func getLedgerIndexInfo(
454-
pClients map[models.Network]platformvm.Client,
455-
index uint32,
450+
clients *Clients,
456451
networks []models.Network,
452+
index uint32,
457453
addr ids.ShortID,
454+
evmAddr common.Address,
458455
) ([]addressInfo, error) {
459456
addrInfos := []addressInfo{}
460457
for _, network := range networks {
461458
pChainAddr, err := address.Format("P", key.GetHRP(network.ID), addr[:])
462459
if err != nil {
463460
return nil, err
464461
}
465-
addrInfo, err := getPChainAddrInfo(
466-
pClients,
467-
network,
468-
pChainAddr,
469-
"ledger",
470-
fmt.Sprintf("index %d", index),
471-
)
472-
if err != nil {
473-
return nil, err
462+
if _, ok := clients.p[network]; ok {
463+
addrInfo, err := getPChainAddrInfo(
464+
clients.p,
465+
network,
466+
pChainAddr,
467+
"ledger",
468+
fmt.Sprintf("index %d", index),
469+
)
470+
if err != nil {
471+
return nil, err
472+
}
473+
addrInfos = append(addrInfos, addrInfo)
474+
}
475+
if _, ok := clients.x[network]; ok {
476+
xChainAddr, err := address.Format("X", key.GetHRP(network.ID), addr[:])
477+
if err != nil {
478+
return nil, err
479+
}
480+
addrInfo, err := getXChainAddrInfo(
481+
clients.x,
482+
network,
483+
xChainAddr,
484+
"ledger",
485+
fmt.Sprintf("index %d", index),
486+
)
487+
if err != nil {
488+
return nil, err
489+
}
490+
addrInfos = append(addrInfos, addrInfo)
491+
}
492+
if _, ok := clients.c[network]; ok {
493+
addrInfosAux, err := getEvmBasedChainAddrInfo(
494+
"C-Chain",
495+
"AVAX",
496+
clients.c[network],
497+
clients.cEndpoint[network],
498+
network,
499+
evmAddr.Hex(),
500+
"ledger",
501+
fmt.Sprintf("index %d", index),
502+
)
503+
if err != nil {
504+
return nil, err
505+
}
506+
addrInfos = append(addrInfos, addrInfosAux...)
507+
}
508+
if _, ok := clients.evm[network]; ok {
509+
for subnetName := range clients.evm[network] {
510+
addrInfo, err := getEvmBasedChainAddrInfo(
511+
subnetName,
512+
subnetToken,
513+
clients.evm[network][subnetName],
514+
clients.evmEndpoint[network][subnetName],
515+
network,
516+
evmAddr.Hex(),
517+
"ledger",
518+
fmt.Sprintf("index %d", index),
519+
)
520+
if err != nil {
521+
ux.Logger.RedXToUser(
522+
"failure obtaining info for blockchain %s on url %s",
523+
subnetName,
524+
clients.blockchainRPC[network][subnetName],
525+
)
526+
continue
527+
}
528+
addrInfos = append(addrInfos, addrInfo...)
529+
}
474530
}
475-
addrInfos = append(addrInfos, addrInfo)
476531
}
477532
return addrInfos, nil
478533
}

tests/e2e/commands/key.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,28 @@ func ExportKeyToFile(keyName string, outputPath string) (string, error) {
158158
return string(out), err
159159
}
160160

161+
/* #nosec G204 */
162+
func ListLedgerKeys(network string, ledgerIndices []uint, subnets string, chains string) (string, error) {
163+
args := []string{KeyCmd, "list", "--" + network, "--" + constants.SkipUpdateFlag}
164+
for _, idx := range ledgerIndices {
165+
args = append(args, "--ledger", fmt.Sprintf("%d", idx))
166+
}
167+
if subnets != "" {
168+
args = append(args, "--subnets", subnets)
169+
}
170+
if chains != "" {
171+
args = append(args, "--blockchains", chains)
172+
}
173+
cmd := exec.Command(CLIBinary, args...)
174+
out, err := cmd.CombinedOutput()
175+
if err != nil {
176+
fmt.Println(cmd.String())
177+
fmt.Println(string(out))
178+
utils.PrintStdErr(err)
179+
}
180+
return string(out), err
181+
}
182+
161183
/* #nosec G204 */
162184
func KeyTransferSend(
163185
args []string,

tests/e2e/testcases/key/list/suite.go

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package list
44

55
import (
66
"fmt"
7+
"os"
78

89
"github.com/ava-labs/avalanche-cli/tests/e2e/commands"
910
"github.com/ava-labs/avalanche-cli/tests/e2e/utils"
@@ -12,7 +13,17 @@ import (
1213
)
1314

1415
const (
15-
keyName = "e2eKey"
16+
keyName = "e2eKey"
17+
ledger1Seed = "ledger1"
18+
ledger2Seed = "ledger2"
19+
20+
// Expected addresses for ledger1 seed (deterministic)
21+
ledger1Index0PChain = "P-custom1jkjatcy2vxfx3st0kft8p0jup6k4ucxugtzhlc"
22+
ledger1Index0XChain = "X-custom1jkjatcy2vxfx3st0kft8p0jup6k4ucxugtzhlc"
23+
ledger1Index0CChain = "0x90f207c2D78E871CFd23071f0545e72233B33767"
24+
ledger1Index1PChain = "P-custom13wzadkyffwlk0a936u5y089dxtw3kx5fyaewc9"
25+
ledger1Index1XChain = "X-custom13wzadkyffwlk0a936u5y089dxtw3kx5fyaewc9"
26+
ledger1Index1CChain = "0x6235F11635BfDe6319E836e487A47D4686c4752E"
1627
)
1728

1829
var _ = ginkgo.Describe("[Key] list", func() {
@@ -58,4 +69,82 @@ var _ = ginkgo.Describe("[Key] list", func() {
5869
gomega.Expect(output).Should(gomega.MatchRegexp(regex4))
5970
gomega.Expect(output).Should(gomega.ContainSubstring(keyName))
6071
})
72+
73+
ginkgo.It("can list ledger addresses for multiple indices and chains", func() {
74+
gomega.Expect(os.Getenv("LEDGER_SIM")).Should(gomega.Equal("true"), "ledger list test not designed for real ledgers: please set env var LEDGER_SIM to true")
75+
76+
// Start ledger simulator once for all tests
77+
interactionEndCh, ledgerSimEndCh := utils.StartLedgerSim(0, ledger1Seed, false)
78+
79+
// Test 1: List all chains (P-Chain, C-Chain, X-Chain) for multiple indices
80+
output, err := commands.ListLedgerKeys("local", []uint{0, 1}, "p,c,x", "")
81+
gomega.Expect(err).Should(gomega.BeNil())
82+
83+
// Verify output contains expected headers (case-insensitive check)
84+
gomega.Expect(output).Should(gomega.ContainSubstring("KIND"))
85+
gomega.Expect(output).Should(gomega.ContainSubstring("NAME"))
86+
gomega.Expect(output).Should(gomega.ContainSubstring("SUBNET"))
87+
gomega.Expect(output).Should(gomega.ContainSubstring("ADDRESS"))
88+
gomega.Expect(output).Should(gomega.ContainSubstring("TOKEN"))
89+
gomega.Expect(output).Should(gomega.ContainSubstring("BALANCE"))
90+
91+
// Verify ledger indices are shown
92+
gomega.Expect(output).Should(gomega.ContainSubstring("index 0"))
93+
gomega.Expect(output).Should(gomega.ContainSubstring("index 1"))
94+
95+
// Verify all chains are listed
96+
gomega.Expect(output).Should(gomega.ContainSubstring("P-Chain"))
97+
gomega.Expect(output).Should(gomega.ContainSubstring("C-Chain"))
98+
gomega.Expect(output).Should(gomega.ContainSubstring("X-Chain"))
99+
100+
// Verify kind is "ledger"
101+
gomega.Expect(output).Should(gomega.ContainSubstring("ledger"))
102+
103+
// Verify specific addresses for index 0 (deterministic with ledger1 seed)
104+
gomega.Expect(output).Should(gomega.ContainSubstring(ledger1Index0PChain))
105+
gomega.Expect(output).Should(gomega.ContainSubstring(ledger1Index0XChain))
106+
gomega.Expect(output).Should(gomega.ContainSubstring(ledger1Index0CChain))
107+
108+
// Verify specific addresses for index 1 (deterministic with ledger1 seed)
109+
gomega.Expect(output).Should(gomega.ContainSubstring(ledger1Index1PChain))
110+
gomega.Expect(output).Should(gomega.ContainSubstring(ledger1Index1XChain))
111+
gomega.Expect(output).Should(gomega.ContainSubstring(ledger1Index1CChain))
112+
113+
// Test 2: List only P-Chain addresses
114+
output, err = commands.ListLedgerKeys("local", []uint{0}, "p", "")
115+
gomega.Expect(err).Should(gomega.BeNil())
116+
gomega.Expect(output).Should(gomega.ContainSubstring("P-Chain"))
117+
gomega.Expect(output).Should(gomega.ContainSubstring("index 0"))
118+
gomega.Expect(output).Should(gomega.ContainSubstring(ledger1Index0PChain))
119+
gomega.Expect(output).ShouldNot(gomega.ContainSubstring("C-Chain"))
120+
gomega.Expect(output).ShouldNot(gomega.ContainSubstring("X-Chain"))
121+
gomega.Expect(output).ShouldNot(gomega.ContainSubstring(ledger1Index0CChain))
122+
gomega.Expect(output).ShouldNot(gomega.ContainSubstring(ledger1Index0XChain))
123+
124+
// Test 3: List only C-Chain addresses
125+
output, err = commands.ListLedgerKeys("local", []uint{0}, "c", "")
126+
gomega.Expect(err).Should(gomega.BeNil())
127+
gomega.Expect(output).Should(gomega.ContainSubstring("C-Chain"))
128+
gomega.Expect(output).Should(gomega.ContainSubstring("index 0"))
129+
gomega.Expect(output).Should(gomega.ContainSubstring(ledger1Index0CChain))
130+
gomega.Expect(output).ShouldNot(gomega.ContainSubstring("P-Chain"))
131+
gomega.Expect(output).ShouldNot(gomega.ContainSubstring("X-Chain"))
132+
gomega.Expect(output).ShouldNot(gomega.ContainSubstring(ledger1Index0PChain))
133+
gomega.Expect(output).ShouldNot(gomega.ContainSubstring(ledger1Index0XChain))
134+
135+
// Test 4: List only X-Chain addresses
136+
output, err = commands.ListLedgerKeys("local", []uint{0}, "x", "")
137+
gomega.Expect(err).Should(gomega.BeNil())
138+
gomega.Expect(output).Should(gomega.ContainSubstring("X-Chain"))
139+
gomega.Expect(output).Should(gomega.ContainSubstring("index 0"))
140+
gomega.Expect(output).Should(gomega.ContainSubstring(ledger1Index0XChain))
141+
gomega.Expect(output).ShouldNot(gomega.ContainSubstring("P-Chain"))
142+
gomega.Expect(output).ShouldNot(gomega.ContainSubstring("C-Chain"))
143+
gomega.Expect(output).ShouldNot(gomega.ContainSubstring(ledger1Index0PChain))
144+
gomega.Expect(output).ShouldNot(gomega.ContainSubstring(ledger1Index0CChain))
145+
146+
// Close ledger simulator
147+
close(interactionEndCh)
148+
<-ledgerSimEndCh
149+
})
61150
})

0 commit comments

Comments
 (0)