From 3c34f4aabaa8af225db57f3e6735f1605b65bb47 Mon Sep 17 00:00:00 2001 From: Juan Cortes Ross Date: Tue, 12 Jan 2021 17:47:59 +0100 Subject: [PATCH] LL-4373 Discard existing ops on sync if blacklist has changed Add testing for blacklisted feature --- src/account/serialization.js | 4 + src/families/ethereum/synchronisation.js | 9 +- src/families/ethereum/synchronisation.test.js | 97 +++++++++++++++++++ src/reconciliation.js | 5 + src/types/account.js | 4 + 5 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 src/families/ethereum/synchronisation.test.js diff --git a/src/account/serialization.js b/src/account/serialization.js index 29fa157df2..c519ac2603 100644 --- a/src/account/serialization.js +++ b/src/account/serialization.js @@ -574,6 +574,7 @@ export function fromAccountRaw(rawAccount: AccountRaw): Account { bitcoinResources, swapHistory, algorandResources, + blacklistedTokensCache, } = rawAccount; const subAccounts = @@ -626,6 +627,7 @@ export function fromAccountRaw(rawAccount: AccountRaw): Account { currency, lastSyncDate: new Date(lastSyncDate || 0), swapHistory: [], + blacklistedTokensCache, }; if (xpub) { @@ -691,6 +693,7 @@ export function toAccountRaw({ bitcoinResources, swapHistory, algorandResources, + blacklistedTokensCache, }: Account): AccountRaw { const res: $Exact = { id, @@ -703,6 +706,7 @@ export function toAccountRaw({ freshAddressPath, freshAddresses, blockHeight, + blacklistedTokensCache, creationDate: creationDate.toISOString(), operationsCount, operations: (operations || []).map((o) => toOperationRaw(o)), diff --git a/src/families/ethereum/synchronisation.js b/src/families/ethereum/synchronisation.js index 558d5180fc..c5e2b0bf98 100644 --- a/src/families/ethereum/synchronisation.js +++ b/src/families/ethereum/synchronisation.js @@ -34,10 +34,16 @@ export const getAccountShape: GetAccountShape = async ( // fetch transactions, incrementally if possible const mostRecentStableOperation = initialStableOperations[0]; + + const newBlacklistedTokensCache = JSON.stringify(blacklistedTokenIds || []); + const outdatedBlacklist = + initialAccount?.blacklistedTokensCache !== newBlacklistedTokensCache; + let pullFromBlockHash = initialAccount && areAllOperationsLoaded(initialAccount) && - mostRecentStableOperation + mostRecentStableOperation && + !outdatedBlacklist ? mostRecentStableOperation.blockHash : undefined; @@ -159,6 +165,7 @@ export const getAccountShape: GetAccountShape = async ( blockHeight, lastSyncDate: new Date(), balanceHistory: undefined, + blacklistedTokensCache: newBlacklistedTokensCache, }; return accountShape; diff --git a/src/families/ethereum/synchronisation.test.js b/src/families/ethereum/synchronisation.test.js new file mode 100644 index 0000000000..7a41f5f9da --- /dev/null +++ b/src/families/ethereum/synchronisation.test.js @@ -0,0 +1,97 @@ +// @flow +import { reduce } from "rxjs/operators"; +import { setSupportedCurrencies } from "../../currencies"; +import { fromAccountRaw } from "../../account"; +import { getAccountCurrency } from "../../account/helpers"; +import type { Account } from "../../types"; +import { getAccountBridge } from "../../bridge"; +import { makeBridgeCacheSystem } from "../../bridge/cache"; +import { ethereum1 } from "./test-dataset"; + +setSupportedCurrencies(["ethereum"]); + +describe("blacklistedTokenIds functionality", () => { + const account = fromAccountRaw(ethereum1); + let localCache = {}; + const cache = makeBridgeCacheSystem({ + saveData(c, d) { + localCache[c.id] = d; + return Promise.resolve(); + }, + getData(c) { + return Promise.resolve(localCache[c.id]); + }, + }); + + test("initial raw account contains no token accounts", async () => { + await cache.prepareCurrency(account.currency); + expect(ethereum1.subAccounts?.length).toBeFalsy(); + }); + + test("sync finds tokens, but not blacklisted ones", async () => { + const bridge = getAccountBridge(account); + const blacklistedTokenIds = ["ethereum/erc20/weth"]; + const synced = await bridge + .sync(account, { + paginationConfig: {}, + blacklistedTokenIds, + }) + .pipe(reduce((a, f: (Account) => Account) => f(a), account)) + .toPromise(); + + // Contains token accounts + expect(synced.subAccounts?.length).toBeTruthy(); + + // Contains a known token + expect( + synced.subAccounts.find( + (a) => getAccountCurrency(a)?.id === "ethereum/erc20/0x_project" + ) + ).toBeTruthy(); + + // Does not contain a blacklisted token + expect( + synced.subAccounts.find((a) => + blacklistedTokenIds.includes(getAccountCurrency(a)?.id) + ) + ).toBe(undefined); + }); + + test("account resyncs tokens if no longer blacklisted", async () => { + const bridge = getAccountBridge(account); + const blacklistedTokenIds = ["ethereum/erc20/weth"]; + const syncedWithoutWeth = await bridge + .sync(account, { + paginationConfig: {}, + blacklistedTokenIds, + }) + .pipe(reduce((a, f: (Account) => Account) => f(a), account)) + .toPromise(); + + // Contains token accounts + expect(syncedWithoutWeth.subAccounts?.length).toBeTruthy(); + + // Does not contain a blacklisted token + expect( + syncedWithoutWeth.subAccounts.find((a) => + blacklistedTokenIds.includes(getAccountCurrency(a)?.id) + ) + ).toBe(undefined); + + //Sync again with `syncedWithoutWeth` as a base but without it being blacklisted + const synced = await bridge + .sync(account, { + paginationConfig: {}, + blacklistedTokenIds: ["ethereum/erc20/somethingElse"], + }) + .pipe(reduce((a, f: (Account) => Account) => f(a), account)) + .toPromise(); + + // Does not contain a blacklisted token + expect( + synced.subAccounts.find( + (a) => getAccountCurrency(a)?.id === "ethereum/erc20/weth" + ) + ).toBeTruthy(); + }); +}); diff --git a/src/reconciliation.js b/src/reconciliation.js index 8e20a1cbdb..7e1ae81786 100644 --- a/src/reconciliation.js +++ b/src/reconciliation.js @@ -260,6 +260,11 @@ export function patchAccount( changed = true; } + if (account.blacklistedTokensCache !== updatedRaw.blacklistedTokensCache) { + next.blacklistedTokensCache = updatedRaw.blacklistedTokensCache; + changed = true; + } + if ( updatedRaw.tronResources && account.tronResources !== updatedRaw.tronResources diff --git a/src/types/account.js b/src/types/account.js index 4db056ce05..d8f3058944 100644 --- a/src/types/account.js +++ b/src/types/account.js @@ -194,6 +194,9 @@ export type Account = { // Swap operations linked to this account swapHistory: SwapOperation[], + + // Hash used to discard tx history on sync if blacklisted token ids change + blacklistedTokensCache?: string, }; export type SubAccount = TokenAccount | ChildAccount; @@ -273,6 +276,7 @@ export type AccountRaw = { algorandResources?: AlgorandResourcesRaw, // Swap operations linked to this account swapHistory?: SwapOperationRaw[], + blacklistedTokensCache?: string, }; export type SubAccountRaw = TokenAccountRaw | ChildAccountRaw;