-
Notifications
You must be signed in to change notification settings - Fork 300
feat: add tx explanation for TxIntentMismatch errors #7580
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -61,6 +61,7 @@ import { | |
| assertValidTransactionRecipient, | ||
| explainTx, | ||
| fromExtendedAddressFormat, | ||
| getTxExplanation, | ||
| isScriptRecipient, | ||
| parseTransaction, | ||
| verifyTransaction, | ||
|
|
@@ -143,7 +144,8 @@ function convertValidationErrorToTxIntentMismatch( | |
| error: AggregateValidationError, | ||
| reqId: string | IRequestTracer | undefined, | ||
| txParams: BaseTransactionParams, | ||
| txHex: string | undefined | ||
| txHex: string | undefined, | ||
| txExplanation?: string | ||
| ): TxIntentMismatchRecipientError { | ||
| const mismatchedRecipients: MismatchedRecipient[] = []; | ||
|
|
||
|
|
@@ -170,7 +172,8 @@ function convertValidationErrorToTxIntentMismatch( | |
| reqId, | ||
| [txParams], | ||
| txHex, | ||
| mismatchedRecipients | ||
| mismatchedRecipients, | ||
| txExplanation | ||
| ); | ||
| // Preserve the original structured error as the cause for debugging | ||
| // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause | ||
|
|
@@ -615,11 +618,19 @@ export abstract class AbstractUtxoCoin extends BaseCoin { | |
| async verifyTransaction<TNumber extends number | bigint = number>( | ||
| params: VerifyTransactionOptions<TNumber> | ||
| ): Promise<boolean> { | ||
| const txExplanation = await getTxExplanation(this, params.txPrebuild); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is pretty wasteful if we only use it in the |
||
|
|
||
| try { | ||
| return await verifyTransaction(this, this.bitgo, params); | ||
| } catch (error) { | ||
| if (error instanceof AggregateValidationError) { | ||
| throw convertValidationErrorToTxIntentMismatch(error, params.reqId, params.txParams, params.txPrebuild.txHex); | ||
| throw convertValidationErrorToTxIntentMismatch( | ||
| error, | ||
| params.reqId, | ||
| params.txParams, | ||
| params.txPrebuild.txHex, | ||
| txExplanation | ||
| ); | ||
| } | ||
| throw error; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ import { DescriptorMap } from '@bitgo/utxo-core/descriptor'; | |
|
|
||
| import { AbstractUtxoCoin, VerifyTransactionOptions } from '../../abstractUtxoCoin'; | ||
| import { BaseOutput, BaseParsedTransactionOutputs } from '../types'; | ||
| import { getTxExplanation } from '../txExplanation'; | ||
|
|
||
| import { toBaseParsedTransactionOutputsFromPsbt } from './parse'; | ||
|
|
||
|
|
@@ -75,13 +76,16 @@ export async function verifyTransaction<TNumber extends number | bigint>( | |
| params: VerifyTransactionOptions<TNumber>, | ||
| descriptorMap: DescriptorMap | ||
| ): Promise<boolean> { | ||
| const txExplanation = await getTxExplanation(coin, params.txPrebuild); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same |
||
|
|
||
| const tx = coin.decodeTransactionFromPrebuild(params.txPrebuild); | ||
| if (!(tx instanceof utxolib.bitgo.UtxoPsbt)) { | ||
| throw new TxIntentMismatchError( | ||
| 'unexpected transaction type', | ||
| params.reqId, | ||
| [params.txParams], | ||
| params.txPrebuild.txHex | ||
| params.txPrebuild.txHex, | ||
| txExplanation | ||
| ); | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import { AbstractUtxoCoin, TransactionPrebuild } from '../abstractUtxoCoin'; | ||
|
|
||
| /** | ||
| * Generate a stringified transaction explanation for error reporting | ||
| * @param coin - The UTXO coin instance | ||
| * @param txPrebuild - Transaction prebuild containing txHex and txInfo | ||
| * @returns Stringified JSON explanation | ||
| */ | ||
| export async function getTxExplanation<TNumber extends number | bigint>( | ||
| coin: AbstractUtxoCoin, | ||
| txPrebuild: TransactionPrebuild<TNumber> | ||
| ): Promise<string | undefined> { | ||
| if (!txPrebuild.txHex) { | ||
| return undefined; | ||
| } | ||
|
|
||
| try { | ||
| const explanation = await coin.explainTransaction({ | ||
| txHex: txPrebuild.txHex, | ||
| txInfo: txPrebuild.txInfo, | ||
| }); | ||
| return JSON.stringify(explanation, null, 2); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this will not work with bigint values
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will do a bigint-safe JSON serialization. Can I create a test file modules/abstract-utxo/test/unit/txExplanation.ts to test this
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do we need to a return a why can't the caller of this func (whoever that is) deal with the string conversion?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the alternative to return the object ( regardless I can move this function to
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
yes
I don't think we need a new function
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. but then we would need to duplicate/in-line this try catch logic in each call site? In
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this is returning a string again though? (instead of the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes but at least it is relegated to the error class, so I'm not as concerned about the interface you can implement it returning |
||
| } catch (e) { | ||
| const errorDetails = { | ||
| error: 'Failed to parse transaction explanation', | ||
| txHex: txPrebuild.txHex, | ||
| details: e instanceof Error ? e.message : String(e), | ||
| }; | ||
| return JSON.stringify(errorDetails, null, 2); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -256,11 +256,13 @@ export interface ContractDataPayload { | |
| * @property {string | IRequestTracer | undefined} id - Transaction ID or request tracer for tracking | ||
| * @property {TransactionParams[]} txParams - Array of transaction parameters that were analyzed | ||
| * @property {string | undefined} txHex - The raw transaction in hexadecimal format | ||
| * @property {string | undefined} txExplanation - Stringified transaction explanation | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. better make this |
||
| */ | ||
| export class TxIntentMismatchError extends BitGoJsError { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add a static function here |
||
| public readonly id: string | IRequestTracer | undefined; | ||
| public readonly txParams: TransactionParams[]; | ||
| public readonly txHex: string | undefined; | ||
| public readonly txExplanation: string | undefined; | ||
|
|
||
| /** | ||
| * Creates an instance of TxIntentMismatchError | ||
|
|
@@ -269,17 +271,20 @@ export class TxIntentMismatchError extends BitGoJsError { | |
| * @param {string | IRequestTracer | undefined} id - Transaction ID or request tracer | ||
| * @param {TransactionParams[]} txParams - Transaction parameters that were analyzed | ||
| * @param {string | undefined} txHex - Raw transaction hex string | ||
| * @param {string | undefined} txExplanation - Stringified transaction explanation | ||
| */ | ||
| public constructor( | ||
| message: string, | ||
| id: string | IRequestTracer | undefined, | ||
| txParams: TransactionParams[], | ||
| txHex: string | undefined | ||
| txHex: string | undefined, | ||
| txExplanation?: string | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| ) { | ||
| super(message); | ||
| this.id = id; | ||
| this.txParams = txParams; | ||
| this.txHex = txHex; | ||
| this.txExplanation = txExplanation; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do some sort of safe stringification here ( |
||
| } | ||
| } | ||
|
|
||
|
|
@@ -304,15 +309,17 @@ export class TxIntentMismatchRecipientError extends TxIntentMismatchError { | |
| * @param {TransactionParams[]} txParams - Transaction parameters that were analyzed | ||
| * @param {string | undefined} txHex - Raw transaction hex string | ||
| * @param {MismatchedRecipient[]} mismatchedRecipients - Array of recipients that don't match user intent | ||
| * @param {string | undefined} txExplanation - Stringified transaction explanation | ||
| */ | ||
| public constructor( | ||
| message: string, | ||
| id: string | IRequestTracer | undefined, | ||
| txParams: TransactionParams[], | ||
| txHex: string | undefined, | ||
| mismatchedRecipients: MismatchedRecipient[] | ||
| mismatchedRecipients: MismatchedRecipient[], | ||
| txExplanation?: string | ||
| ) { | ||
| super(message, id, txParams, txHex); | ||
| super(message, id, txParams, txHex, txExplanation); | ||
| this.mismatchedRecipients = mismatchedRecipients; | ||
| } | ||
| } | ||
|
|
@@ -338,15 +345,17 @@ export class TxIntentMismatchContractError extends TxIntentMismatchError { | |
| * @param {TransactionParams[]} txParams - Transaction parameters that were analyzed | ||
| * @param {string | undefined} txHex - Raw transaction hex string | ||
| * @param {ContractDataPayload} mismatchedDataPayload - The contract interaction data that doesn't match user intent | ||
| * @param {string | undefined} txExplanation - Stringified transaction explanation | ||
| */ | ||
| public constructor( | ||
| message: string, | ||
| id: string | IRequestTracer | undefined, | ||
| txParams: TransactionParams[], | ||
| txHex: string | undefined, | ||
| mismatchedDataPayload: ContractDataPayload | ||
| mismatchedDataPayload: ContractDataPayload, | ||
| txExplanation?: string | ||
| ) { | ||
| super(message, id, txParams, txHex); | ||
| super(message, id, txParams, txHex, txExplanation); | ||
| this.mismatchedDataPayload = mismatchedDataPayload; | ||
| } | ||
| } | ||
|
|
@@ -372,15 +381,17 @@ export class TxIntentMismatchApprovalError extends TxIntentMismatchError { | |
| * @param {TransactionParams[]} txParams - Transaction parameters that were analyzed | ||
| * @param {string | undefined} txHex - Raw transaction hex string | ||
| * @param {TokenApproval} tokenApproval - Details of the token approval that doesn't match user intent | ||
| * @param {string | undefined} txExplanation - Stringified transaction explanation | ||
| */ | ||
| public constructor( | ||
| message: string, | ||
| id: string | IRequestTracer | undefined, | ||
| txParams: TransactionParams[], | ||
| txHex: string | undefined, | ||
| tokenApproval: TokenApproval | ||
| tokenApproval: TokenApproval, | ||
| txExplanation?: string | ||
| ) { | ||
| super(message, id, txParams, txHex); | ||
| super(message, id, txParams, txHex, txExplanation); | ||
| this.tokenApproval = tokenApproval; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we have this inside
throwRecipientMismatchfunction ?so that its only called when required
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and probably have the string conversion inside, instead of a new function