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

temp: profile sync encryption perf analysis #4751

Draft
wants to merge 4 commits into
base: main
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
12 changes: 12 additions & 0 deletions packages/assets-controllers/src/TokenDetectionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ export class TokenDetectionController extends StaticIntervalPollingController<

#isDetectionEnabledForNetwork: boolean;

// OOOH interesting, this is from another controller!
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOL ignore these, these are from another PR I'm working on.

readonly #getBalancesInSingleCall: AssetsContractController['getBalancesInSingleCall'];

readonly #trackMetaMetricsEvent: (options: {
Expand Down Expand Up @@ -203,6 +204,7 @@ export class TokenDetectionController extends StaticIntervalPollingController<
messenger,
}: {
interval?: number;
// NOTE - CHECK HOW THIS WORKS
disabled?: boolean;
getBalancesInSingleCall: AssetsContractController['getBalancesInSingleCall'];
trackMetaMetricsEvent: (options: {
Expand Down Expand Up @@ -467,6 +469,7 @@ export class TokenDetectionController extends StaticIntervalPollingController<
this.setIntervalLength(DEFAULT_INTERVAL);
}

// THIS IS THE MAIN LOGIC
/**
* For each token in the token list provided by the TokenListController, checks the token's balance for the selected account address on the active network.
* On mainnet, if token detection is disabled in preferences, ERC20 token auto detection will be triggered for each contract address in the legacy token list from the @metamask/contract-metadata repo.
Expand Down Expand Up @@ -527,6 +530,7 @@ export class TokenDetectionController extends StaticIntervalPollingController<
await Promise.all(tokenDetectionPromises);
}

// Batches of 1000 tokens arr.
#getSlicesOfTokensToDetect({
chainId,
selectedAddress,
Expand Down Expand Up @@ -572,6 +576,7 @@ export class TokenDetectionController extends StaticIntervalPollingController<
return slicesOfTokensToDetect;
}

// CALLS ARE MADE HERE
async #addDetectedTokens({
tokensSlice,
selectedAddress,
Expand All @@ -584,12 +589,19 @@ export class TokenDetectionController extends StaticIntervalPollingController<
chainId: Hex;
}): Promise<void> {
await safelyExecute(async () => {
// OK NICE
// Lets see if we can add impl inside this `getBalancesInSingleCall` & retain interface?
// Or we can do branching logic?
// Interface is `BalanceMap`, either keep same interface or some add some logic - lets see after inspecting API
// NOTE - this is from a different controller 👀
const balances = await this.#getBalancesInSingleCall(
selectedAddress,
tokensSlice,
networkClientId,
);

// Mapping to `Token` shape.
// We can decide how to handle interface mapping
const tokensWithBalance: Token[] = [];
const eventTokensDetails: string[] = [];
for (const nonZeroTokenAddress of Object.keys(balances)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,10 @@ export function setCachedKey(

cache.set(base64Salt, key);
}

/**
* foo
*/
export function clearCache() {
inMemCachedKDF = {};
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import { sha256 } from '@noble/hashes/sha256';
import { utf8ToBytes, concatBytes, bytesToHex } from '@noble/hashes/utils';

import type { NativeScrypt } from '../types/encryption';
import { getAnyCachedKey, getCachedKeyBySalt, setCachedKey } from './cache';
import {
getAnyCachedKey,
getCachedKeyBySalt,
setCachedKey,
clearCache,
} from './cache';
import { base64ToByteArray, byteArrayToBase64, bytesToUtf8 } from './utils';

export type EncryptedPayload = {
Expand Down Expand Up @@ -247,3 +252,104 @@ export function createSHA256Hash(data: string): string {
const hashedData = sha256(data);
return bytesToHex(hashedData);
}

const getRandomEthereumAddress = () => {
const chars = '0123456789abcdef';
let address = '0x';
for (let i = 0; i < 40; i++) {
address += chars[Math.floor(Math.random() * chars.length)];
}
return address;
};

const getRandomName = () => {
const names = [
'Quick',
'Silent',
'Brave',
'Clever',
'Mighty',
'Swift',
'Wise',
'Bold',
'Fierce',
'Noble',
];
return names[Math.floor(Math.random() * names.length)];
};

const createMockData = () => ({
v: 1,
a: getRandomEthereumAddress(),
id: '3f29b2e4-8c3b-4d6a-9f1c-1a2b3c4d5e6f',
n: getRandomName(),
nlu: Date.now(),
});

// const mockData = JSON.stringify(createMockData());
const mockDataArray = Array.from({ length: 1000 }, createMockData).map((d) =>
JSON.stringify(d),
);

/**
* runEncryptionPerfTestWithoutCache
* Will attempt encrypting 1, 10, 100, 1000 accounts.
* It will not use the cache for the first entry, and then will use the cached keys
* @param nativeScript - native scrypt
*/
export async function runEncryptionPerfTestWithoutCache(
nativeScript?: NativeScrypt,
) {
const password = 'test-password';

const testCases = [1, 10, 100, 1000];

for (const count of testCases) {
clearCache();
const start = performance.now();
for (let i = 0; i < count; i++) {
const plaintext = mockDataArray[i % mockDataArray.length];
await encryption.encryptString(plaintext, password, nativeScript);
}
const end = performance.now();
console.log(
`runEncryptionPerfTestWithoutCache: Time taken for ${count} accounts to encrypt: ${
end - start
} ms`,
);
}
}

/**
* runEncryptionPerfTestWithCache
* Will attempt encrypting 1, 10, 100, 1000 accounts.
* This will use the cached keys if it exists
* @param nativeScript - native scrypt
*/
export async function runEncryptionPerfTestWithCache(
nativeScript?: NativeScrypt,
) {
const password = 'test-password';

const testCases = [1, 10, 100, 1000];

// Something to populate cache
await encryption.encryptString('warm up cache', password);

for (const count of testCases) {
const start = performance.now();
for (let i = 0; i < count; i++) {
const plaintext = mockDataArray[i % mockDataArray.length];
await encryption.encryptString(plaintext, password, nativeScript);
}
const end = performance.now();
console.log(
`runEncryptionPerfTestWithCache: Time taken for ${count} accounts to encrypt: ${
end - start
} ms`,
);
}
}

// runEncryptionPerfTestWithoutCache();
// runEncryptionPerfTestWithCache();
Loading