From 6e30f615a7ab4308b5c15ef94ba3fe36fd7f2eef Mon Sep 17 00:00:00 2001 From: Olusegun Akintayo Date: Wed, 25 Oct 2023 21:23:35 +0100 Subject: [PATCH] feat: Add metrics for provider calls coming from ppom on extension (#21482) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** We need to add metrics to help measure and estimate the usage and cost of Infura by ppom. This fix uses the `providerRequestsCount` object returned as part of the securityAlertResponse of transaction object and gets only the keys that matches the below specified requests. ### Proposal Add new properties to Transactions and Signature events where which property will count the number of rpc requests made by ppom to evaluate that specific transaction or signature. - [x] `ppom_eth_call_count` - counts the number of eth_call rpc requests made by ppom to evaluate that transaction or signature - [x] `ppom_eth_createAccessList_count` - counts the number of eth_call rpc requests made by ppom to evaluate that transaction or signature - [x] `ppom_eth_getStorageAt_count` - counts the number of eth_call rpc requests made by ppom to evaluate that transaction or signature - [x] `ppom_eth_getCode_count` - counts the number of eth_call rpc requests made by ppom to evaluate that transaction or signature - [x] `ppom_eth_getTrasanctionCount_count` - counts the number of eth_call rpc requests made by ppom to evaluate that transaction or signature - [x] `ppom_eth_getBalance_count` - counts the number of eth_call rpc requests made by ppom to evaluate that transaction or signature - [x] `ppom_trace_call_count` - counts the number of eth_call rpc requests made by ppom to evaluate that transaction or signature ### References - [ppom provider usage benchmark](https://wobbly-nutmeg-8a5.notion.site/MM-JSON-RPC-Benchmark-37d4a3bd74914b0fbe9147582c0cde51) - [ppom required json-rpcs](https://docs.google.com/document/d/1aOjVIsGHc0twc96V82OcXmpduGKrPjRScL_jwfgjkj0/edit?usp=sharing) ## **Manual testing steps** 1. Start extension with blockaid enabled 2. Go to test-dapp and initiate a blockaid transaction 3. When blockaid banner is shown, check that the above keys (or some of it at least) are part of the metrics transaction or signature events Fixes [#1357](https://github.com/MetaMask/MetaMask-planning/issues/1357) ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've clearly explained: - [x] What problem this PR is solving. - [x] How this problem was solved. - [x] How reviewers can test my changes. - [x] I’ve indicated what issue this PR is linked to: Fixes #??? - [ ] I’ve included tests if applicable. - [x] I’ve documented any added code. - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). - [x] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [x] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- .../lib/createRPCMethodTrackingMiddleware.js | 10 + .../createRPCMethodTrackingMiddleware.test.js | 41 ++ app/scripts/lib/transaction-metrics.test.ts | 566 +++++++++++------- app/scripts/lib/transaction-metrics.ts | 15 + 4 files changed, 401 insertions(+), 231 deletions(-) diff --git a/app/scripts/lib/createRPCMethodTrackingMiddleware.js b/app/scripts/lib/createRPCMethodTrackingMiddleware.js index c255cbb7f805..e0f49aff1004 100644 --- a/app/scripts/lib/createRPCMethodTrackingMiddleware.js +++ b/app/scripts/lib/createRPCMethodTrackingMiddleware.js @@ -197,6 +197,16 @@ export default function createRPCMethodTrackingMiddleware({ const paramsExamplePassword = req?.params?.[2]; ///: BEGIN:ONLY_INCLUDE_IN(blockaid) + if (req.securityAlertResponse?.providerRequestsCount) { + Object.keys(req.securityAlertResponse.providerRequestsCount).forEach( + (key) => { + const metricKey = `ppom_${key}_count`; + eventProperties[metricKey] = + req.securityAlertResponse.providerRequestsCount[key]; + }, + ); + } + eventProperties.security_alert_response = req.securityAlertResponse?.result_type ?? BlockaidResultType.NotApplicable; diff --git a/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js b/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js index 16c83c31f2b2..17c745e741d6 100644 --- a/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js +++ b/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js @@ -143,6 +143,47 @@ describe('createRPCMethodTrackingMiddleware', () => { }); }); + it(`should track an event with correct blockaid parameters when providerRequestsCount is provided`, async () => { + const req = { + method: MESSAGE_TYPE.ETH_SIGN, + origin: 'some.dapp', + securityAlertResponse: { + result_type: BlockaidResultType.Malicious, + reason: BlockaidReason.maliciousDomain, + providerRequestsCount: { + eth_call: 5, + eth_getCode: 3, + }, + }, + }; + + const res = { + error: null, + }; + const { next } = getNext(); + await handler(req, res, next); + expect(trackEvent).toHaveBeenCalledTimes(1); + /** + * TODO: + * toMatchObject matches even if the the matched object does not contain some of the properties of the expected object + * I'm not sure why toMatchObject is used but we should probably check the other tests in this file for correctness in + * another PR. + * + */ + expect(trackEvent.mock.calls[0][0]).toStrictEqual({ + category: 'inpage_provider', + event: MetaMetricsEventName.SignatureRequested, + properties: { + signature_type: MESSAGE_TYPE.ETH_SIGN, + security_alert_response: BlockaidResultType.Malicious, + security_alert_reason: BlockaidReason.maliciousDomain, + ppom_eth_call_count: 5, + ppom_eth_getCode_count: 3, + }, + referrer: { url: 'some.dapp' }, + }); + }); + it(`should track a ${MetaMetricsEventName.SignatureApproved} event if the user approves`, async () => { const req = { method: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4, diff --git a/app/scripts/lib/transaction-metrics.test.ts b/app/scripts/lib/transaction-metrics.test.ts index 8634e3201465..dd922699cb26 100644 --- a/app/scripts/lib/transaction-metrics.test.ts +++ b/app/scripts/lib/transaction-metrics.test.ts @@ -67,6 +67,9 @@ describe('Transaction metrics', () => { mockChainId, mockNetworkId, mockTransactionMeta, + mockTransactionMetaWithBlockaid, + expectedProperties, + expectedSensitiveProperties, mockActionId; beforeEach(() => { @@ -98,6 +101,51 @@ describe('Transaction metrics', () => { }, }; + // copy mockTransactionMeta and add blockaid data + mockTransactionMetaWithBlockaid = { + ...JSON.parse(JSON.stringify(mockTransactionMeta)), + securityProviderResponse: { + flagAsDangerous: 1, + providerRequestsCount: { + eth_call: 5, + eth_getCode: 3, + }, + }, + }; + + expectedProperties = { + account_snap_type: 'snaptype', + account_snap_version: 'snapversion', + account_type: undefined, + asset_type: AssetType.native, + chain_id: mockChainId, + device_model: undefined, + eip_1559_version: '0', + gas_edit_attempted: 'none', + gas_edit_type: 'none', + network: mockNetworkId, + referrer: ORIGIN_METAMASK, + security_alert_reason: BlockaidReason.notApplicable, + security_alert_response: BlockaidReason.notApplicable, + source: MetaMetricsTransactionEventSource.User, + status: 'unapproved', + token_standard: TokenStandard.none, + transaction_speed_up: false, + transaction_type: TransactionType.simpleSend, + ui_customizations: null, + }; + + expectedSensitiveProperties = { + default_gas: '0.000031501', + default_gas_price: '2', + first_seen: 1624408066355, + gas_limit: '0x7b0d', + gas_price: '2', + transaction_contract_method: undefined, + transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, + transaction_replaced: undefined, + }; + jest.clearAllMocks(); }); @@ -115,6 +163,28 @@ describe('Transaction metrics', () => { actionId: mockActionId, }); + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledWith({ + actionId: mockActionId, + category: MetaMetricsEventCategory.Transactions, + failureEvent: TransactionMetaMetricsEvent.rejected, + initialEvent: TransactionMetaMetricsEvent.added, + successEvent: TransactionMetaMetricsEvent.approved, + uniqueIdentifier: 'transaction-added-1', + persist: true, + properties: expectedProperties, + sensitiveProperties: expectedSensitiveProperties, + }); + }); + + it('should create event fragment with blockaid', async () => { + await handleTransactionAdded(mockTransactionMetricsRequest, { + transactionMeta: mockTransactionMetaWithBlockaid as any, + actionId: mockActionId, + }); + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( 1, ); @@ -127,36 +197,12 @@ describe('Transaction metrics', () => { uniqueIdentifier: 'transaction-added-1', persist: true, properties: { - account_snap_type: 'snaptype', - account_snap_version: 'snapversion', - account_type: undefined, - asset_type: AssetType.native, - chain_id: mockChainId, - device_model: undefined, - eip_1559_version: '0', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - network: mockNetworkId, - referrer: ORIGIN_METAMASK, - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidReason.notApplicable, - source: MetaMetricsTransactionEventSource.User, - status: 'unapproved', - token_standard: TokenStandard.none, - transaction_speed_up: false, - transaction_type: TransactionType.simpleSend, - ui_customizations: null, - }, - sensitiveProperties: { - default_gas: '0.000031501', - default_gas_price: '2', - first_seen: 1624408066355, - gas_limit: '0x7b0d', - gas_price: '2', - transaction_contract_method: undefined, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - transaction_replaced: undefined, + ...expectedProperties, + ui_customizations: ['flagged_as_malicious'], + ppom_eth_call_count: 5, + ppom_eth_getCode_count: 3, }, + sensitiveProperties: expectedSensitiveProperties, }); }); }); @@ -182,38 +228,6 @@ describe('Transaction metrics', () => { }); const expectedUniqueId = 'transaction-added-1'; - const expectedProperties = { - account_snap_type: 'snaptype', - account_snap_version: 'snapversion', - account_type: undefined, - asset_type: AssetType.native, - chain_id: mockChainId, - device_model: undefined, - eip_1559_version: '0', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - network: mockNetworkId, - referrer: ORIGIN_METAMASK, - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidReason.notApplicable, - source: MetaMetricsTransactionEventSource.User, - status: 'unapproved', - token_standard: TokenStandard.none, - transaction_speed_up: false, - transaction_type: TransactionType.simpleSend, - ui_customizations: null, - }; - - const expectedSensitiveProperties = { - default_gas: '0.000031501', - default_gas_price: '2', - first_seen: 1624408066355, - gas_limit: '0x7b0d', - gas_price: '2', - transaction_contract_method: undefined, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - transaction_replaced: undefined, - }; expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( 1, @@ -247,6 +261,57 @@ describe('Transaction metrics', () => { mockTransactionMetricsRequest.finalizeEventFragment, ).toBeCalledWith(expectedUniqueId); }); + + it('should create, update, finalize event fragment with blockaid', async () => { + await handleTransactionApproved(mockTransactionMetricsRequest, { + transactionMeta: mockTransactionMetaWithBlockaid as any, + actionId: mockActionId, + }); + + const expectedUniqueId = 'transaction-added-1'; + + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledWith({ + actionId: mockActionId, + category: MetaMetricsEventCategory.Transactions, + successEvent: TransactionMetaMetricsEvent.approved, + failureEvent: TransactionMetaMetricsEvent.rejected, + uniqueIdentifier: expectedUniqueId, + persist: true, + properties: { + ...expectedProperties, + ui_customizations: ['flagged_as_malicious'], + ppom_eth_call_count: 5, + ppom_eth_getCode_count: 3, + }, + sensitiveProperties: expectedSensitiveProperties, + }); + + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledWith( + expectedUniqueId, + { + properties: { + ...expectedProperties, + ui_customizations: ['flagged_as_malicious'], + ppom_eth_call_count: 5, + ppom_eth_getCode_count: 3, + }, + sensitiveProperties: expectedSensitiveProperties, + }, + ); + + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledTimes(1); + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledWith(expectedUniqueId); + }); }); describe('handleTransactionFinalized', () => { @@ -279,41 +344,6 @@ describe('Transaction metrics', () => { } as any); const expectedUniqueId = 'transaction-submitted-1'; - const expectedProperties = { - account_snap_type: 'snaptype', - account_snap_version: 'snapversion', - account_type: undefined, - asset_type: AssetType.native, - chain_id: mockChainId, - device_model: undefined, - eip_1559_version: '0', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - network: mockNetworkId, - referrer: ORIGIN_METAMASK, - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidReason.notApplicable, - source: MetaMetricsTransactionEventSource.User, - status: 'unapproved', - token_standard: TokenStandard.none, - transaction_speed_up: false, - transaction_type: TransactionType.simpleSend, - ui_customizations: null, - }; - - const expectedSensitiveProperties = { - completion_time: expect.any(String), - default_gas: '0.000031501', - default_gas_price: '2', - first_seen: 1624408066355, - gas_limit: '0x7b0d', - gas_price: '2', - gas_used: '0.000000291', - transaction_contract_method: undefined, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - transaction_replaced: undefined, - status: METRICS_STATUS_FAILED, - }; expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( 1, @@ -325,7 +355,12 @@ describe('Transaction metrics', () => { uniqueIdentifier: expectedUniqueId, persist: true, properties: expectedProperties, - sensitiveProperties: expectedSensitiveProperties, + sensitiveProperties: { + ...expectedSensitiveProperties, + completion_time: expect.any(String), + gas_used: '0.000000291', + status: METRICS_STATUS_FAILED, + }, }); expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledTimes( @@ -335,7 +370,78 @@ describe('Transaction metrics', () => { expectedUniqueId, { properties: expectedProperties, - sensitiveProperties: expectedSensitiveProperties, + sensitiveProperties: { + ...expectedSensitiveProperties, + completion_time: expect.any(String), + gas_used: '0.000000291', + status: METRICS_STATUS_FAILED, + }, + }, + ); + + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledTimes(1); + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledWith(expectedUniqueId); + }); + + it('should create, update, finalize event fragment with blockaid', async () => { + mockTransactionMetaWithBlockaid.txReceipt = { + gasUsed: '0x123', + status: '0x0', + }; + mockTransactionMetaWithBlockaid.submittedTime = 123; + + await handleTransactionFinalized(mockTransactionMetricsRequest, { + transactionMeta: mockTransactionMetaWithBlockaid, + actionId: mockActionId, + } as any); + + const expectedUniqueId = 'transaction-submitted-1'; + + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledWith({ + actionId: mockActionId, + category: MetaMetricsEventCategory.Transactions, + successEvent: TransactionMetaMetricsEvent.finalized, + uniqueIdentifier: expectedUniqueId, + persist: true, + properties: { + ...expectedProperties, + ui_customizations: ['flagged_as_malicious'], + ppom_eth_call_count: 5, + ppom_eth_getCode_count: 3, + }, + sensitiveProperties: { + ...expectedSensitiveProperties, + completion_time: expect.any(String), + gas_used: '0.000000291', + status: METRICS_STATUS_FAILED, + }, + }); + + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledWith( + expectedUniqueId, + { + properties: { + ...expectedProperties, + ui_customizations: ['flagged_as_malicious'], + ppom_eth_call_count: 5, + ppom_eth_getCode_count: 3, + }, + sensitiveProperties: { + ...expectedSensitiveProperties, + completion_time: expect.any(String), + gas_used: '0.000000291', + status: METRICS_STATUS_FAILED, + }, }, ); @@ -357,39 +463,6 @@ describe('Transaction metrics', () => { } as any); const expectedUniqueId = 'transaction-submitted-1'; - const expectedProperties = { - account_snap_type: 'snaptype', - account_snap_version: 'snapversion', - account_type: undefined, - asset_type: AssetType.native, - chain_id: mockChainId, - device_model: undefined, - eip_1559_version: '0', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - network: mockNetworkId, - referrer: ORIGIN_METAMASK, - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidReason.notApplicable, - source: MetaMetricsTransactionEventSource.User, - status: 'unapproved', - token_standard: TokenStandard.none, - transaction_speed_up: false, - transaction_type: TransactionType.simpleSend, - ui_customizations: null, - }; - - const expectedSensitiveProperties = { - default_gas: '0.000031501', - default_gas_price: '2', - error: mockErrorMessage, - first_seen: 1624408066355, - gas_limit: '0x7b0d', - gas_price: '2', - transaction_contract_method: undefined, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - transaction_replaced: undefined, - }; expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( 1, @@ -401,7 +474,10 @@ describe('Transaction metrics', () => { uniqueIdentifier: expectedUniqueId, persist: true, properties: expectedProperties, - sensitiveProperties: expectedSensitiveProperties, + sensitiveProperties: { + ...expectedSensitiveProperties, + error: mockErrorMessage, + }, }); expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledTimes( @@ -411,7 +487,10 @@ describe('Transaction metrics', () => { expectedUniqueId, { properties: expectedProperties, - sensitiveProperties: expectedSensitiveProperties, + sensitiveProperties: { + ...expectedSensitiveProperties, + error: mockErrorMessage, + }, }, ); @@ -445,39 +524,6 @@ describe('Transaction metrics', () => { } as any); const expectedUniqueId = 'transaction-submitted-1'; - const expectedProperties = { - account_snap_type: 'snaptype', - account_snap_version: 'snapversion', - account_type: undefined, - asset_type: AssetType.native, - chain_id: mockChainId, - device_model: undefined, - eip_1559_version: '0', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - network: mockNetworkId, - referrer: ORIGIN_METAMASK, - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidReason.notApplicable, - source: MetaMetricsTransactionEventSource.User, - status: 'unapproved', - token_standard: TokenStandard.none, - transaction_speed_up: false, - transaction_type: TransactionType.simpleSend, - ui_customizations: null, - }; - - const expectedSensitiveProperties = { - default_gas: '0.000031501', - default_gas_price: '2', - dropped: true, - first_seen: 1624408066355, - gas_limit: '0x7b0d', - gas_price: '2', - transaction_contract_method: undefined, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - transaction_replaced: 'other', - }; expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( 1, @@ -489,7 +535,11 @@ describe('Transaction metrics', () => { uniqueIdentifier: expectedUniqueId, persist: true, properties: expectedProperties, - sensitiveProperties: expectedSensitiveProperties, + sensitiveProperties: { + ...expectedSensitiveProperties, + dropped: true, + transaction_replaced: 'other', + }, }); expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledTimes( @@ -499,7 +549,69 @@ describe('Transaction metrics', () => { expectedUniqueId, { properties: expectedProperties, - sensitiveProperties: expectedSensitiveProperties, + sensitiveProperties: { + ...expectedSensitiveProperties, + dropped: true, + transaction_replaced: 'other', + }, + }, + ); + + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledTimes(1); + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledWith(expectedUniqueId); + }); + + it('should create, update, finalize event fragment with blockaid', async () => { + await handleTransactionDropped(mockTransactionMetricsRequest, { + transactionMeta: mockTransactionMetaWithBlockaid, + actionId: mockActionId, + } as any); + + const expectedUniqueId = 'transaction-submitted-1'; + + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledWith({ + actionId: mockActionId, + category: MetaMetricsEventCategory.Transactions, + successEvent: TransactionMetaMetricsEvent.finalized, + uniqueIdentifier: expectedUniqueId, + persist: true, + properties: { + ...expectedProperties, + ui_customizations: ['flagged_as_malicious'], + ppom_eth_call_count: 5, + ppom_eth_getCode_count: 3, + }, + sensitiveProperties: { + ...expectedSensitiveProperties, + dropped: true, + transaction_replaced: 'other', + }, + }); + + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledWith( + expectedUniqueId, + { + properties: { + ...expectedProperties, + ui_customizations: ['flagged_as_malicious'], + ppom_eth_call_count: 5, + ppom_eth_getCode_count: 3, + }, + sensitiveProperties: { + ...expectedSensitiveProperties, + dropped: true, + transaction_replaced: 'other', + }, }, ); @@ -533,38 +645,6 @@ describe('Transaction metrics', () => { } as any); const expectedUniqueId = 'transaction-added-1'; - const expectedProperties = { - account_snap_type: 'snaptype', - account_snap_version: 'snapversion', - account_type: undefined, - asset_type: AssetType.native, - chain_id: mockChainId, - device_model: undefined, - eip_1559_version: '0', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - network: mockNetworkId, - referrer: ORIGIN_METAMASK, - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidReason.notApplicable, - source: MetaMetricsTransactionEventSource.User, - status: 'unapproved', - token_standard: TokenStandard.none, - transaction_speed_up: false, - transaction_type: TransactionType.simpleSend, - ui_customizations: null, - }; - - const expectedSensitiveProperties = { - default_gas: '0.000031501', - default_gas_price: '2', - first_seen: 1624408066355, - gas_limit: '0x7b0d', - gas_price: '2', - transaction_contract_method: undefined, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - transaction_replaced: undefined, - }; expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( 1, @@ -600,6 +680,59 @@ describe('Transaction metrics', () => { abandoned: true, }); }); + + it('should create, update, finalize event fragment with blockaid', async () => { + await handleTransactionRejected(mockTransactionMetricsRequest, { + transactionMeta: mockTransactionMetaWithBlockaid, + actionId: mockActionId, + } as any); + + const expectedUniqueId = 'transaction-added-1'; + + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledWith({ + actionId: mockActionId, + category: MetaMetricsEventCategory.Transactions, + successEvent: TransactionMetaMetricsEvent.approved, + failureEvent: TransactionMetaMetricsEvent.rejected, + uniqueIdentifier: expectedUniqueId, + persist: true, + properties: { + ...expectedProperties, + ui_customizations: ['flagged_as_malicious'], + ppom_eth_call_count: 5, + ppom_eth_getCode_count: 3, + }, + sensitiveProperties: expectedSensitiveProperties, + }); + + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledWith( + expectedUniqueId, + { + properties: { + ...expectedProperties, + ui_customizations: ['flagged_as_malicious'], + ppom_eth_call_count: 5, + ppom_eth_getCode_count: 3, + }, + sensitiveProperties: expectedSensitiveProperties, + }, + ); + + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledTimes(1); + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledWith(expectedUniqueId, { + abandoned: true, + }); + }); }); describe('handleTransactionSubmitted', () => { @@ -629,37 +762,8 @@ describe('Transaction metrics', () => { successEvent: TransactionMetaMetricsEvent.finalized, uniqueIdentifier: 'transaction-submitted-1', persist: true, - properties: { - account_snap_type: 'snaptype', - account_snap_version: 'snapversion', - account_type: undefined, - asset_type: AssetType.native, - chain_id: mockChainId, - device_model: undefined, - eip_1559_version: '0', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - network: mockNetworkId, - referrer: ORIGIN_METAMASK, - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidReason.notApplicable, - source: MetaMetricsTransactionEventSource.User, - status: 'unapproved', - token_standard: TokenStandard.none, - transaction_speed_up: false, - transaction_type: TransactionType.simpleSend, - ui_customizations: null, - }, - sensitiveProperties: { - default_gas: '0.000031501', - default_gas_price: '2', - first_seen: 1624408066355, - gas_limit: '0x7b0d', - gas_price: '2', - transaction_contract_method: undefined, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - transaction_replaced: undefined, - }, + properties: expectedProperties, + sensitiveProperties: expectedSensitiveProperties, }); expect( diff --git a/app/scripts/lib/transaction-metrics.ts b/app/scripts/lib/transaction-metrics.ts index 9c61e2bb9e30..e975da3fe55f 100644 --- a/app/scripts/lib/transaction-metrics.ts +++ b/app/scripts/lib/transaction-metrics.ts @@ -773,6 +773,20 @@ async function buildEventFragmentProperties({ } ///: END:ONLY_INCLUDE_IN + ///: BEGIN:ONLY_INCLUDE_IN(blockaid) + const additionalBlockaidParams = {} as Record; + + if (securityProviderResponse?.providerRequestsCount) { + Object.keys(securityProviderResponse.providerRequestsCount).forEach( + (key) => { + const metricKey = `ppom_${key}_count`; + additionalBlockaidParams[metricKey] = + securityProviderResponse.providerRequestsCount[key]; + }, + ); + } + ///: END:ONLY_INCLUDE_IN + /** The transaction status property is not considered sensitive and is now included in the non-anonymous event */ let properties = { chain_id: chainId, @@ -799,6 +813,7 @@ async function buildEventFragmentProperties({ securityAlertResponse?.result_type ?? BlockaidResultType.NotApplicable, security_alert_reason: securityAlertResponse?.reason ?? BlockaidReason.notApplicable, + ...additionalBlockaidParams, ///: END:ONLY_INCLUDE_IN } as Record;