Skip to content

Commit

Permalink
TrezorActions: Allow updating firmware.
Browse files Browse the repository at this point in the history
We are unable to update firmware from TrezorActions.js. This is believed
to be due to the size of the firmware and the limitations of this
electron child process. We send the firmware to the main process to push
to the device. The main process uses a temporary connection to trezor in
order to accomplish this.
  • Loading branch information
JoeGruffins committed Oct 31, 2020
1 parent 106226b commit 0309b33
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 78 deletions.
155 changes: 81 additions & 74 deletions app/actions/TrezorActions.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as wallet from "wallet";
import * as selectors from "selectors";
import fs from "fs";
import { hexToBytes, str2utf8hex } from "helpers";
import { rawToHex, hexToBytes, str2utf8hex } from "helpers";
import {
walletTxToBtcjsTx,
walletTxToRefTx,
Expand All @@ -22,6 +22,7 @@ import {
SIGNMESSAGE_SUCCESS
} from "./ControlActions";
import { getAmountFromTxInputs, getTxFromInputs } from "./TransactionActions";
import { ipcRenderer } from "electron";

const session = require("connect").default;
const { TRANSPORT_EVENT, UI, UI_EVENT, DEVICE_EVENT } = require("connect");
Expand All @@ -32,6 +33,8 @@ const AQUIRED = "acquired";
const NOBACKUP = "no-backup";
const TRANSPORT_ERROR = "transport-error";
const TRANSPORT_START = "transport-start";
const BOOTLOADER_MODE = "bootloader";
const DISCONNECTED_DURING_ACTION = "device disconnected during action";

let setListeners = false;

Expand All @@ -56,7 +59,7 @@ export const enableTrezor = () => (dispatch, getState) => {
connect()(dispatch, getState);
};

async function initTransport(dispatch, debug) {
export const initTransport = async (session, debug) => {
await session.init({
connectSrc: "https://localhost:8088/",
env: "web",
Expand All @@ -71,7 +74,7 @@ async function initTransport(dispatch, debug) {
.catch(err => {
throw err;
});
}
};

export const TRZ_CONNECT_ATTEMPT = "TRZ_CONNECT_ATTEMPT";
export const TRZ_CONNECT_FAILED = "TRZ_CONNECT_FAILED";
Expand All @@ -87,7 +90,7 @@ export const connect = () => async (dispatch, getState) => {
wallet.allowExternalRequest(EXTERNALREQUEST_TREZOR_BRIDGE);

const debug = getState().trezor.debug;
await initTransport(dispatch, debug)
await initTransport(session, debug)
.catch(error => {
dispatch({ error, type: TRZ_CONNECT_FAILED });
return;
Expand All @@ -111,20 +114,26 @@ export const TRZ_SELECTEDDEVICE_CHANGED = "TRZ_SELECTEDDEVICE_CHANGED";
export const TRZ_NOCONNECTEDDEVICE = "TRZ_NOCONNECTEDDEVICE";

function onChange (dispatch, getState, features) {
if (features == null) throw "no features on change";
if (features == null) return;
const currentDevice = selectors.trezorDevice(getState());
const device = features.id;
// No current device handle by connect.
if (!currentDevice) return;
let device = features.id;
if (features.mode == BOOTLOADER_MODE) {
device = BOOTLOADER_MODE;
}
if (device == currentDevice) return;
const deviceLabel = features.label;
dispatch({ deviceLabel, device, type: TRZ_SELECTEDDEVICE_CHANGED });
};

function onConnect (dispatch, getState, features) {
if (features == null) throw "no features on connect";
const device = features.id;
if (features == null) return;
let device = features.id;
const deviceLabel = features.label;
if (features.mode == BOOTLOADER_MODE) {
device = BOOTLOADER_MODE;
}
dispatch({ deviceLabel, device, type: TRZ_LOADDEVICE });
return device;
};
Expand Down Expand Up @@ -175,11 +184,12 @@ function setDeviceListeners(dispatch, getState) {
break;
};
});

session.on(DEVICE_EVENT, (event) => {
const type = event.type;
switch (type) {
case CHANGE:
if (event.payload.type == AQUIRED) {
if (event.payload && event.payload.type == AQUIRED) {
onChange(dispatch, getState, event.payload);
}
break;
Expand All @@ -191,6 +201,7 @@ function setDeviceListeners(dispatch, getState) {
break;
};
});

// TODO: Trezor needs some time to start listening for the responses to its
// requests. Find a better way than static sleeps to accomplish this.
session.on(UI_EVENT, async (event) => {
Expand All @@ -207,49 +218,49 @@ function setDeviceListeners(dispatch, getState) {
payload: false
});
};
dispatch({ type: TRZ_NOTBACKEDUP });
break;
dispatch({ type: TRZ_NOTBACKEDUP });
break;
case UI.REQUEST_PASSPHRASE: {
console.log("passphrase requested, waiting two seconds to respond");
await new Promise(r => setTimeout(r, 2000));
const passPhraseCallBack = (canceled, passphrase) => {
if (canceled) return;
session.uiResponse({
type: UI.RECEIVE_PASSPHRASE,
payload: {
value: passphrase,
save: true
}
});
};
dispatch({ passPhraseCallBack, type: TRZ_PASSPHRASE_REQUESTED });
break;
console.log("passphrase requested, waiting two seconds to respond");
await new Promise(r => setTimeout(r, 2000));
const passPhraseCallBack = (canceled, passphrase) => {
if (canceled) return;
session.uiResponse({
type: UI.RECEIVE_PASSPHRASE,
payload: {
value: passphrase,
save: true
}
});
};
dispatch({ passPhraseCallBack, type: TRZ_PASSPHRASE_REQUESTED });
break;
}
case UI.REQUEST_PIN: {
console.log("pin requested, waiting two seconds to respond");
await new Promise(r => setTimeout(r, 2000));
const pinCallBack = (canceled, pin) => {
if (canceled) return;
session.uiResponse({
type: UI.RECEIVE_PIN,
payload: pin
});
};
dispatch({ pinCallBack, type: TRZ_PIN_REQUESTED });
break;
console.log("pin requested, waiting two seconds to respond");
await new Promise(r => setTimeout(r, 2000));
const pinCallBack = (canceled, pin) => {
if (canceled) return;
session.uiResponse({
type: UI.RECEIVE_PIN,
payload: pin
});
};
dispatch({ pinCallBack, type: TRZ_PIN_REQUESTED });
break;
}
case UI.REQUEST_WORD: {
console.log("word requested, waiting two seconds to respond");
await new Promise(r => setTimeout(r, 2000));
const wordCallBack = (canceled, word) => {
if (canceled) return;
session.uiResponse({
type: UI.RECEIVE_WORD,
payload: word
});
};
dispatch({ wordCallBack, type: TRZ_WORD_REQUESTED });
break;
console.log("word requested, waiting two seconds to respond");
await new Promise(r => setTimeout(r, 2000));
const wordCallBack = (canceled, word) => {
if (canceled) return;
session.uiResponse({
type: UI.RECEIVE_WORD,
payload: word
});
};
dispatch({ wordCallBack, type: TRZ_WORD_REQUESTED });
break;
}
}
});
Expand All @@ -265,9 +276,8 @@ async function deviceRun(dispatch, getState, fn) {
if (noDevice(getState)) throw "no trezor device";
const handleError = (error) => {
const {
trezor: { waitingForPin, waitingForPassphrase, debug }
trezor: { waitingForPin, waitingForPassphrase }
} = getState();
debug && console.log("Handle error no deviceRun", error);
if (waitingForPin) dispatch({ error, type: TRZ_PIN_CANCELED });
if (waitingForPassphrase)
dispatch({ error, type: TRZ_PASSPHRASE_CANCELED });
Expand All @@ -280,21 +290,11 @@ async function deviceRun(dispatch, getState, fn) {
};

try {
const waitFor = async () => {
try {
return await fn();
} catch (err) {
// doesn't seem to be reachable by trezor interruptions, but might be
// caused by fn() failing in some other way (even though it's
// recommended not to do (non-trezor) lengthy operations inside fn())
throw handleError(err);
}
};
const res = await waitFor();
const res = await fn();
if (res && res.error) throw handleError(res.error);
return res;
} catch (outerErr) {
throw handleError(outerErr);
} catch (error) {
throw handleError(error);
}
};

Expand Down Expand Up @@ -767,13 +767,22 @@ export const TRZ_UPDATEFIRMWARE_ATTEMPT = "TRZ_UPDATEFIRMWARE_ATTEMPT";
export const TRZ_UPDATEFIRMWARE_FAILED = "TRZ_UPDATEFIRMWARE_FAILED";
export const TRZ_UPDATEFIRMWARE_SUCCESS = "TRZ_UPDATEFIRMWARE_SUCCESS";

// updateFirmware attempts to update the device's firmware. For some reason,
// possibly the size of the firmware, this action will not complete if called
// from here. We send the firmware to a higher place in the electron hiearchy
// to send it for us.
export const updateFirmware = (path) => async (dispatch, getState) => {
// Attempting to update the firmware while already updating will cause the
// trezor to lock up.
const {
trezor: { performingUpdate }
} = getState();
if (performingUpdate) {
console.log("already updating firmware");
return;
}

dispatch({ type: TRZ_UPDATEFIRMWARE_ATTEMPT });
// TODO: Allow firmware installation.
dispatch({ error: "firware install currently disabled", type: TRZ_UPDATEFIRMWARE_FAILED });
// Strange var to fool linter.
const abort = true;
if (abort) return;

if (noDevice(getState)) {
dispatch({
Expand All @@ -785,13 +794,11 @@ export const updateFirmware = (path) => async (dispatch, getState) => {

try {
const rawFirmware = fs.readFileSync(path);

await deviceRun(dispatch, getState, async () => {
const res = await session.firmwareUpdate({
binary: rawFirmware.buffer
});
return res.payload;
});
const hexFirmware = rawToHex(rawFirmware);
// Ask main.development.js to send the firmware for us.
const errorStr = await ipcRenderer.invoke("upload-firmware", hexFirmware);
// A successful upload will end with the user unplugging the device.
if (errorStr != DISCONNECTED_DURING_ACTION) throw errorStr;
dispatch({ type: TRZ_UPDATEFIRMWARE_SUCCESS });
} catch (error) {
dispatch({ error, type: TRZ_UPDATEFIRMWARE_FAILED });
Expand Down
8 changes: 7 additions & 1 deletion app/main.development.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ import {
startDcrlnd,
stopDcrlnd,
removeDcrlnd,
lnScbInfo
lnScbInfo,
updateTrezorFirmware
} from "./main_dev/ipc";
import {
initTemplate,
Expand Down Expand Up @@ -298,6 +299,11 @@ ipcMain.on("remove-wallet", (event, walletPath, testnet) => {
event.returnValue = removeWallet(testnet, walletPath);
});

ipcMain.handle("upload-firmware", async (event, firmware) => {
const res = await updateTrezorFirmware(firmware);
return res;
});

ipcMain.on("stop-daemon", (event) => {
event.returnValue = stopDaemon();
});
Expand Down
24 changes: 24 additions & 0 deletions app/main_dev/ipc.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
setDcrdRpcCredentials
} from "./launch";
import { MAINNET } from "constants";
import { initTransport } from "actions/TrezorActions.js";
import * as connect from "connect";

const logger = createLogger();
let watchingOnlyWallet;
Expand Down Expand Up @@ -141,6 +143,28 @@ export const removeWallet = (testnet, walletPath) => {
}
};

// updateTrezorFirmware attempts to make a temporary connection to a trezor
// device and update it with the supplied firmware. It returns an error string
// in case of error.
export const updateTrezorFirmware = async ( firmware ) => {
let session = connect.default;
try {
await initTransport(session, false);
const res = await session.firmwareUpdate({
binary: firmware
});
if (res.payload && res.payload.error) {
throw res.payload.error;
}
return null;
} catch (e) {
logger.log("error", "error uploading trezor firmware: " + e);
return e.toString();
} finally {
session = null;
}
};

export const startWallet = (
mainWindow,
daemonIsAdvanced,
Expand Down
16 changes: 13 additions & 3 deletions app/reducers/trezor.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,13 @@ export default function trezor(state = {}, action) {
case TRZ_WIPEDEVICE_ATTEMPT:
case TRZ_RECOVERDEVICE_ATTEMPT:
case TRZ_INITDEVICE_ATTEMPT:
case TRZ_UPDATEFIRMWARE_ATTEMPT:
return { ...state, performingOperation: true };
case TRZ_UPDATEFIRMWARE_ATTEMPT:
return {
...state,
performingOperation: true,
performingUpdate: true
};
case TRZ_CHANGELABEL_SUCCESS:
return {
...state,
Expand All @@ -203,11 +208,16 @@ export default function trezor(state = {}, action) {
case TRZ_RECOVERDEVICE_SUCCESS:
case TRZ_INITDEVICE_FAILED:
case TRZ_INITDEVICE_SUCCESS:
case TRZ_UPDATEFIRMWARE_FAILED:
case TRZ_UPDATEFIRMWARE_SUCCESS:
case TRZ_BACKUPDEVICE_FAILED:
case TRZ_BACKUPDEVICE_SUCCESS:
return { ...state, performingOperation: false };
case TRZ_UPDATEFIRMWARE_FAILED:
case TRZ_UPDATEFIRMWARE_SUCCESS:
return {
...state,
performingOperation: false,
performingUpdate: false
};
case CLOSEWALLET_SUCCESS:
return { ...state, enabled: false };
default:
Expand Down

0 comments on commit 0309b33

Please sign in to comment.