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

Protobuf types improvement #17608

Draft
wants to merge 3 commits into
base: develop
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,18 @@ export type CardanoSignTransactionParams = {
signingMode: PROTO.CardanoTxSigningMode;
inputsWithPath: InputWithPath[];
outputsWithData: OutputWithData[];
fee: PROTO.UintType;
ttl?: PROTO.UintType;
fee: PROTO.CardanoSignTxInit['fee'];
ttl?: PROTO.CardanoSignTxInit['ttl'];
certificatesWithPoolOwnersAndRelays: CertificateWithPoolOwnersAndRelays[];
withdrawals: PROTO.CardanoTxWithdrawal[];
mint: AssetGroupWithTokens[];
auxiliaryData?: PROTO.CardanoTxAuxiliaryData;
validityIntervalStart?: PROTO.UintType;
validityIntervalStart?: PROTO.CardanoSignTxInit['validity_interval_start'];
scriptDataHash?: string;
collateralInputsWithPath: CollateralInputWithPath[];
requiredSigners: PROTO.CardanoTxRequiredSigner[];
collateralReturnWithData?: OutputWithData;
totalCollateral?: PROTO.UintType;
totalCollateral?: PROTO.CardanoSignTxInit['total_collateral'];
referenceInputs: PROTO.CardanoTxReferenceInput[];
protocolMagic: number;
networkId: number;
Expand Down
54 changes: 23 additions & 31 deletions packages/connect/src/device/DeviceCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,16 @@ import { initLog } from '../utils/debug';
import * as hdnodeUtils from '../utils/hdnodeUtils';
import { getScriptType, getSerializedPath, isTaprootPath, toHardened } from '../utils/pathUtils';

type MessageType = Messages.MessageType;
type MessageKey = keyof MessageType;
type TypedPayload<T extends MessageKey> = {
type: T;
message: MessageType[T];
};
type TypedCallResponseMap = {
[K in keyof MessageType]: TypedPayload<K>;
};
type DefaultPayloadMessage = TypedCallResponseMap[keyof MessageType];
type TypedCall = Messages.TypedCall;

export type { TypedCall };

const logger = initLog('DeviceCommands');

const assertType = (res: DefaultPayloadMessage, resType: MessageKey | MessageKey[]) => {
const assertType = (
res: Messages.MessageResponse,
resType: Messages.MessageKey | Messages.MessageKey[],
) => {
const splitResTypes = Array.isArray(resType) ? resType : resType.split('|');
if (!splitResTypes.includes(res.type)) {
throw ERRORS.TypedError(
Expand Down Expand Up @@ -295,10 +291,10 @@ export class DeviceCommands {
}

// Sends an async message to the opened device.
private async call(
type: MessageKey,
msg: DefaultPayloadMessage['message'] = {},
): Promise<DefaultPayloadMessage> {
private async call<T extends Messages.MessageKey>(
type: T,
msg: Messages.MessagePayload<T>,
): Promise<Messages.MessageResponse> {
logger.debug('Sending', type, filterForLog(type, msg));

this.callPromise = this.transport.call({
Expand Down Expand Up @@ -327,32 +323,30 @@ export class DeviceCommands {
filterForLog(res.payload.type, res.payload.message),
);

// TODO: https://github.com/trezor/trezor-suite/issues/5301
// @ts-expect-error
return res.payload;
}

typedCall<T extends MessageKey, R extends MessageKey[]>(
typedCall<T extends Messages.MessageKey, R extends Messages.MessageKey[]>(
type: T,
resType: R,
msg?: MessageType[T],
): Promise<TypedCallResponseMap[R[number]]>;
typedCall<T extends MessageKey, R extends MessageKey>(
msg?: Messages.MessagePayload<T>,
): Promise<Messages.MessageResponse<R[number]>>;
typedCall<T extends Messages.MessageKey, R extends Messages.MessageKey>(
type: T,
resType: R,
msg?: MessageType[T],
): Promise<TypedPayload<R>>;
msg?: Messages.MessagePayload<T>,
): Promise<Messages.MessageResponse<R>>;
async typedCall(
type: MessageKey,
resType: MessageKey | MessageKey[],
msg?: DefaultPayloadMessage['message'],
type: Messages.MessageKey,
resType: Messages.MessageKey | Messages.MessageKey[],
msg: Messages.MessagePayload = {},
) {
if (this.disposed) {
throw ERRORS.TypedError('Runtime', 'typedCall: DeviceCommands already disposed');
}
// Assert message type
// msg is allowed to be undefined for some calls, in that case the schema is an empty object
Assert(Messages.MessageType.properties[type], msg ?? {});
Assert(Messages.MessageType.properties[type], msg);
const response = await this._commonCall(type, msg);
try {
assertType(response, resType);
Expand Down Expand Up @@ -380,7 +374,7 @@ export class DeviceCommands {
return response;
}

async _commonCall(type: MessageKey, msg?: DefaultPayloadMessage['message']) {
async _commonCall<T extends Messages.MessageKey>(type: T, msg: Messages.MessagePayload<T>) {
if (this.disposed) {
throw ERRORS.TypedError('Runtime', 'typedCall: DeviceCommands already disposed');
}
Expand All @@ -389,7 +383,7 @@ export class DeviceCommands {
return this._filterCommonTypes(resp);
}

_filterCommonTypes(res: DefaultPayloadMessage): Promise<DefaultPayloadMessage> {
_filterCommonTypes(res: Messages.MessageResponse): Promise<Messages.MessageResponse> {
this.device.clearCancelableAction();

if (res.type === 'Failure') {
Expand Down Expand Up @@ -589,5 +583,3 @@ export class DeviceCommands {
}
}
}

export type TypedCall = DeviceCommands['typedCall'];
7 changes: 5 additions & 2 deletions packages/connect/src/device/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ export const cancelPrompt = (device: Device, expectResponse = true) => {
return expectResponse ? device.transport.call(cancelArgs) : device.transport.send(cancelArgs);
};

const extractMessage = (payload?: Messages.MessageResponse) =>
(payload && 'message' in payload.message && payload.message.message) || '';

const prompt = <E extends PromptEvents>(event: E, { device, ...rest }: DeviceEventArgs<E>) =>
// return non nullable first arg of PromptCallback<E>
new Promise<PromptReturnType<E>>(resolve => {
Expand All @@ -43,8 +46,8 @@ const prompt = <E extends PromptEvents>(event: E, { device, ...rest }: DeviceEve
response.success
? resolve({
success: false,
error: error || (response.payload?.message.message as string),
message: response.payload?.message.message as string,
error: error || extractMessage(response.payload),
message: extractMessage(response.payload),
isTransportError: !response.success,
})
: resolve({
Expand Down
29 changes: 20 additions & 9 deletions packages/protobuf/scripts/protobuf-patches/MessageType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,24 @@

export type MessageKey = keyof MessageType;

export type MessageResponse<T extends MessageKey> = {
type: T;
message: MessageType[T];
};
export type MessagePayload<T extends MessageKey = MessageKey> = MessageType[T];

export type MessageResponse<T extends MessageKey = MessageKey> = T extends any
? {
type: T;
message: MessagePayload<T>;
}
: never;

export type TypedCall = <T extends MessageKey, R extends MessageKey>(
type: T,
resType: R,
message?: MessageType[T],
) => Promise<MessageResponse<R>>;
export type TypedCall = {
<T extends MessageKey, R extends MessageKey[]>(
type: T,
resType: R,
message?: MessagePayload<T>,
): Promise<MessageResponse<R[number]>>;
<T extends MessageKey, R extends MessageKey>(
type: T,
resType: R,
message?: MessagePayload<T>,
): Promise<MessageResponse<R>>;
};
4 changes: 3 additions & 1 deletion packages/protobuf/scripts/protobuf-patches/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,9 @@ export const TYPE_PATCH = {
};

export const readPatch = (file: string) =>
fs.readFileSync(path.join(__dirname, file), 'utf8').replace(/^\/\/ @ts-nocheck.*\n?/gm, '');
fs
.readFileSync(path.join(__dirname, file), 'utf8')
.replace(/^\/\/ (@ts-nocheck|eslint-disable-next-line).*\n?/gm, '');

export const DEFINITION_PATCH = {
TxInputType: () => readPatch('./TxInputType.ts'),
Expand Down
5 changes: 3 additions & 2 deletions packages/protobuf/scripts/protobuf-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,9 @@ const createCustomTypes = () => {
const lines: string[] = [];
lines.push('// This file is auto generated by @trezor/protobuf package', '');
lines.push('// custom type uint32/64 may be represented as string');
lines.push(`export type ${UINT_TYPE} = string | number;`, '');
lines.push(`type ${UINT_TYPE} = string | number;`, '');
lines.push('// custom type sint32/64');
lines.push(`export type ${SINT_TYPE} = string | number;`, '');
lines.push(`type ${SINT_TYPE} = string | number;`, '');
lines.push(readPatch(`../../../device-utils/src/deviceModelInternal.ts`), '');

return lines;
Expand All @@ -200,6 +200,7 @@ const createMessageType = (types: TypeItem[]) => {
});
lines.push('};');

lines.push('\n// @COPY from this marker to the EOF, types are copied into messages-schema');
lines.push(readPatch('MessageType.ts'));

return lines;
Expand Down
3 changes: 2 additions & 1 deletion packages/protobuf/src/decode.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Field, Message as MessageType, Type } from 'protobufjs/light';

import type { MessageResponse } from './messages';
import { createMessageFromType, isPrimitiveField } from './utils';

const transform = (field: Field, value: any) => {
Expand Down Expand Up @@ -84,5 +85,5 @@ export const decodeMessage = (
const { Message, messageName } = createMessageFromType(messages, messageType);
const message = decode(Message, data);

return { messageName, message };
return { type: messageName, message } as MessageResponse;
};
1 change: 0 additions & 1 deletion packages/protobuf/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export const { parseConfigure, decodeMessage, encodeMessage } = (() => {
return { parseConfigure: parse, decodeMessage: decode, encodeMessage: encode };
})();

export * from './types';
export * as Messages from './messages';
export { loadDefinitions } from './load-definitions';
export * as MessagesSchema from './messages-schema';
32 changes: 21 additions & 11 deletions packages/protobuf/src/messages-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3820,18 +3820,28 @@ export const MessageType = Type.Object(
{ $id: 'MessageType' },
);

// custom type uint32/64 may be represented as string
export type UintType = string | number;
// @COPY from this marker to the EOF, types are copied into messages-schema

export type MessageKey = keyof MessageType;

export type MessageResponse<T extends MessageKey> = {
type: T;
message: MessageType[T];
export type MessagePayload<T extends MessageKey = MessageKey> = MessageType[T];

export type MessageResponse<T extends MessageKey = MessageKey> = T extends any
? {
type: T;
message: MessagePayload<T>;
}
: never;

export type TypedCall = {
<T extends MessageKey, R extends MessageKey[]>(
type: T,
resType: R,
message?: MessagePayload<T>,
): Promise<MessageResponse<R[number]>>;
<T extends MessageKey, R extends MessageKey>(
type: T,
resType: R,
message?: MessagePayload<T>,
): Promise<MessageResponse<R>>;
};

export type TypedCall = <T extends MessageKey, R extends MessageKey>(
type: T,
resType: R,
message?: MessageType[T],
) => Promise<MessageResponse<R>>;
35 changes: 24 additions & 11 deletions packages/protobuf/src/messages.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// This file is auto generated by @trezor/protobuf package

// custom type uint32/64 may be represented as string
export type UintType = string | number;
type UintType = string | number;

// custom type sint32/64
export type SintType = string | number;
type SintType = string | number;

export enum DeviceModelInternal {
T1B1 = 'T1B1',
Expand Down Expand Up @@ -2569,15 +2569,28 @@ export type MessageType = {
TezosSignedTx: TezosSignedTx;
};

// @COPY from this marker to the EOF, types are copied into messages-schema

export type MessageKey = keyof MessageType;

export type MessageResponse<T extends MessageKey> = {
type: T;
message: MessageType[T];
};
export type MessagePayload<T extends MessageKey = MessageKey> = MessageType[T];

export type TypedCall = <T extends MessageKey, R extends MessageKey>(
type: T,
resType: R,
message?: MessageType[T],
) => Promise<MessageResponse<R>>;
export type MessageResponse<T extends MessageKey = MessageKey> = T extends any
? {
type: T;
message: MessagePayload<T>;
}
: never;

export type TypedCall = {
<T extends MessageKey, R extends MessageKey[]>(
type: T,
resType: R,
message?: MessagePayload<T>,
): Promise<MessageResponse<R[number]>>;
<T extends MessageKey, R extends MessageKey>(
type: T,
resType: R,
message?: MessagePayload<T>,
): Promise<MessageResponse<R>>;
};
6 changes: 0 additions & 6 deletions packages/protobuf/src/types.ts

This file was deleted.

6 changes: 3 additions & 3 deletions packages/protobuf/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import * as protobuf from 'protobufjs/light';

import type { MessageFromTrezor } from './types';
import type { MessageKey } from './messages';

const primitiveTypes = [
'bool',
Expand Down Expand Up @@ -58,13 +58,13 @@ export const createMessageFromType = (messages: protobuf.Root, messageType: numb

return {
Message,
messageName: messageType as MessageFromTrezor['type'],
messageName: messageType as MessageKey,
};
}

const messageTypes = messages.lookupEnum('MessageType');

const messageName = messageTypes.valuesById[messageType] as MessageFromTrezor['type'];
const messageName = messageTypes.valuesById[messageType] as MessageKey;

const Message = messages.lookupType(messageName);

Expand Down
Loading
Loading