diff --git a/package-lock.json b/package-lock.json index 7a7147a9..f2a9672b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -75,7 +75,7 @@ "webpack-cli": "^4.7.2" }, "engines": { - "bee": "1.1.0-80cdea19", + "bee": "1.2.0-29eb9414", "node": ">=12.0.0", "npm": ">=6.0.0" } diff --git a/src/bee-debug.ts b/src/bee-debug.ts index eba04ed7..804c2a8b 100644 --- a/src/bee-debug.ts +++ b/src/bee-debug.ts @@ -59,11 +59,6 @@ import * as stamps from './modules/debug/stamps' import type { Options as KyOptions } from 'ky-universal' import { makeDefaultKy, wrapRequestClosure, wrapResponseClosure } from './utils/http' -/** - * The BeeDebug class provides a way of interacting with the Bee debug APIs based on the provided url - * - * @param url URL of a running Bee node - */ export class BeeDebug { /** * URL on which is the Debug API of Bee node exposed @@ -445,6 +440,53 @@ export class BeeDebug { return stamps.createPostageBatch(this.getKy(options), amount, depth, options) } + /** + * Topup a fresh amount of BZZ to given Postage Batch. + * + * For better understanding what each parameter means and what are the optimal values please see + * [Bee docs - Keep your data alive / Postage stamps](https://docs.ethswarm.org/docs/access-the-swarm/keep-your-data-alive). + * + * **WARNING: THIS CREATES TRANSACTIONS THAT SPENDS MONEY** + * + * @param postageBatchId Batch ID + * @param amount Amount to be added to the batch + * @param options Request options + * + * @see [Bee docs - Keep your data alive / Postage stamps](https://docs.ethswarm.org/docs/access-the-swarm/keep-your-data-alive) + * @see [Bee Debug API reference - `PATCH /stamps/topup/${id}/${amount}`](https://docs.ethswarm.org/debug-api/#tag/Postage-Stamps/paths/~1stamps~1topup~1{id}~1{amount}/patch) + */ + async topUpBatch(postageBatchId: BatchId | string, amount: NumberString, options?: RequestOptions): Promise { + assertRequestOptions(options) + assertNonNegativeInteger(amount, 'Amount') + assertBatchId(postageBatchId) + + await stamps.topUpBatch(this.getKy(options), postageBatchId, amount) + } + + /** + * Dilute given Postage Batch with new depth (that has to be bigger then the original depth), which allows + * the Postage Batch to be used for more chunks. + * + * For better understanding what each parameter means and what are the optimal values please see + * [Bee docs - Keep your data alive / Postage stamps](https://docs.ethswarm.org/docs/access-the-swarm/keep-your-data-alive). + * + * **WARNING: THIS CREATES TRANSACTIONS THAT SPENDS MONEY** + * + * @param postageBatchId Batch ID + * @param depth Amount to be added to the batch + * @param options Request options + * + * @see [Bee docs - Keep your data alive / Postage stamps](https://docs.ethswarm.org/docs/access-the-swarm/keep-your-data-alive) + * @see [Bee Debug API reference - `PATCH /stamps/topup/${id}/${amount}`](https://docs.ethswarm.org/debug-api/#tag/Postage-Stamps/paths/~1stamps~1topup~1{id}~1{amount}/patch) + */ + async diluteBatch(postageBatchId: BatchId | string, depth: number, options?: RequestOptions): Promise { + assertRequestOptions(options) + assertNonNegativeInteger(depth, 'Depth') + assertBatchId(postageBatchId) + + await stamps.diluteBatch(this.getKy(options), postageBatchId, depth) + } + /** * Return details for specific postage batch. * diff --git a/src/modules/debug/stamps.ts b/src/modules/debug/stamps.ts index 5f5890a1..40e36b66 100644 --- a/src/modules/debug/stamps.ts +++ b/src/modules/debug/stamps.ts @@ -14,7 +14,7 @@ interface GetAllStampsResponse { stamps: DebugPostageBatch[] } -interface CreateStampResponse { +interface StampResponse { batchID: BatchId } @@ -64,7 +64,7 @@ export async function createPostageBatch( headers.immutable = String(options.immutableFlag) } - const response = await http(ky, { + const response = await http(ky, { method: 'post', path: `${STAMPS_ENDPOINT}/${amount}/${depth}`, responseType: 'json', @@ -74,3 +74,23 @@ export async function createPostageBatch( return response.data.batchID } + +export async function topUpBatch(ky: Ky, id: string, amount: NumberString): Promise { + const response = await http(ky, { + method: 'patch', + path: `${STAMPS_ENDPOINT}/topup/${id}/${amount}`, + responseType: 'json', + }) + + return response.data.batchID +} + +export async function diluteBatch(ky: Ky, id: string, depth: number): Promise { + const response = await http(ky, { + method: 'patch', + path: `${STAMPS_ENDPOINT}/dilute/${id}/${depth}`, + responseType: 'json', + }) + + return response.data.batchID +} diff --git a/test/integration/bee-class.spec.ts b/test/integration/bee-class.spec.ts index c42e7228..07a36bbc 100644 --- a/test/integration/bee-class.spec.ts +++ b/test/integration/bee-class.spec.ts @@ -20,7 +20,7 @@ import { FEED_TIMEOUT, getPostageBatch, makeTestTarget, - POSTAGE_BATCH_TIMEOUT, + BLOCKCHAIN_TRANSACTION_TIMEOUT, PSS_TIMEOUT, randomByteArray, sleep, @@ -603,7 +603,7 @@ describe('Bee class', () => { expect(allBatches.find(batch => batch.batchID === batchId)).toBeTruthy() }, - POSTAGE_BATCH_TIMEOUT, + BLOCKCHAIN_TRANSACTION_TIMEOUT, ) it( @@ -616,7 +616,7 @@ describe('Bee class', () => { expect(allBatches.find(batch => batch.immutableFlag === true)).toBeTruthy() expect(allBatches.find(batch => batch.immutableFlag === false)).toBeTruthy() }, - POSTAGE_BATCH_TIMEOUT * 2, + BLOCKCHAIN_TRANSACTION_TIMEOUT * 2, ) it('should have all properties', async () => { diff --git a/test/integration/bee-debug-class.spec.ts b/test/integration/bee-debug-class.spec.ts index b9680439..3e0019b1 100644 --- a/test/integration/bee-debug-class.spec.ts +++ b/test/integration/bee-debug-class.spec.ts @@ -1,5 +1,5 @@ import { BeeArgumentError, BeeDebug } from '../../src' -import { beeDebugUrl, commonMatchers, POSTAGE_BATCH_TIMEOUT } from '../utils' +import { beeDebugUrl, commonMatchers, getOrCreatePostageBatch, BLOCKCHAIN_TRANSACTION_TIMEOUT, sleep } from '../utils' commonMatchers() @@ -16,7 +16,36 @@ describe('Bee Debug class', () => { expect(allBatches.find(batch => batch.batchID === batchId)).toBeTruthy() }, - POSTAGE_BATCH_TIMEOUT, + BLOCKCHAIN_TRANSACTION_TIMEOUT, + ) + + // TODO: Finish topup and dilute testing https://github.com/ethersphere/bee-js/issues/427 + it.skip( + 'should topup postage batch', + async () => { + const batch = await getOrCreatePostageBatch(undefined, undefined, false) + + await beeDebug.topUpBatch(batch.batchID, '10') + + await sleep(4000) + const batchDetails = await beeDebug.getPostageBatch(batch.batchID) + const newAmount = (parseInt(batch.amount) + 10).toString() + expect(batchDetails.amount).toEqual(newAmount) + }, + BLOCKCHAIN_TRANSACTION_TIMEOUT * 3, + ) + + // TODO: Finish topup and dilute testing https://github.com/ethersphere/bee-js/issues/427 + it.skip( + 'should dilute postage batch', + async () => { + const batch = await getOrCreatePostageBatch(undefined, 17, false) + await beeDebug.diluteBatch(batch.batchID, batch.depth + 2) + + const batchDetails = await beeDebug.getPostageBatch(batch.batchID) + expect(batchDetails.depth).toEqual(batch.depth + 2) + }, + BLOCKCHAIN_TRANSACTION_TIMEOUT * 2, ) it( @@ -29,7 +58,7 @@ describe('Bee Debug class', () => { expect(allBatches.find(batch => batch.immutableFlag === true)).toBeTruthy() expect(allBatches.find(batch => batch.immutableFlag === false)).toBeTruthy() }, - POSTAGE_BATCH_TIMEOUT * 2, + BLOCKCHAIN_TRANSACTION_TIMEOUT * 2, ) it('should have all properties', async () => { diff --git a/test/utils.ts b/test/utils.ts index da69da74..91ff7662 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1,12 +1,14 @@ import { Readable } from 'stream' -import type { Ky, BeeGenericResponse, Reference, Address, BatchId } from '../src/types' +import ky from 'ky-universal' +import { ReadableStream } from 'web-streams-polyfill/ponyfill' + +import type { Ky, BeeGenericResponse, Reference, Address, BatchId, DebugPostageBatch } from '../src/types' import { bytesToHex, HexString } from '../src/utils/hex' import { deleteChunkFromLocalStorage } from '../src/modules/debug/chunk' import { BeeResponseError } from '../src' import { ChunkAddress } from '../src/chunk/cac' import { assertBytes } from '../src/utils/bytes' -import ky from 'ky-universal' -import { ReadableStream } from 'web-streams-polyfill/ponyfill' +import * as stamps from '../src/modules/debug/stamps' declare global { // eslint-disable-next-line @typescript-eslint/no-namespace @@ -238,7 +240,7 @@ export function beeDebugUrl(): string { } export function beeDebugKy(): Ky { - return ky.create({ prefixUrl: beeDebugUrl() }) + return ky.create({ prefixUrl: beeDebugUrl(), timeout: false }) } /** @@ -249,7 +251,7 @@ export function beePeerDebugUrl(): string { } export function beePeerDebugKy(): Ky { - return ky.create({ prefixUrl: beePeerDebugUrl() }) + return ky.create({ prefixUrl: beePeerDebugUrl(), timeout: false }) } /** @@ -290,6 +292,97 @@ export function shorten(inputStr: unknown, len = 17): string { return `${str.slice(0, 6)}...${str.slice(-6)} (length: ${str.length})` } +async function timeout(ms: number, message = 'Execution reached timeout!'): Promise { + await sleep(ms) + throw new Error(message) +} + +export async function waitForBatchToBeUsable(batchId: string, pollingInterval = 200): Promise { + await Promise.race([ + timeout(USABLE_TIMEOUT, 'Awaiting of usable postage batch timed out!'), + async () => { + let stamp + + do { + await sleep(pollingInterval) + stamp = await stamps.getPostageBatch(beeDebugKy(), batchId as BatchId) + } while (!stamp.usable) + }, + ]) +} + +const DEFAULT_BATCH_AMOUNT = '1' +const DEFAULT_BATCH_DEPTH = 17 + +/** + * Returns already existing batch or will create one. + * + * If some specification is passed then it is guaranteed that the batch will have this property(ies) + * + * @param amount + * @param depth + * @param immutable + */ +export async function getOrCreatePostageBatch( + amount?: string, + depth?: number, + immutable?: boolean, +): Promise { + // Non-usable stamps are ignored by Bee + const allUsableStamps = (await stamps.getAllPostageBatches(beeDebugKy())).filter(stamp => stamp.usable) + + if (allUsableStamps.length === 0) { + const batchId = await stamps.createPostageBatch( + beeDebugKy(), + amount ?? DEFAULT_BATCH_AMOUNT, + depth ?? DEFAULT_BATCH_DEPTH, + ) + + await waitForBatchToBeUsable(batchId) + + return stamps.getPostageBatch(beeDebugKy(), batchId) + } + + // User does not want any specific batch, lets give him the first one + if (amount === undefined && depth === undefined && immutable === undefined) { + return allUsableStamps[0] + } + + // User wants some specific batch + for (const stamp of allUsableStamps) { + let meetingAllCriteria = false + + if (amount !== undefined) { + meetingAllCriteria = amount === stamp.amount + } else { + meetingAllCriteria = true + } + + if (depth !== undefined) { + meetingAllCriteria = meetingAllCriteria && depth === stamp.depth + } + + if (immutable !== undefined) { + meetingAllCriteria = meetingAllCriteria && immutable === stamp.immutableFlag + } + + if (meetingAllCriteria) { + return stamp + } + } + + // No stamp meeting the criteria was found ==> we need to create a new one + const batchId = await stamps.createPostageBatch( + beeDebugKy(), + amount ?? DEFAULT_BATCH_AMOUNT, + depth ?? DEFAULT_BATCH_DEPTH, + ) + + await waitForBatchToBeUsable(batchId) + + return stamps.getPostageBatch(beeDebugKy(), batchId) +} + export function makeTestTarget(target: string): string { return target.slice(0, 2) } @@ -306,11 +399,12 @@ export const createdResponse: BeeGenericResponse = { message: 'Created', } -export const ERR_TIMEOUT = 40000 -export const BIG_FILE_TIMEOUT = 100000 -export const PSS_TIMEOUT = 120000 -export const FEED_TIMEOUT = 120000 -export const POSTAGE_BATCH_TIMEOUT = 40000 +const USABLE_TIMEOUT = 7_000 +export const ERR_TIMEOUT = 40_000 +export const BIG_FILE_TIMEOUT = 100_000 +export const PSS_TIMEOUT = 120_000 +export const FEED_TIMEOUT = 120_000 +export const BLOCKCHAIN_TRANSACTION_TIMEOUT = 40_000 export const testChunkPayload = new Uint8Array([1, 2, 3]) // span is the payload length encoded as uint64 little endian