From 9997e636fec25e62be7c7e92f3b89a4b3a0ae757 Mon Sep 17 00:00:00 2001 From: IgorShadurin Date: Wed, 1 Mar 2023 19:09:06 +0300 Subject: [PATCH] refactor: added caching (#219) * refactor: added caching * refactor: added example of cache recovering * refactor: bee-js updated * refactor: added retry param for browser tests * refactor: added node version for the web tests * refactor: added node 18 support * refactor: removed node 18 support --- .github/workflows/tests.yaml | 5 + README.md | 25 +++ package-lock.json | 274 +++++++++++++++------------ package.json | 2 +- src/account/account-data.ts | 16 +- src/account/utils.ts | 12 +- src/cache/types.ts | 57 ++++++ src/cache/utils.ts | 74 ++++++++ src/connection/connection.ts | 8 +- src/content-items/types.ts | 10 + src/content-items/utils.ts | 12 +- src/directory/directory.ts | 23 ++- src/fdp-storage.ts | 8 +- src/feed/api.ts | 6 +- src/file/file.ts | 2 +- src/pod/api.ts | 47 +++-- src/pod/cache/api.ts | 43 +++++ src/pod/personal-storage.ts | 55 ++++-- src/pod/utils.ts | 57 +++--- src/types.ts | 9 +- src/utils/cache/wallet.ts | 27 +++ src/utils/wallet.ts | 2 +- test/integration/fdp-class.spec.ts | 178 ++++++++++++++++- test/integration/speed-check.spec.ts | 50 +++++ test/unit/cache/utils.spec.ts | 63 ++++++ test/utils.ts | 18 +- 26 files changed, 846 insertions(+), 237 deletions(-) create mode 100644 src/cache/types.ts create mode 100644 src/cache/utils.ts create mode 100644 src/pod/cache/api.ts create mode 100644 src/utils/cache/wallet.ts create mode 100644 test/integration/speed-check.spec.ts create mode 100644 test/unit/cache/utils.spec.ts diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index ccbbf71b..e361bc0d 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -73,6 +73,11 @@ jobs: with: fetch-depth: 1 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: Auth to Github Package Docker Registry run: | echo "${{ secrets.GITHUB_TOKEN }}" | docker login https://docker.pkg.github.com -u ${GITHUB_ACTOR} --password-stdin diff --git a/README.md b/README.md index 835815f6..8bd15a0a 100644 --- a/README.md +++ b/README.md @@ -292,6 +292,31 @@ await fdp.account.migrate('oldusername', 'oldpassword', { }) ``` +Using FDP instance with cache + +```js +const fdpCache = new FdpStorage('https://localhost:1633', batchId, { + cacheOptions: { + isUseCache: true, + onSaveCache: async cacheObject => { + const cache = JSON.stringify(cacheObject) + console.log('cache updated', cache) + }, + } +}) +``` + +Recovering FDP instance with saved cache + +```js +const fdpCache = new FdpStorage('https://localhost:1633', batchId, { + cacheOptions: { + isUseCache: true, + } +}) +fdpCache.cache.object = JSON.parse(cache) +``` + ## Documentation You can generate API docs locally with: diff --git a/package-lock.json b/package-lock.json index 4b7aa341..427dc132 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.7.0", "license": "BSD-3-Clause", "dependencies": { - "@ethersphere/bee-js": "^3.1.0", + "@ethersphere/bee-js": "^5.1.0", "@fairdatasociety/fdp-contracts-js": "^2.0.1", "crypto-js": "^4.1.1", "ethers": "^5.5.2", @@ -2270,36 +2270,37 @@ } }, "node_modules/@ethersphere/bee-js": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@ethersphere/bee-js/-/bee-js-3.3.1.tgz", - "integrity": "sha512-4XiyUD9sYZJ5Wac4/2oRKDfoUlQjea3xxMj80nqsDCUfrUTzIGwTdRbLSA7LhINbCZMP7vn9EVRq/DYe0kFGOA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@ethersphere/bee-js/-/bee-js-5.1.0.tgz", + "integrity": "sha512-DJERG2F0Qjxsu3gZ9e7w2yc83/EdvN+gRIKczkNW0arpXn6IBphK21esGURqkhasmrbgPPZ1pA178BIAIb6/hg==", "dependencies": { + "@ethersphere/swarm-cid": "^0.1.0", "@types/readable-stream": "^2.3.13", - "bufferutil": "^4.0.3", - "cross-blob": "^2.0.1", + "bufferutil": "^4.0.6", "elliptic": "^6.5.4", + "fetch-blob": "2.1.2", "isomorphic-ws": "^4.0.1", "js-sha3": "^0.8.0", "ky": "^0.25.1", "ky-universal": "^0.8.2", "semver": "^7.3.5", "tar-js": "^0.3.0", - "utf-8-validate": "^5.0.8", - "web-streams-polyfill": "^3.1.0", - "ws": "^8.5.0" + "utf-8-validate": "^5.0.9", + "web-streams-polyfill": "^4.0.0-beta.1", + "ws": "^8.7.0" }, "engines": { - "bee": "1.4.3-1213e063", - "beeApiVersion": "2.0.0", - "beeDebugApiVersion": "1.2.1", - "node": ">=12.0.0", + "bee": "1.9.0-13a47043", + "beeApiVersion": "3.2.0", + "beeDebugApiVersion": "3.2.0", + "node": ">=14.0.0", "npm": ">=6.0.0" } }, "node_modules/@ethersphere/bee-js/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -2310,6 +2311,26 @@ "node": ">=10" } }, + "node_modules/@ethersphere/bee-js/node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@ethersphere/swarm-cid": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@ethersphere/swarm-cid/-/swarm-cid-0.1.0.tgz", + "integrity": "sha512-n+n+w5RJBPm7CiPf8TIgNFIRMo4bLh5bYpZxHObgCaxFW8WNZ4UGfqg+qjS/jEr+A3Mmp0lugKDvd8GnfFy7JQ==", + "dependencies": { + "multiformats": "^9.5.4" + }, + "engines": { + "node": ">=12.0.0", + "npm": ">=6.0.0" + } + }, "node_modules/@ethersproject/abi": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.6.1.tgz", @@ -4263,14 +4284,19 @@ } }, "node_modules/@types/readable-stream": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.13.tgz", - "integrity": "sha512-4JSCx8EUzaW9Idevt+9lsRAt1lcSccoQfE+AouM1gk8sFxnnytKNIO3wTl9Dy+4m6jRJ1yXhboLHHT/LXBQiEw==", + "version": "2.3.15", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.15.tgz", + "integrity": "sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==", "dependencies": { "@types/node": "*", - "safe-buffer": "*" + "safe-buffer": "~5.1.1" } }, + "node_modules/@types/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -5351,11 +5377,6 @@ "ieee754": "^1.1.13" } }, - "node_modules/blob-polyfill": { - "version": "5.0.20210201", - "resolved": "https://registry.npmjs.org/blob-polyfill/-/blob-polyfill-5.0.20210201.tgz", - "integrity": "sha512-SrH6IG6aXL9pCgSysBCiDpGcAJ1j6/c1qCwR3sTEQJhb+MTk6FITNA6eW6WNYQDNZVi4Z9GjxH5v2MMTv59CrQ==" - }, "node_modules/bn.js": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", @@ -5833,18 +5854,6 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, - "node_modules/cross-blob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/cross-blob/-/cross-blob-2.0.1.tgz", - "integrity": "sha512-ARuKPPo3I6DSqizal4UCyMCiGPQdMpMJS3Owx6Lleuh26vSt2UnfWRwbMLCYqbJUrcol+KzGVSLR91ezSHP80A==", - "dependencies": { - "blob-polyfill": "^5.0.20210201", - "fetch-blob": "^2.1.2" - }, - "engines": { - "node": "^10.17.0 || >=12.3.0" - } - }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -5910,6 +5919,14 @@ "node": ">=8" } }, + "node_modules/data-uri-to-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", + "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==", + "engines": { + "node": ">= 6" + } + }, "node_modules/data-urls": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", @@ -10942,30 +10959,6 @@ } } }, - "node_modules/ky-universal/node_modules/data-uri-to-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", - "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/ky-universal/node_modules/node-fetch": { - "version": "3.0.0-beta.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.0.0-beta.9.tgz", - "integrity": "sha512-RdbZCEynH2tH46+tj0ua9caUHVWrd/RHnRfvly2EVdqGmI3ndS1Vn/xjm5KuGejDt2RNDQsVRLPNd2QPwcewVg==", - "dependencies": { - "data-uri-to-buffer": "^3.0.1", - "fetch-blob": "^2.1.1" - }, - "engines": { - "node": "^10.17 || >=12.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, "node_modules/lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", @@ -11387,6 +11380,11 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==" + }, "node_modules/multimatch": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz", @@ -11454,6 +11452,22 @@ "node": ">= 10.13" } }, + "node_modules/node-fetch": { + "version": "3.0.0-beta.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.0.0-beta.9.tgz", + "integrity": "sha512-RdbZCEynH2tH46+tj0ua9caUHVWrd/RHnRfvly2EVdqGmI3ndS1Vn/xjm5KuGejDt2RNDQsVRLPNd2QPwcewVg==", + "dependencies": { + "data-uri-to-buffer": "^3.0.1", + "fetch-blob": "^2.1.1" + }, + "engines": { + "node": "^10.17 || >=12.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/node-gyp-build": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", @@ -12663,6 +12677,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, "funding": [ { "type": "github", @@ -13264,7 +13279,7 @@ "node_modules/tar-js": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/tar-js/-/tar-js-0.3.0.tgz", - "integrity": "sha1-aUmqv7C6GLsVYq5RpDn9DzAYOhc=", + "integrity": "sha512-9uqP2hJUZNKRkwPDe5nXxXdzo6w+BFBPq9x/tyi5/U/DneuSesO/HMb0y5TeWpfcv49YDJTs7SrrZeeu8ZHWDA==", "engines": { "node": "*" } @@ -14007,9 +14022,11 @@ } }, "node_modules/web-streams-polyfill": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz", - "integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "optional": true, + "peer": true, "engines": { "node": ">= 8" } @@ -14496,15 +14513,15 @@ } }, "node_modules/ws": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", - "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.1.tgz", + "integrity": "sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -16161,35 +16178,49 @@ } }, "@ethersphere/bee-js": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@ethersphere/bee-js/-/bee-js-3.3.1.tgz", - "integrity": "sha512-4XiyUD9sYZJ5Wac4/2oRKDfoUlQjea3xxMj80nqsDCUfrUTzIGwTdRbLSA7LhINbCZMP7vn9EVRq/DYe0kFGOA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@ethersphere/bee-js/-/bee-js-5.1.0.tgz", + "integrity": "sha512-DJERG2F0Qjxsu3gZ9e7w2yc83/EdvN+gRIKczkNW0arpXn6IBphK21esGURqkhasmrbgPPZ1pA178BIAIb6/hg==", "requires": { + "@ethersphere/swarm-cid": "^0.1.0", "@types/readable-stream": "^2.3.13", - "bufferutil": "^4.0.3", - "cross-blob": "^2.0.1", + "bufferutil": "^4.0.6", "elliptic": "^6.5.4", + "fetch-blob": "2.1.2", "isomorphic-ws": "^4.0.1", "js-sha3": "^0.8.0", "ky": "^0.25.1", "ky-universal": "^0.8.2", "semver": "^7.3.5", "tar-js": "^0.3.0", - "utf-8-validate": "^5.0.8", - "web-streams-polyfill": "^3.1.0", - "ws": "^8.5.0" + "utf-8-validate": "^5.0.9", + "web-streams-polyfill": "^4.0.0-beta.1", + "ws": "^8.7.0" }, "dependencies": { "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "requires": { "lru-cache": "^6.0.0" } + }, + "web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==" } } }, + "@ethersphere/swarm-cid": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@ethersphere/swarm-cid/-/swarm-cid-0.1.0.tgz", + "integrity": "sha512-n+n+w5RJBPm7CiPf8TIgNFIRMo4bLh5bYpZxHObgCaxFW8WNZ4UGfqg+qjS/jEr+A3Mmp0lugKDvd8GnfFy7JQ==", + "requires": { + "multiformats": "^9.5.4" + } + }, "@ethersproject/abi": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.6.1.tgz", @@ -17602,12 +17633,19 @@ } }, "@types/readable-stream": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.13.tgz", - "integrity": "sha512-4JSCx8EUzaW9Idevt+9lsRAt1lcSccoQfE+AouM1gk8sFxnnytKNIO3wTl9Dy+4m6jRJ1yXhboLHHT/LXBQiEw==", + "version": "2.3.15", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.15.tgz", + "integrity": "sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==", "requires": { "@types/node": "*", - "safe-buffer": "*" + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } } }, "@types/stack-utils": { @@ -18446,11 +18484,6 @@ } } }, - "blob-polyfill": { - "version": "5.0.20210201", - "resolved": "https://registry.npmjs.org/blob-polyfill/-/blob-polyfill-5.0.20210201.tgz", - "integrity": "sha512-SrH6IG6aXL9pCgSysBCiDpGcAJ1j6/c1qCwR3sTEQJhb+MTk6FITNA6eW6WNYQDNZVi4Z9GjxH5v2MMTv59CrQ==" - }, "bn.js": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", @@ -18814,15 +18847,6 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, - "cross-blob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/cross-blob/-/cross-blob-2.0.1.tgz", - "integrity": "sha512-ARuKPPo3I6DSqizal4UCyMCiGPQdMpMJS3Owx6Lleuh26vSt2UnfWRwbMLCYqbJUrcol+KzGVSLR91ezSHP80A==", - "requires": { - "blob-polyfill": "^5.0.20210201", - "fetch-blob": "^2.1.2" - } - }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -18878,6 +18902,11 @@ "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", "dev": true }, + "data-uri-to-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", + "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==" + }, "data-urls": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", @@ -22598,22 +22627,6 @@ "requires": { "abort-controller": "^3.0.0", "node-fetch": "3.0.0-beta.9" - }, - "dependencies": { - "data-uri-to-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", - "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==" - }, - "node-fetch": { - "version": "3.0.0-beta.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.0.0-beta.9.tgz", - "integrity": "sha512-RdbZCEynH2tH46+tj0ua9caUHVWrd/RHnRfvly2EVdqGmI3ndS1Vn/xjm5KuGejDt2RNDQsVRLPNd2QPwcewVg==", - "requires": { - "data-uri-to-buffer": "^3.0.1", - "fetch-blob": "^2.1.1" - } - } } }, "lazy-cache": { @@ -22946,6 +22959,11 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==" + }, "multimatch": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz", @@ -22997,6 +23015,15 @@ "propagate": "^2.0.0" } }, + "node-fetch": { + "version": "3.0.0-beta.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.0.0-beta.9.tgz", + "integrity": "sha512-RdbZCEynH2tH46+tj0ua9caUHVWrd/RHnRfvly2EVdqGmI3ndS1Vn/xjm5KuGejDt2RNDQsVRLPNd2QPwcewVg==", + "requires": { + "data-uri-to-buffer": "^3.0.1", + "fetch-blob": "^2.1.1" + } + }, "node-gyp-build": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", @@ -23874,7 +23901,8 @@ "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -24349,7 +24377,7 @@ "tar-js": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/tar-js/-/tar-js-0.3.0.tgz", - "integrity": "sha1-aUmqv7C6GLsVYq5RpDn9DzAYOhc=" + "integrity": "sha512-9uqP2hJUZNKRkwPDe5nXxXdzo6w+BFBPq9x/tyi5/U/DneuSesO/HMb0y5TeWpfcv49YDJTs7SrrZeeu8ZHWDA==" }, "tar-stream": { "version": "2.2.0", @@ -24891,9 +24919,11 @@ } }, "web-streams-polyfill": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz", - "integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==" + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "optional": true, + "peer": true }, "webidl-conversions": { "version": "6.1.0", @@ -25233,9 +25263,9 @@ } }, "ws": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", - "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.1.tgz", + "integrity": "sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==", "requires": {} }, "xml-name-validator": { diff --git a/package.json b/package.json index b6d7fd8f..3ad2f772 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "depcheck": "depcheck ." }, "dependencies": { - "@ethersphere/bee-js": "^3.1.0", + "@ethersphere/bee-js": "^5.1.0", "@fairdatasociety/fdp-contracts-js": "^2.0.1", "crypto-js": "^4.1.1", "ethers": "^5.5.2", diff --git a/src/account/account-data.ts b/src/account/account-data.ts index 5323f846..d0da9627 100644 --- a/src/account/account-data.ts +++ b/src/account/account-data.ts @@ -1,8 +1,8 @@ import { utils, Wallet } from 'ethers' import { + assertAccount, assertMnemonic, assertPassword, - assertRegistrationAccount, assertUsername, CHUNK_ALREADY_EXISTS_ERROR, HD_PATH, @@ -47,15 +47,13 @@ export class AccountData { } /** - * Sets FDP account from seed - * - * With the help of the seed, account data can be managed, but cannot register a new account + * Sets FDP account from a seed * * @param seed data extracted from mnemonic phrase or from uploaded account */ setAccountFromSeed(seed: Uint8Array): void { - // correct public key can't be extracted from seed - this.publicKey = undefined + const hdNode = utils.HDNode.fromSeed(seed).derivePath(HD_PATH) + this.publicKey = new utils.SigningKey(hdNode.privateKey).publicKey this.connectWalletWithENS(seed) } @@ -171,7 +169,7 @@ export class AccountData { async register(username: string, password: string): Promise { assertUsername(username) assertPassword(password) - assertRegistrationAccount(this) + assertAccount(this) const wallet = this.wallet! @@ -203,7 +201,7 @@ export class AccountData { * @param username FDP username */ async isPublicKeyEqual(username: string): Promise { - assertRegistrationAccount(this) + assertAccount(this) try { return (await this.ens.getPublicKey(username)) === this.publicKey @@ -219,7 +217,7 @@ export class AccountData { * @param password FDP password */ async reuploadPortableAccount(username: string, password: string): Promise { - assertRegistrationAccount(this) + assertAccount(this) const wallet = this.wallet! const seed = CryptoJS.enc.Hex.parse(removeZeroFromHex(bytesToHex(this.seed!))) diff --git a/src/account/utils.ts b/src/account/utils.ts index f0bea10c..f7061579 100644 --- a/src/account/utils.ts +++ b/src/account/utils.ts @@ -141,19 +141,9 @@ export function assertAccount(value: unknown): asserts value is AccountData { if (!data.seed) { throw new Error('Account seed not found') } -} - -/** - * Asserts whether an account is defined - * - * @param value instance of AccountData to check - */ -export function assertRegistrationAccount(value: unknown): asserts value is AccountData { - const data = value as AccountData - assertAccount(value) if (!data.publicKey) { - throw new Error('Account public key not found') + throw new Error('Public key is empty') } } diff --git a/src/cache/types.ts b/src/cache/types.ts new file mode 100644 index 00000000..4d3bc23a --- /dev/null +++ b/src/cache/types.ts @@ -0,0 +1,57 @@ +import { Epoch } from '../feed/lookup/epoch' + +export const DEFAULT_CACHE_OPTIONS: CacheOptions = { + isUseCache: true, +} + +/** + * Cache processor options + */ +export interface ProcessCacheDataOptions { + key: string + onGetData: () => Promise + onRecoverData: (data: CacheEpochDataPrepared) => Promise + cacheInfo?: CacheInfo +} + +/** + * Combined cache information + */ +export interface CacheInfo { + object: CacheObject + options: CacheOptions +} + +/** + * Serializable epoch data for caching + */ +export interface CacheEpochData { + epoch?: { + level: number + time: number + } + data?: string +} + +/** + * Recovered epoch data from cache + */ +export interface CacheEpochDataPrepared { + epoch?: Epoch + data?: string +} + +/** + * Object contains cache info + */ +export interface CacheObject { + [key: string]: CacheEpochData +} + +/** + * Configuration for caching + */ +export interface CacheOptions { + isUseCache: boolean + onSaveCache?: (cache: CacheObject) => Promise +} diff --git a/src/cache/utils.ts b/src/cache/utils.ts new file mode 100644 index 00000000..f21c4db3 --- /dev/null +++ b/src/cache/utils.ts @@ -0,0 +1,74 @@ +import CryptoJS from 'crypto-js' +import { CacheEpochData, CacheEpochDataPrepared, CacheInfo, CacheObject, ProcessCacheDataOptions } from './types' +import { Epoch } from '../feed/lookup/epoch' + +/** + * Creates cache key + */ +export function getCacheKey(...keys: string[]): string { + const key = [...keys].filter(item => Boolean(item)).join(':') + + return CryptoJS.enc.Hex.stringify(CryptoJS.SHA256(key)) +} + +/** + * Gets prepared cache value + */ +export function getEpochCacheData(cacheObject: CacheObject, key: string): CacheEpochDataPrepared | undefined { + const data = cacheObject[key] + + return data === undefined + ? undefined + : { + epoch: data?.epoch && new Epoch(data.epoch.level, data.epoch.time), + data: data?.data, + } +} + +/** + * Sets epoch cache data + */ +export async function setEpochCache(cacheInfo: CacheInfo, key: string, value: CacheEpochData): Promise { + if (!cacheInfo.options.isUseCache) { + return + } + + cacheInfo.object[key] = value + + if (cacheInfo.options.onSaveCache) { + await cacheInfo.options.onSaveCache(cacheInfo.object) + } +} + +/** + * Process data with caching + * + * @param key caching key + * @param onGetData callback for data that should be cached + * @param onRecoverData callback for recovering data from cache + * @param cacheInfo cache info + */ +export async function processCacheData({ + key, + onGetData, + onRecoverData, + cacheInfo, +}: ProcessCacheDataOptions): Promise { + const isUseCache = cacheInfo?.options?.isUseCache + const value = isUseCache ? getEpochCacheData(cacheInfo.object, key) : undefined + + if (value !== undefined) { + return onRecoverData(value) + } + + const cacheData = await onGetData() + + if (isUseCache) { + await setEpochCache(cacheInfo, key, cacheData) + } + + const { epoch: cacheEpoch, data } = cacheData + const epoch = cacheEpoch && new Epoch(cacheEpoch.level, cacheEpoch.time) + + return onRecoverData({ epoch, data }) +} diff --git a/src/connection/connection.ts b/src/connection/connection.ts index 9357c32d..7cb9564f 100644 --- a/src/connection/connection.ts +++ b/src/connection/connection.ts @@ -1,9 +1,15 @@ import { BatchId, Bee } from '@ethersphere/bee-js' import { Options } from '../types' +import { CacheInfo } from '../cache/types' /** * Holder for Bee instance and BatchId */ export class Connection { - constructor(public readonly bee: Bee, public readonly postageBatchId: BatchId, public readonly options?: Options) {} + constructor( + public readonly bee: Bee, + public readonly postageBatchId: BatchId, + public readonly cacheInfo: CacheInfo, + public readonly options?: Options, + ) {} } diff --git a/src/content-items/types.ts b/src/content-items/types.ts index cc536d21..2d638abf 100644 --- a/src/content-items/types.ts +++ b/src/content-items/types.ts @@ -1,5 +1,15 @@ import { Epoch } from '../feed/lookup/epoch' import { RawDirectoryMetadata, RawFileMetadata } from '../pod/types' +import { RequestOptions } from '@ethersphere/bee-js' +import { CacheInfo } from '../cache/types' + +/** + * Download data options + */ +export interface DownloadOptions { + requestOptions?: RequestOptions + cacheInfo?: CacheInfo +} /** * Metadata of a file or directory with epoch diff --git a/src/content-items/utils.ts b/src/content-items/utils.ts index 7531aa3d..0bfb558f 100644 --- a/src/content-items/utils.ts +++ b/src/content-items/utils.ts @@ -14,16 +14,16 @@ import CryptoJS from 'crypto-js' * @param path path with information * @param address Ethereum address of the pod which owns the path * @param podPassword bytes for data encryption from pod metadata - * @param downloadOptions options for downloading + * @param requestOptions options for downloading */ export async function getRawMetadata( bee: Bee, path: string, address: EthAddress, podPassword: PodPasswordBytes, - downloadOptions?: RequestOptions, + requestOptions?: RequestOptions, ): Promise { - const feedData = await getFeedData(bee, path, address, downloadOptions) + const feedData = await getFeedData(bee, path, address, requestOptions) const data = decryptJson(podPassword, feedData.data.chunkContent()) let metadata @@ -47,16 +47,16 @@ export async function getRawMetadata( * @param bee Bee instance * @param fullPath full path to the item * @param address uploader address - * @param downloadOptions options for downloading + * @param requestOptions options for downloading */ export async function isItemExists( bee: Bee, fullPath: string, address: EthAddress, - downloadOptions: RequestOptions | undefined, + requestOptions: RequestOptions | undefined, ): Promise { try { - await getFeedData(bee, fullPath, address, downloadOptions) + await getFeedData(bee, fullPath, address, requestOptions) return true } catch (e) { diff --git a/src/directory/directory.ts b/src/directory/directory.ts index 1b08fe7b..bd941502 100644 --- a/src/directory/directory.ts +++ b/src/directory/directory.ts @@ -45,7 +45,7 @@ export class Directory { podAddress, pod.password, isRecursive, - this.accountData.connection.options?.downloadOptions, + this.accountData.connection.options?.requestOptions, ) } @@ -58,10 +58,15 @@ export class Directory { async create(podName: string, fullPath: string): Promise { assertAccount(this.accountData) assertPodName(podName) - const downloadOptions = this.accountData.connection.options?.downloadOptions const { podWallet, pod } = await getExtendedPodsListByAccountData(this.accountData, podName) - return createDirectory(this.accountData.connection, fullPath, podWallet, pod.password, downloadOptions) + return createDirectory( + this.accountData.connection, + fullPath, + podWallet, + pod.password, + this.accountData.connection.options?.requestOptions, + ) } /** @@ -75,7 +80,6 @@ export class Directory { assertPodName(podName) const pathInfo = extractPathInfo(fullPath) const connection = this.accountData.connection - const downloadOptions = connection.options?.downloadOptions const { podWallet, pod } = await getExtendedPodsListByAccountData(this.accountData, podName) await removeEntryFromDirectory( @@ -85,7 +89,7 @@ export class Directory { pathInfo.path, pathInfo.filename, false, - downloadOptions, + connection.options?.requestOptions, ) } @@ -99,7 +103,6 @@ export class Directory { async upload(podName: string, filesSource: string | FileList, options?: UploadDirectoryOptions): Promise { assertAccount(this.accountData) assertPodName(podName) - const downloadOptions = this.accountData.connection.options?.downloadOptions const { podWallet, pod } = await getExtendedPodsListByAccountData(this.accountData, podName) options = { ...DEFAULT_UPLOAD_DIRECTORY_OPTIONS, ...options } @@ -131,7 +134,13 @@ export class Directory { ) for (const directory of directoriesToCreate) { try { - await createDirectory(this.accountData.connection, directory, podWallet, pod.password, downloadOptions) + await createDirectory( + this.accountData.connection, + directory, + podWallet, + pod.password, + this.accountData.connection.options?.requestOptions, + ) } catch (e) { const error = e as Error diff --git a/src/fdp-storage.ts b/src/fdp-storage.ts index 015f1666..63eedca7 100644 --- a/src/fdp-storage.ts +++ b/src/fdp-storage.ts @@ -6,6 +6,7 @@ import { Options } from './types' import { Directory } from './directory/directory' import { File } from './file/file' import { ENS } from '@fairdatasociety/fdp-contracts-js' +import { CacheInfo, DEFAULT_CACHE_OPTIONS } from './cache/types' export class FdpStorage { public readonly connection: Connection @@ -14,9 +15,14 @@ export class FdpStorage { public readonly directory: Directory public readonly file: File public readonly ens: ENS + public readonly cache: CacheInfo constructor(beeUrl: string, postageBatchId: BatchId, options?: Options) { - this.connection = new Connection(new Bee(beeUrl), postageBatchId, options) + this.cache = { + object: {}, + options: options?.cacheOptions || DEFAULT_CACHE_OPTIONS, + } + this.connection = new Connection(new Bee(beeUrl), postageBatchId, this.cache, options) this.ens = new ENS(options?.ensOptions, null, options?.ensDomain) this.account = new AccountData(this.connection, this.ens) this.personalStorage = new PersonalStorage(this.account) diff --git a/src/feed/api.ts b/src/feed/api.ts index 428fd0f3..06ba386a 100644 --- a/src/feed/api.ts +++ b/src/feed/api.ts @@ -15,13 +15,13 @@ import { encryptBytes, PodPasswordBytes } from '../utils/encryption' * @param bee Bee client * @param topic topic for calculation swarm chunk * @param address Ethereum address for calculation swarm chunk - * @param options download chunk options + * @param requestOptions download chunk requestOptions */ export async function getFeedData( bee: Bee, topic: string, address: Utils.EthAddress | Uint8Array, - options?: RequestOptions, + requestOptions?: RequestOptions, ): Promise { const topicHash = bmtHashString(topic) @@ -29,7 +29,7 @@ export async function getFeedData( const tempId = getId(topicHash, time, epoch.level) const chunkReference = bytesToHex(Utils.keccak256Hash(tempId.buffer, address.buffer)) - return bee.downloadChunk(chunkReference, options) + return bee.downloadChunk(chunkReference, requestOptions) }) } diff --git a/src/file/file.ts b/src/file/file.ts index eca5768b..80c06e19 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -45,7 +45,7 @@ export class File { fullPath, podAddress, pod.password, - this.accountData.connection.options?.downloadOptions, + this.accountData.connection.options?.requestOptions, ) } diff --git a/src/pod/api.ts b/src/pod/api.ts index 33faead4..b9e380ae 100644 --- a/src/pod/api.ts +++ b/src/pod/api.ts @@ -1,32 +1,45 @@ -import { Bee, RequestOptions } from '@ethersphere/bee-js' -import { LookupAnswer } from '../feed/types' +import { Bee } from '@ethersphere/bee-js' import { getFeedData } from '../feed/api' import { POD_TOPIC } from './personal-storage' import { ExtendedPodInfo, extractPods, PodsInfo } from './utils' -import { getWalletByIndex, prepareEthAddress, privateKeyToBytes } from '../utils/wallet' +import { prepareEthAddress, privateKeyToBytes } from '../utils/wallet' import { utils } from 'ethers' -import { PodsListPrepared } from './types' +import { DownloadOptions } from '../content-items/types' +import { getWalletByIndex } from '../utils/cache/wallet' +import { getPodsList as getPodsListCached } from './cache/api' /** * Gets pods list with lookup answer * * @param bee Bee instance * @param userWallet root wallet for downloading and decrypting data - * @param options request options + * @param downloadOptions request download */ -export async function getPodsList(bee: Bee, userWallet: utils.HDNode, options?: RequestOptions): Promise { - let lookupAnswer: LookupAnswer | undefined - let podsList: PodsListPrepared = { pods: [], sharedPods: [] } - +export async function getPodsList( + bee: Bee, + userWallet: utils.HDNode, + downloadOptions?: DownloadOptions, +): Promise { + let lookupAnswer try { - lookupAnswer = await getFeedData(bee, POD_TOPIC, prepareEthAddress(userWallet.address), options) - podsList = extractPods(lookupAnswer.data.chunkContent(), privateKeyToBytes(userWallet.privateKey)) + lookupAnswer = await getFeedData( + bee, + POD_TOPIC, + prepareEthAddress(userWallet.address), + downloadOptions?.requestOptions, + ) // eslint-disable-next-line no-empty } catch (e) {} + if (!lookupAnswer) { + throw new Error('Pods data can not be found') + } + + const podsList = extractPods(lookupAnswer.data.chunkContent(), privateKeyToBytes(userWallet.privateKey)) + return { podsList, - lookupAnswer, + epoch: lookupAnswer.epoch, } } @@ -44,21 +57,21 @@ export async function getExtendedPodsList( podName: string, userWallet: utils.HDNode, seed: Uint8Array, - downloadOptions?: RequestOptions, + downloadOptions?: DownloadOptions, ): Promise { - const podsInfo = await getPodsList(bee, userWallet, downloadOptions) - const pod = podsInfo.podsList.pods.find(item => item.name === podName) + const { podsList, epoch } = await getPodsListCached(bee, userWallet, downloadOptions) + const pod = podsList.pods.find(item => item.name === podName) if (!pod) { throw new Error(`Pod "${podName}" does not exist`) } - const podWallet = getWalletByIndex(seed, pod.index) + const podWallet = await getWalletByIndex(seed, pod.index, downloadOptions?.cacheInfo) return { pod, podAddress: prepareEthAddress(podWallet.address), podWallet, - lookupAnswer: podsInfo.lookupAnswer, + epoch, } } diff --git a/src/pod/cache/api.ts b/src/pod/cache/api.ts new file mode 100644 index 00000000..a136d1c9 --- /dev/null +++ b/src/pod/cache/api.ts @@ -0,0 +1,43 @@ +import { Bee } from '@ethersphere/bee-js' +import { utils } from 'ethers' +import { DownloadOptions } from '../../content-items/types' +import { jsonToPodsList, podListToJSON, PodsInfo } from '../utils' +import { CacheEpochData } from '../../cache/types' +import { getCacheKey, processCacheData } from '../../cache/utils' +import { getPodsList as getPodsNoCache } from '../api' + +/** + * Gets pods list with lookup answer + * + * @param bee Bee instance + * @param userWallet root wallet for downloading and decrypting data + * @param downloadOptions request download + */ +export async function getPodsList( + bee: Bee, + userWallet: utils.HDNode, + downloadOptions?: DownloadOptions, +): Promise { + return processCacheData({ + key: getCacheKey(userWallet.address), + onGetData: async (): Promise => { + const data = await getPodsNoCache(bee, userWallet, downloadOptions) + + return { + epoch: data.epoch, + data: podListToJSON(data.podsList.pods, data.podsList.sharedPods), + } + }, + onRecoverData: async (data): Promise => { + if (!(data.data && data.epoch)) { + throw new Error('Incorrect recovered cache data for pods list') + } + + return { + podsList: jsonToPodsList(data.data), + epoch: data.epoch, + } + }, + cacheInfo: downloadOptions?.cacheInfo, + }) +} diff --git a/src/pod/personal-storage.ts b/src/pod/personal-storage.ts index ff3738f5..1514f677 100644 --- a/src/pod/personal-storage.ts +++ b/src/pod/personal-storage.ts @@ -1,4 +1,4 @@ -import { SharedPod, PodReceiveOptions, PodShareInfo, PodsList, Pod } from './types' +import { SharedPod, PodReceiveOptions, PodShareInfo, PodsList, Pod, PodsListPrepared } from './types' import { assertAccount } from '../account/utils' import { writeFeedData } from '../feed/api' import { AccountData } from '../account/account-data' @@ -14,14 +14,17 @@ import { podsListPreparedToPodsList, podPreparedToPod, sharedPodPreparedToSharedPod, + podListToJSON, } from './utils' import { getUnixTimestamp } from '../utils/time' -import { getExtendedPodsList, getPodsList } from './api' +import { getExtendedPodsList } from './api' import { uploadBytes } from '../file/utils' import { stringToBytes } from '../utils/bytes' import { Reference, Utils } from '@ethersphere/bee-js' import { assertEncryptedReference, EncryptedReference } from '../utils/hex' import { prepareEthAddress, preparePrivateKey } from '../utils/wallet' +import { getCacheKey, setEpochCache } from '../cache/utils' +import { getPodsList } from './cache/api' export const POD_TOPIC = 'Pods' @@ -36,13 +39,22 @@ export class PersonalStorage { async list(): Promise { assertAccount(this.accountData) - const data = await getPodsList( - this.accountData.connection.bee, - this.accountData.wallet!, - this.accountData.connection.options?.downloadOptions, - ) + let podsList: PodsListPrepared = { + pods: [], + sharedPods: [], + } - return podsListPreparedToPodsList(data.podsList) + try { + podsList = ( + await getPodsList(this.accountData.connection.bee, this.accountData.wallet!, { + requestOptions: this.accountData.connection.options?.requestOptions, + cacheInfo: this.accountData.connection.cacheInfo, + }) + ).podsList + // eslint-disable-next-line no-empty + } catch (e) {} + + return podsListPreparedToPodsList(podsList) } /** @@ -76,11 +88,10 @@ export class PersonalStorage { async delete(name: string): Promise { assertAccount(this.accountData) name = name.trim() - const podsInfo = await getPodsList( - this.accountData.connection.bee, - this.accountData.wallet!, - this.accountData.connection.options?.downloadOptions, - ) + const podsInfo = await getPodsList(this.accountData.connection.bee, this.accountData.wallet!, { + requestOptions: this.accountData.connection.options?.requestOptions, + cacheInfo: this.accountData.connection.cacheInfo, + }) assertPodsLength(podsInfo.podsList.pods.length) const pod = podsInfo.podsList.pods.find(item => item.name === name) @@ -93,14 +104,19 @@ export class PersonalStorage { const podsSharedFiltered = podsInfo.podsList.sharedPods.filter(item => item.name !== name) const allPodsData = podListToBytes(podsFiltered, podsSharedFiltered) const wallet = this.accountData.wallet! + const epoch = podsInfo.epoch.getNextEpoch(getUnixTimestamp()) await writeFeedData( this.accountData.connection, POD_TOPIC, allPodsData, wallet.privateKey, preparePrivateKey(wallet.privateKey), - podsInfo.lookupAnswer!.epoch.getNextEpoch(getUnixTimestamp()), + epoch, ) + await setEpochCache(this.accountData.connection.cacheInfo, getCacheKey(wallet.address), { + epoch, + data: podListToJSON(podsFiltered, podsSharedFiltered), + }) } /** @@ -115,13 +131,10 @@ export class PersonalStorage { assertPodName(name) const wallet = this.accountData.wallet! const address = prepareEthAddress(wallet.address) - const podInfo = await getExtendedPodsList( - this.accountData.connection.bee, - name, - wallet, - this.accountData.seed!, - this.accountData.connection.options?.downloadOptions, - ) + const podInfo = await getExtendedPodsList(this.accountData.connection.bee, name, wallet, this.accountData.seed!, { + requestOptions: this.accountData.connection.options?.requestOptions, + cacheInfo: this.accountData.connection.cacheInfo, + }) const data = stringToBytes( JSON.stringify(createPodShareInfo(name, podInfo.podAddress, address, podInfo.pod.password)), diff --git a/src/pod/utils.ts b/src/pod/utils.ts index 47211e19..c0ab3f25 100644 --- a/src/pod/utils.ts +++ b/src/pod/utils.ts @@ -11,7 +11,6 @@ import { } from './types' import { Bee, Data, Utils } from '@ethersphere/bee-js' import { bytesToString, stringToBytes, wordArrayToBytes } from '../utils/bytes' -import { LookupAnswer } from '../feed/types' import { utils } from 'ethers' import { getRawDirectoryMetadataBytes } from '../directory/adapter' import { @@ -26,11 +25,11 @@ import { isString, } from '../utils/type' import { bytesToHex, EncryptedReference, isHexEthAddress } from '../utils/hex' -import { getExtendedPodsList, getPodsList } from './api' +import { getExtendedPodsList } from './api' import { Epoch, getFirstEpoch } from '../feed/lookup/epoch' import { getUnixTimestamp } from '../utils/time' import { writeFeedData } from '../feed/api' -import { getWalletByIndex, preparePrivateKey } from '../utils/wallet' +import { preparePrivateKey } from '../utils/wallet' import { createRootDirectory } from '../directory/handler' import { POD_TOPIC } from './personal-storage' import { Connection } from '../connection/connection' @@ -39,6 +38,9 @@ import { decryptBytes, POD_PASSWORD_LENGTH, PodPasswordBytes } from '../utils/en import CryptoJS from 'crypto-js' import { jsonParse } from '../utils/json' import { DEFAULT_DIRECTORY_PERMISSIONS, getDirectoryMode } from '../directory/utils' +import { getCacheKey, setEpochCache } from '../cache/utils' +import { getWalletByIndex } from '../utils/cache/wallet' +import { getPodsList } from './cache/api' export const META_VERSION = 2 export const MAX_PODS_COUNT = 65536 @@ -49,7 +51,7 @@ export const MAX_POD_NAME_LENGTH = 64 */ export interface PodsInfo { podsList: PodsListPrepared - lookupAnswer: LookupAnswer | undefined + epoch: Epoch } /** @@ -59,7 +61,7 @@ export interface ExtendedPodInfo { pod: PodPrepared podWallet: utils.HDNode podAddress: Utils.EthAddress - lookupAnswer: LookupAnswer | undefined + epoch: Epoch } /* @@ -367,23 +369,26 @@ export async function createPod( pod.name = pod.name.trim() assertPodName(pod.name) - const podsInfo = await getPodsList(bee, userWallet, connection.options?.downloadOptions) - const nextIndex = podsInfo.podsList.pods.length + 1 + const { cacheInfo } = connection + let podsList: PodsListPrepared = { pods: [], sharedPods: [] } + let podsInfo + try { + podsInfo = await getPodsList(bee, userWallet, { + requestOptions: connection.options?.requestOptions, + cacheInfo, + }) + podsList = podsInfo.podsList + // eslint-disable-next-line no-empty + } catch (e) {} + const nextIndex = podsList.pods.length + 1 assertPodsLength(nextIndex) - const pods = podsInfo.podsList.pods - const sharedPods = podsInfo.podsList.sharedPods + const pods = podsList.pods + const sharedPods = podsList.sharedPods assertPodNameAvailable(pod.name, pods) assertSharedPodNameAvailable(pod.name, sharedPods) - let epoch: Epoch const currentTime = getUnixTimestamp() - - if (podsInfo.lookupAnswer) { - epoch = podsInfo.lookupAnswer.epoch.getNextEpoch(currentTime) - } else { - epoch = getFirstEpoch(currentTime) - } - + const epoch = podsInfo ? podsInfo.epoch.getNextEpoch(currentTime) : getFirstEpoch(currentTime) let realPod: PodPrepared | SharedPodPrepared if (isSharedPod(pod)) { @@ -409,10 +414,15 @@ export async function createPod( ) if (isPod(realPod)) { - const podWallet = getWalletByIndex(seed, nextIndex) + const podWallet = await getWalletByIndex(seed, nextIndex, cacheInfo) await createRootDirectory(connection, realPod.password, preparePrivateKey(podWallet.privateKey)) } + await setEpochCache(cacheInfo, getCacheKey(userWallet.address), { + epoch, + data: podListToJSON(pods, sharedPods), + }) + return realPod } @@ -426,13 +436,10 @@ export async function getExtendedPodsListByAccountData( accountData: AccountData, podName: string, ): Promise { - return getExtendedPodsList( - accountData.connection.bee, - podName, - accountData.wallet!, - accountData.seed!, - accountData.connection.options?.downloadOptions, - ) + return getExtendedPodsList(accountData.connection.bee, podName, accountData.wallet!, accountData.seed!, { + requestOptions: accountData.connection.options?.requestOptions, + cacheInfo: accountData.connection.cacheInfo, + }) } /** diff --git a/src/types.ts b/src/types.ts index 98c3bca2..be90776d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,6 @@ import { RequestOptions } from '@ethersphere/bee-js' import { Environment } from '@fairdatasociety/fdp-contracts-js' +import { CacheOptions } from './cache/types' export { DirectoryItem, FileItem } from './content-items/types' @@ -8,9 +9,9 @@ export { DirectoryItem, FileItem } from './content-items/types' */ export interface Options { /** - * Downloads options for requests + * Request options */ - downloadOptions?: RequestOptions + requestOptions?: RequestOptions /** * FDP-contracts options */ @@ -19,4 +20,8 @@ export interface Options { * ENS domain for usernames */ ensDomain?: string + /** + * Cache options + */ + cacheOptions?: CacheOptions } diff --git a/src/utils/cache/wallet.ts b/src/utils/cache/wallet.ts new file mode 100644 index 00000000..be89c963 --- /dev/null +++ b/src/utils/cache/wallet.ts @@ -0,0 +1,27 @@ +import { CacheInfo } from '../../cache/types' +import { utils } from 'ethers' +import { getCacheKey, processCacheData } from '../../cache/utils' +import { bytesToHex } from '../hex' +import { getWalletByIndex as getWallet } from '../wallet' + +/** + * Get Hierarchical Deterministic Wallet from seed by index with caching + * + * @param seed data for wallet creation + * @param index wallet index + * @param cacheInfo cache info + */ +export async function getWalletByIndex(seed: Uint8Array, index: number, cacheInfo?: CacheInfo): Promise { + return processCacheData({ + key: getCacheKey(bytesToHex(seed), index.toString()), + onGetData: async () => ({ data: getWallet(seed, index).extendedKey }), + onRecoverData: async data => { + if (!data.data) { + throw new Error(`Incorrect recovered cache data for the wallet by index ${index}`) + } + + return utils.HDNode.fromExtendedKey(data.data) + }, + cacheInfo, + }) +} diff --git a/src/utils/wallet.ts b/src/utils/wallet.ts index 3da1d832..65eb45b6 100644 --- a/src/utils/wallet.ts +++ b/src/utils/wallet.ts @@ -3,7 +3,7 @@ import { PrivateKeyBytes, Utils } from '@ethersphere/bee-js' import { removeZeroFromHex } from '../account/utils' /** - * Get Hierarchal Deterministic Wallet from seed by index + * Get Hierarchical Deterministic Wallet from seed by index * * @param seed data for wallet creation * @param index wallet index diff --git a/test/integration/fdp-class.spec.ts b/test/integration/fdp-class.spec.ts index 149c0b37..27f1f9b9 100644 --- a/test/integration/fdp-class.spec.ts +++ b/test/integration/fdp-class.spec.ts @@ -13,23 +13,27 @@ import { createUserV1 } from '../../src/account/account' import { PodShareInfo, RawFileMetadata } from '../../src/pod/types' import { FileShareInfo } from '../../src/file/types' import { getFeedData } from '../../src/feed/api' +import * as feedApi from '../../src/feed/api' import { POD_TOPIC } from '../../src/pod/personal-storage' import { decryptBytes } from '../../src/utils/encryption' import { Wallet } from 'ethers' import { removeZeroFromHex } from '../../src/account/utils' import { bytesToString, wrapBytesWithHelpers } from '../../src/utils/bytes' -import { getWalletByIndex, mnemonicToSeed, prepareEthAddress } from '../../src/utils/wallet' +import { mnemonicToSeed, prepareEthAddress } from '../../src/utils/wallet' import { assertEncryptedReference } from '../../src/utils/hex' import { base64toReference } from '../../src/file/utils' import path from 'path' import { getNodeFileContent } from '../../src/directory/utils' import { ETH_ADDR_HEX_LENGTH } from '../../src/utils/type' +import * as walletApi from '../../src/utils/wallet' +import { HIGHEST_LEVEL } from '../../src/feed/lookup/epoch' +import { getWalletByIndex } from '../../src/utils/cache/wallet' jest.setTimeout(400000) describe('Fair Data Protocol class', () => { it('should strip trailing slash', () => { const fdp = new FdpStorage('http://localhost:1633/', batchId(), { - downloadOptions: { + requestOptions: { timeout: GET_FEED_DATA_TIMEOUT, }, }) @@ -679,7 +683,7 @@ describe('Fair Data Protocol class', () => { expect(encryptedText1).not.toContain(pod) expect(decryptedText1).toContain(pod) // HDNode with index 1 is for first pod - const node1 = getWalletByIndex(seed, 1) + const node1 = await getWalletByIndex(seed, 1) const rootDirectoryData = await getFeedData(bee, '/', prepareEthAddress(node1.address)) const encryptedText2 = rootDirectoryData.data.chunkContent().text() const encryptedBytes2 = rootDirectoryData.data.chunkContent() @@ -734,4 +738,172 @@ describe('Fair Data Protocol class', () => { expect(decryptedText6).toEqual(contentSmall) }) }) + + describe('Caching', () => { + it('should collect correct metrics without cache', async () => { + const writeFeedDataSpy = jest.spyOn(feedApi, 'writeFeedData') + const getFeedDataSpy = jest.spyOn(feedApi, 'getFeedData') + const getWalletByIndexSpy = jest.spyOn(walletApi, 'getWalletByIndex') + + const fdpNoCache = createFdp({ + isUseCache: false, + }) + + const pod1 = generateRandomHexString() + const pod2 = generateRandomHexString() + const fileSize = 100 + const fileContent = generateRandomHexString(fileSize) + const filename = generateRandomHexString() + '.txt' + const fullFilename = '/' + filename + fdpNoCache.account.createWallet() + + // no cache - create first pod + await fdpNoCache.personalStorage.create(pod1) + // for the first feed write it should be the highest level + expect(writeFeedDataSpy.mock.calls[0][5]?.level).toEqual(HIGHEST_LEVEL) + // getting info about pods from the network + expect(getFeedDataSpy).toBeCalledTimes(1) + // calculating wallet by index for the pod + expect(getWalletByIndexSpy).toBeCalledTimes(1) + + jest.clearAllMocks() + await fdpNoCache.personalStorage.create(pod2) + expect(writeFeedDataSpy.mock.calls[0][5]?.level).toEqual(HIGHEST_LEVEL - 1) + expect(getFeedDataSpy).toBeCalledTimes(1) + expect(getWalletByIndexSpy).toBeCalledTimes(1) + + jest.clearAllMocks() + await fdpNoCache.personalStorage.list() + // getting info about pods from the network + expect(getFeedDataSpy).toBeCalledTimes(1) + + jest.clearAllMocks() + await fdpNoCache.directory.read(pod1, '/', true) + // should not write any info + expect(writeFeedDataSpy).toBeCalledTimes(0) + // getting info about pods from the network and getting root info of the pod + expect(getFeedDataSpy).toBeCalledTimes(2) + // calculating wallet by index for the pod + expect(getWalletByIndexSpy).toBeCalledTimes(1) + + jest.clearAllMocks() + await fdpNoCache.file.uploadData(pod1, fullFilename, fileContent) + // write file metadata + update root dir + expect(writeFeedDataSpy).toBeCalledTimes(2) + // get pods info + check file exists + get parent metadata + expect(getFeedDataSpy).toBeCalledTimes(3) + // calc the pod wallet + expect(getWalletByIndexSpy).toBeCalledTimes(1) + + jest.clearAllMocks() + await fdpNoCache.file.delete(pod1, fullFilename) + // update root dir metadata + expect(writeFeedDataSpy).toBeCalledTimes(1) + // get pods info + update root dir metadata + expect(getFeedDataSpy).toBeCalledTimes(2) + // calc the pod wallet + expect(getWalletByIndexSpy).toBeCalledTimes(1) + + jest.clearAllMocks() + await fdpNoCache.personalStorage.delete(pod1) + // writing info about pods to the network + expect(writeFeedDataSpy).toBeCalledTimes(1) + // get pods info + expect(getFeedDataSpy).toBeCalledTimes(1) + expect(getWalletByIndexSpy).toBeCalledTimes(0) + + jest.restoreAllMocks() + }) + + it('should collect correct metrics with cache', async () => { + const writeFeedDataSpy = jest.spyOn(feedApi, 'writeFeedData') + const getFeedDataSpy = jest.spyOn(feedApi, 'getFeedData') + const getWalletByIndexSpy = jest.spyOn(walletApi, 'getWalletByIndex') + let cache = '' + const fdpWithCache = createFdp({ + isUseCache: true, + onSaveCache: async cacheObject => { + cache = JSON.stringify(cacheObject) + }, + }) + + const pod1 = generateRandomHexString() + const pod2 = generateRandomHexString() + const fileSize = 100 + const fileContent = generateRandomHexString(fileSize) + const filename = generateRandomHexString() + '.txt' + const fullFilename = '/' + filename + const wallet = fdpWithCache.account.createWallet() + + // with cache - create first pod + await fdpWithCache.personalStorage.create(pod1) + // for the first feed write it should be the highest level + expect(writeFeedDataSpy.mock.calls[0][5]?.level).toEqual(HIGHEST_LEVEL) + // getting info about pods from the network + expect(getFeedDataSpy).toBeCalledTimes(1) + // calculating wallet by index for the pod + expect(getWalletByIndexSpy).toBeCalledTimes(1) + + jest.clearAllMocks() + await fdpWithCache.personalStorage.create(pod2) + // data about pods shouldn't be retrieved + expect(getFeedDataSpy).toBeCalledTimes(0) + // should be calculated correct level without getting previous one from the network + expect(writeFeedDataSpy.mock.calls[0][5]?.level).toEqual(HIGHEST_LEVEL - 1) + // calc a wallet for the new pod + expect(getWalletByIndexSpy).toBeCalledTimes(1) + + jest.clearAllMocks() + await fdpWithCache.personalStorage.list() + // the list cached + expect(getFeedDataSpy).toBeCalledTimes(0) + + jest.clearAllMocks() + await fdpWithCache.directory.read(pod1, '/', true) + // should not write any info + expect(writeFeedDataSpy).toBeCalledTimes(0) + // should get root info of the pod only. should not get info about pods from the network + expect(getFeedDataSpy).toBeCalledTimes(1) + // shouldn't calculate the pod wallet again + expect(getWalletByIndexSpy).toBeCalledTimes(0) + + jest.clearAllMocks() + await fdpWithCache.file.uploadData(pod1, fullFilename, fileContent) + // write file metadata + update root dir + expect(writeFeedDataSpy).toBeCalledTimes(2) + // check file exists + get parent metadata. pods info is cached + expect(getFeedDataSpy).toBeCalledTimes(2) + // the pod wallet is cached + expect(getWalletByIndexSpy).toBeCalledTimes(0) + + jest.clearAllMocks() + await fdpWithCache.file.delete(pod1, fullFilename) + // update root dir metadata + expect(writeFeedDataSpy).toBeCalledTimes(1) + // update root dir metadata only. should not get pods info + expect(getFeedDataSpy).toBeCalledTimes(1) + // the pod wallet is cached + expect(getWalletByIndexSpy).toBeCalledTimes(0) + + jest.clearAllMocks() + await fdpWithCache.personalStorage.delete(pod1) + // writing info about pods to the network + expect(writeFeedDataSpy).toBeCalledTimes(1) + // should not get pods info + expect(getFeedDataSpy).toBeCalledTimes(0) + expect(getWalletByIndexSpy).toBeCalledTimes(0) + + // recovering cache data + const fdpRecovered = createFdp({ + isUseCache: true, + }) + fdpRecovered.cache.object = JSON.parse(cache) + fdpRecovered.account.setAccountFromMnemonic(wallet.mnemonic.phrase) + const pods = await fdpRecovered.personalStorage.list() + expect(pods.pods).toHaveLength(1) + expect(pods.pods[0].name).toEqual(pod2) + + jest.restoreAllMocks() + }) + }) }) diff --git a/test/integration/speed-check.spec.ts b/test/integration/speed-check.spec.ts new file mode 100644 index 00000000..e9a54aa8 --- /dev/null +++ b/test/integration/speed-check.spec.ts @@ -0,0 +1,50 @@ +import { createFdp, generateRandomHexString } from '../utils' + +jest.setTimeout(400000) +it('Fair Data Protocol speed check', async () => { + const fdpNoCache = createFdp({ + isUseCache: false, + }) + const fdpCache = createFdp({ + isUseCache: true, + }) + + for (const item of [ + { + fdp: fdpNoCache, + title: 'FDP No Cache', + }, + { + fdp: fdpCache, + title: 'FDP With Cache', + }, + ]) { + const { fdp, title } = item + const pod1 = generateRandomHexString() + const pod2 = generateRandomHexString() + const dir1 = `/${generateRandomHexString()}` + const dir2 = `/${generateRandomHexString()}` + const fileSize = 100000 + const fileContent = generateRandomHexString(fileSize) + const filename = generateRandomHexString() + '.txt' + const fullFilename = '/' + filename + fdp.account.createWallet() + + // eslint-disable-next-line no-console + console.time(title) + await fdp.personalStorage.create(pod1) + await fdp.personalStorage.create(pod2) + await fdp.directory.create(pod1, dir1) + await fdp.directory.create(pod1, dir2) + await fdp.directory.create(pod2, dir1) + await fdp.directory.create(pod2, dir2) + await fdp.file.uploadData(pod1, fullFilename, fileContent) + await fdp.file.uploadData(pod2, fullFilename, fileContent) + await fdp.file.downloadData(pod1, fullFilename) + await fdp.file.downloadData(pod2, fullFilename) + await fdp.directory.read(pod1, '/', true) + await fdp.directory.read(pod2, '/', true) + // eslint-disable-next-line no-console + console.timeEnd(title) + } +}) diff --git a/test/unit/cache/utils.spec.ts b/test/unit/cache/utils.spec.ts new file mode 100644 index 00000000..9dafe607 --- /dev/null +++ b/test/unit/cache/utils.spec.ts @@ -0,0 +1,63 @@ +import { getCacheKey } from '../../../src/cache/utils' +import CryptoJS from 'crypto-js' + +describe('cache/utils', () => { + it('getCacheKey', () => { + const examples = [ + { + input: { + username: 'satoshi', + pod: '', + path: '', + }, + expected: 'satoshi', + }, + { + input: { + username: 'satoshi', + pod: 'one', + path: '', + }, + expected: 'satoshi:one', + }, + { + input: { + username: 'satoshi', + pod: 'one', + path: '/', + }, + expected: 'satoshi:one:/', + }, + { + input: { + username: 'nakamoto', + pod: 'one-more', + path: '/dir-one', + }, + expected: 'nakamoto:one-more:/dir-one', + }, + { + input: { + username: 'satoshi', + pod: 'one', + path: '/dir-one/file', + }, + expected: 'satoshi:one:/dir-one/file', + }, + { + input: { + username: 'vitalik', + pod: 'my-pod', + path: '/dir-one/dir/', + }, + expected: 'vitalik:my-pod:/dir-one/dir/', + }, + ] + + for (const example of examples) { + const { username, pod, path } = example.input + const expectedSha256 = CryptoJS.enc.Hex.stringify(CryptoJS.SHA256(example.expected)) + expect(getCacheKey(username, pod, path)).toEqual(expectedSha256) + } + }) +}) diff --git a/test/utils.ts b/test/utils.ts index 013953ef..a8e52dab 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1,9 +1,10 @@ import crypto from 'crypto' import { BATCH_ID_HEX_LENGTH, BatchId, Bee, BeeDebug, Utils } from '@ethersphere/bee-js' -import { FdpStorage } from '../src' +import { FdpStorage, Options } from '../src' import { utils, Wallet } from 'ethers' import { Environments, getEnvironmentConfig } from '@fairdatasociety/fdp-contracts-js' import axios from 'axios' +import { CacheOptions } from '../src/cache/types' export interface TestUser { username: string @@ -97,21 +98,24 @@ export function numbersToSegment(numbers: number[]): Utils.Bytes<32> { /** * Options for FDP initialization */ -export const fdpOptions = { - downloadOptions: { +export const fdpOptions: Options = { + requestOptions: { timeout: GET_FEED_DATA_TIMEOUT, }, ensOptions: { ...getEnvironmentConfig(Environments.LOCALHOST), performChecks: true, }, + cacheOptions: { + isUseCache: false, + }, } /** * Creates FDP instance with default configuration for testing */ -export function createFdp(): FdpStorage { - return new FdpStorage(beeUrl(), batchId(), fdpOptions) +export function createFdp(cacheOptions?: CacheOptions): FdpStorage { + return new FdpStorage(beeUrl(), batchId(), { ...fdpOptions, ...(cacheOptions ? { cacheOptions } : undefined) }) } /** @@ -158,7 +162,9 @@ export async function createUsableBatch(): Promise { return getUsableBatch(beeDebug) } - await beeDebug.createPostageBatch('10000000', 24) + await beeDebug.createPostageBatch('10000000', 24, { + retry: 5, + }) for (let i = 0; i < 100; i++) { if (await isUsableBatchExists()) { break