Skip to content

Commit 0eb71a2

Browse files
authored
refresh Ledger connection before each instruction (#5444)
some errors cause the CLI to disconnect from the device. this can make it impossible to continue running commands until a new connection is made handles 'disconnect' events on the app transport by closing the connection refreshes the connection before each instruction: checks if the app is undefined and establishes a new connection refactors 'tryInstruction' to take a function instead of a promise
1 parent 8cc37be commit 0eb71a2

File tree

1 file changed

+36
-41
lines changed

1 file changed

+36
-41
lines changed

ironfish-cli/src/utils/ledger.ts

Lines changed: 36 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import {
55
ACCOUNT_SCHEMA_VERSION,
66
AccountImport,
7+
Assert,
78
createRootLogger,
89
CurrencyUtils,
910
Logger,
@@ -45,9 +46,12 @@ export class LedgerDkg {
4546
this.logger = logger ? logger : createRootLogger()
4647
}
4748

48-
tryInstruction = async <T>(promise: Promise<T>) => {
49+
tryInstruction = async <T>(instruction: (app: IronfishDkgApp) => Promise<T>) => {
50+
await this.refreshConnection()
51+
Assert.isNotUndefined(this.app, 'Unable to establish connection with Ledger device')
52+
4953
try {
50-
return await promise
54+
return await instruction(this.app)
5155
} catch (error: unknown) {
5256
if (isResponseError(error)) {
5357
this.logger.debug(`Ledger ResponseError returnCode: ${error.returnCode.toString(16)}`)
@@ -69,6 +73,11 @@ export class LedgerDkg {
6973
connect = async () => {
7074
const transport = await TransportNodeHid.create(3000)
7175

76+
transport.on('disconnect', async () => {
77+
await transport.close()
78+
this.app = undefined
79+
})
80+
7281
if (transport.deviceModel) {
7382
this.logger.debug(`${transport.deviceModel.productName} found.`)
7483
}
@@ -83,15 +92,17 @@ export class LedgerDkg {
8392
return { app, PATH: this.PATH }
8493
}
8594

86-
dkgGetIdentity = async (index: number): Promise<Buffer> => {
95+
private refreshConnection = async () => {
8796
if (!this.app) {
88-
throw new Error('Connect to Ledger first')
97+
await this.connect()
8998
}
99+
}
90100

101+
dkgGetIdentity = async (index: number): Promise<Buffer> => {
91102
this.logger.log('Retrieving identity from ledger device.')
92103

93-
const response: ResponseIdentity = await this.tryInstruction(
94-
this.app.dkgGetIdentity(index, false),
104+
const response: ResponseIdentity = await this.tryInstruction((app) =>
105+
app.dkgGetIdentity(index, false),
95106
)
96107

97108
return response.identity
@@ -102,28 +113,20 @@ export class LedgerDkg {
102113
identities: string[],
103114
minSigners: number,
104115
): Promise<ResponseDkgRound1> => {
105-
if (!this.app) {
106-
throw new Error('Connect to Ledger first')
107-
}
108-
109116
this.logger.log('Please approve the request on your ledger device.')
110117

111-
return this.tryInstruction(this.app.dkgRound1(index, identities, minSigners))
118+
return this.tryInstruction((app) => app.dkgRound1(index, identities, minSigners))
112119
}
113120

114121
dkgRound2 = async (
115122
index: number,
116123
round1PublicPackages: string[],
117124
round1SecretPackage: string,
118125
): Promise<ResponseDkgRound2> => {
119-
if (!this.app) {
120-
throw new Error('Connect to Ledger first')
121-
}
122-
123126
this.logger.log('Please approve the request on your ledger device.')
124127

125-
return this.tryInstruction(
126-
this.app.dkgRound2(index, round1PublicPackages, round1SecretPackage),
128+
return this.tryInstruction((app) =>
129+
app.dkgRound2(index, round1PublicPackages, round1SecretPackage),
127130
)
128131
}
129132

@@ -135,14 +138,10 @@ export class LedgerDkg {
135138
round2SecretPackage: string,
136139
gskBytes: string[],
137140
): Promise<void> => {
138-
if (!this.app) {
139-
throw new Error('Connect to Ledger first')
140-
}
141-
142141
this.logger.log('Please approve the request on your ledger device.')
143142

144-
return this.tryInstruction(
145-
this.app.dkgRound3Min(
143+
return this.tryInstruction((app) =>
144+
app.dkgRound3Min(
146145
index,
147146
participants,
148147
round1PublicPackages,
@@ -160,28 +159,24 @@ export class LedgerDkg {
160159
outgoingViewKey: string
161160
proofAuthorizingKey: string
162161
}> => {
163-
if (!this.app) {
164-
throw new Error('Connect to Ledger first')
165-
}
166-
167-
const responseAddress: KeyResponse = await this.tryInstruction(
168-
this.app.dkgRetrieveKeys(IronfishKeys.PublicAddress),
162+
const responseAddress: KeyResponse = await this.tryInstruction((app) =>
163+
app.dkgRetrieveKeys(IronfishKeys.PublicAddress),
169164
)
170165

171166
if (!isResponseAddress(responseAddress)) {
172167
throw new Error(`No public address returned.`)
173168
}
174169

175-
const responseViewKey = await this.tryInstruction(
176-
this.app.dkgRetrieveKeys(IronfishKeys.ViewKey),
170+
const responseViewKey = await this.tryInstruction((app) =>
171+
app.dkgRetrieveKeys(IronfishKeys.ViewKey),
177172
)
178173

179174
if (!isResponseViewKey(responseViewKey)) {
180175
throw new Error(`No view key returned.`)
181176
}
182177

183-
const responsePGK: KeyResponse = await this.tryInstruction(
184-
this.app.dkgRetrieveKeys(IronfishKeys.ProofGenerationKey),
178+
const responsePGK: KeyResponse = await this.tryInstruction((app) =>
179+
app.dkgRetrieveKeys(IronfishKeys.ProofGenerationKey),
185180
)
186181

187182
if (!isResponseProofGenKey(responsePGK)) {
@@ -202,7 +197,7 @@ export class LedgerDkg {
202197
throw new Error('Connect to Ledger first')
203198
}
204199

205-
const response = await this.tryInstruction(this.app.dkgGetPublicPackage())
200+
const response = await this.tryInstruction((app) => app.dkgGetPublicPackage())
206201

207202
return response.publicPackage
208203
}
@@ -216,7 +211,7 @@ export class LedgerDkg {
216211
'Please review and approve the outputs of this transaction on your ledger device.',
217212
)
218213

219-
const { hash } = await this.tryInstruction(this.app.reviewTransaction(transaction))
214+
const { hash } = await this.tryInstruction((app) => app.reviewTransaction(transaction))
220215

221216
return hash
222217
}
@@ -226,8 +221,8 @@ export class LedgerDkg {
226221
throw new Error('Connect to Ledger first')
227222
}
228223

229-
const { commitments } = await this.tryInstruction(
230-
this.app.dkgGetCommitments(transactionHash),
224+
const { commitments } = await this.tryInstruction((app) =>
225+
app.dkgGetCommitments(transactionHash),
231226
)
232227

233228
return commitments
@@ -242,8 +237,8 @@ export class LedgerDkg {
242237
throw new Error('Connect to Ledger first')
243238
}
244239

245-
const { signature } = await this.tryInstruction(
246-
this.app.dkgSign(randomness, frostSigningPackage, transactionHash),
240+
const { signature } = await this.tryInstruction((app) =>
241+
app.dkgSign(randomness, frostSigningPackage, transactionHash),
247242
)
248243

249244
return signature
@@ -256,7 +251,7 @@ export class LedgerDkg {
256251

257252
this.logger.log('Please approve the request on your ledger device.')
258253

259-
const { encryptedKeys } = await this.tryInstruction(this.app.dkgBackupKeys())
254+
const { encryptedKeys } = await this.tryInstruction((app) => app.dkgBackupKeys())
260255

261256
return encryptedKeys
262257
}
@@ -268,7 +263,7 @@ export class LedgerDkg {
268263

269264
this.logger.log('Please approve the request on your ledger device.')
270265

271-
await this.tryInstruction(this.app.dkgRestoreKeys(encryptedKeys))
266+
await this.tryInstruction((app) => app.dkgRestoreKeys(encryptedKeys))
272267
}
273268
}
274269

0 commit comments

Comments
 (0)