Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TrezorActions: Allow updating firmware. #2816

Merged
merged 3 commits into from
Nov 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 42 additions & 36 deletions app/actions/TrezorActions.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as wallet from "wallet";
import * as selectors from "selectors";
import fs from "fs";
import { hexToBytes, str2utf8hex } from "helpers";
import {
walletTxToBtcjsTx,
Expand All @@ -22,6 +21,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 +32,7 @@ const AQUIRED = "acquired";
const NOBACKUP = "no-backup";
const TRANSPORT_ERROR = "transport-error";
const TRANSPORT_START = "transport-start";
const BOOTLOADER_MODE = "bootloader";

let setListeners = false;

Expand All @@ -56,7 +57,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 +72,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 +88,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 @@ -113,24 +114,30 @@ export const TRZ_NOCONNECTEDDEVICE = "TRZ_NOCONNECTEDDEVICE";
function onChange(dispatch, getState, features) {
if (features == null) throw "no features on change";
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;
let device = features.id;
const deviceLabel = features.label;
if (features.mode == BOOTLOADER_MODE) {
device = BOOTLOADER_MODE;
}
dispatch({ deviceLabel, device, type: TRZ_LOADDEVICE });
return device;
};

function onDisconnect(dispatch, getState, features) {
if (features == null) throw "no features on change";
if (features == null) throw "no features on disconnect";
const currentDevice = selectors.trezorDevice(getState());
const device = features.id;
// If this is not the device we presume is current, ignore.
Expand Down Expand Up @@ -175,11 +182,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 +199,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 Down Expand Up @@ -274,9 +283,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 @@ -289,21 +297,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 @@ -776,13 +774,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, device }
} = 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 @@ -793,14 +800,13 @@ 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;
});
if (device != BOOTLOADER_MODE) throw "device must be in bootloader mode";
// Ask main.development.js to send the firmware for us.
const { error, started } = await ipcRenderer.invoke("upload-firmware", path);
// If the updated started, the device must be disconnected before further
// use.
if (started) alertNoConnectedDevice()(dispatch);
if (error) throw error;
dispatch({ type: TRZ_UPDATEFIRMWARE_SUCCESS });
} catch (error) {
dispatch({ error, type: TRZ_UPDATEFIRMWARE_FAILED });
Expand Down
7 changes: 6 additions & 1 deletion app/components/views/TrezorPage/ConfigSections.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ class TrezorConfigSections extends React.Component {
this.props.backupDevice();
}

isUpdating() {
return this.props.isPerformingUpdate;
}

render() {
const {
onTogglePinProtection,
Expand All @@ -57,6 +61,7 @@ class TrezorConfigSections extends React.Component {
onInitDevice,
onBackupDevice,
onUpdateFirmware,
isUpdating,
loading
} = this;

Expand All @@ -77,7 +82,7 @@ class TrezorConfigSections extends React.Component {
{...{ onWipeDevice, onRecoverDevice, onInitDevice, onBackupDevice, loading }}
/>

<FirmwareUpdate {...{ onUpdateFirmware, loading }} />
<FirmwareUpdate {...{ onUpdateFirmware, loading, isUpdating }} />
</>
);
}
Expand Down
6 changes: 3 additions & 3 deletions app/components/views/TrezorPage/FirmwareUpdate.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class FirmwareUpdate extends React.Component {
</>
);

const { loading } = this.props;
const { loading, isUpdating } = this.props;

return (
<VerticalAccordion
Expand All @@ -54,8 +54,8 @@ class FirmwareUpdate extends React.Component {

<DangerButton
onClick={this.onUpdateFirmware}
loading={loading}
disabled={loading}>
loading={loading || isUpdating()}
disabled={loading || isUpdating()}>
<T id="trezorPage.updateFirmwareBtn" m="Update Firmware" />
</DangerButton>
</VerticalAccordion>
Expand Down
1 change: 1 addition & 0 deletions app/connectors/trezor.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as trza from "../actions/TrezorActions";

const mapStateToProps = selectorMap({
isTrezor: sel.isTrezor,
isPerformingUpdate: sel.isPerformingTrezorUpdate,
waitingForPin: sel.trezorWaitingForPin,
waitingForPassPhrase: sel.trezorWaitingForPassPhrase,
waitingForWord: sel.trezorWaitingForWord,
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
46 changes: 46 additions & 0 deletions app/main_dev/ipc.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import {
setDcrdRpcCredentials
} from "./launch";
import { MAINNET } from "constants";
import { initTransport } from "actions/TrezorActions.js";
import * as connect from "connect";
import { rawToHex } from "helpers";


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

// updateTrezorFirmware attempts to make a temporary connection to a trezor
// device and update it with the firmware at path. It returns an error string
// in case of error and whether the update process was started at all.
export const updateTrezorFirmware = async ( firmwarePath ) => {
let started = false;
let completed = false;
const rawFirmware = fs.readFileSync(firmwarePath);
const hexFirmware = rawToHex(rawFirmware);
let session = connect.default;
try {
await initTransport(session, false);
session.on(connect.UI_EVENT, (event) => {
if (event.type == connect.UI.FIRMWARE_PROGRESS) {
logger.log("info", "Trezor update progress: " + event.payload.progress+"%");
// Ignore disconnect errors if completed.
if (event.payload.progress == 100) {
completed = true;
}
}
});
started = true;
const res = await session.firmwareUpdate({
binary: hexFirmware
});
if (res.payload) {
if (res.payload.error) {
throw res.payload.error;
}
if (!res.payload.success) {
throw res.payload.code;
}
}
return { error: null, started };
} catch (e) {
if (completed) return { error: null, started };
logger.log("error", "error uploading trezor firmware: " + e);
return { error: e.toString(), started };
} 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
1 change: 1 addition & 0 deletions app/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1457,6 +1457,7 @@ export const autobuyerRunningModalVisible = get([
]);

export const isTrezor = get(["trezor", "enabled"]);
export const isPerformingTrezorUpdate = get(["trezor", "performingUpdate"]);

export const isSignMessageDisabled = and(isWatchingOnly, not(isTrezor));
export const isChangePassPhraseDisabled = isWatchingOnly;
Expand Down