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

feat: topup and dilute batch methods #424

Merged
merged 10 commits into from
Oct 13, 2021
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 47 additions & 5 deletions src/bee-debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<void> {
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<void> {
assertRequestOptions(options)
assertNonNegativeInteger(depth, 'Depth')
assertBatchId(postageBatchId)

await stamps.diluteBatch(this.getKy(options), postageBatchId, depth)
}

/**
* Return details for specific postage batch.
*
Expand Down
24 changes: 22 additions & 2 deletions src/modules/debug/stamps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface GetAllStampsResponse {
stamps: DebugPostageBatch[]
}

interface CreateStampResponse {
interface StampResponse {
batchID: BatchId
}

Expand Down Expand Up @@ -64,7 +64,7 @@ export async function createPostageBatch(
headers.immutable = String(options.immutableFlag)
}

const response = await http<CreateStampResponse>(ky, {
const response = await http<StampResponse>(ky, {
method: 'post',
path: `${STAMPS_ENDPOINT}/${amount}/${depth}`,
responseType: 'json',
Expand All @@ -74,3 +74,23 @@ export async function createPostageBatch(

return response.data.batchID
}

export async function topUpBatch(ky: Ky, id: string, amount: NumberString): Promise<BatchId> {
const response = await http<StampResponse>(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<BatchId> {
const response = await http<StampResponse>(ky, {
method: 'patch',
path: `${STAMPS_ENDPOINT}/dilute/${id}/${depth}`,
responseType: 'json',
})

return response.data.batchID
}
6 changes: 3 additions & 3 deletions test/integration/bee-class.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
FEED_TIMEOUT,
getPostageBatch,
makeTestTarget,
POSTAGE_BATCH_TIMEOUT,
BLOCKCHAIN_TRANSACTION_TIMEOUT,
PSS_TIMEOUT,
randomByteArray,
sleep,
Expand Down Expand Up @@ -603,7 +603,7 @@ describe('Bee class', () => {

expect(allBatches.find(batch => batch.batchID === batchId)).toBeTruthy()
},
POSTAGE_BATCH_TIMEOUT,
BLOCKCHAIN_TRANSACTION_TIMEOUT,
)

it(
Expand All @@ -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 () => {
Expand Down
35 changes: 32 additions & 3 deletions test/integration/bee-debug-class.spec.ts
Original file line number Diff line number Diff line change
@@ -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()

Expand All @@ -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(
Expand All @@ -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 () => {
Expand Down
114 changes: 104 additions & 10 deletions test/utils.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -238,7 +240,7 @@ export function beeDebugUrl(): string {
}

export function beeDebugKy(): Ky {
return ky.create({ prefixUrl: beeDebugUrl() })
return ky.create({ prefixUrl: beeDebugUrl(), timeout: false })
}

/**
Expand All @@ -249,7 +251,7 @@ export function beePeerDebugUrl(): string {
}

export function beePeerDebugKy(): Ky {
return ky.create({ prefixUrl: beePeerDebugUrl() })
return ky.create({ prefixUrl: beePeerDebugUrl(), timeout: false })
}

/**
Expand Down Expand Up @@ -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<Error> {
await sleep(ms)
throw new Error(message)
}

export async function waitForBatchToBeUsable(batchId: string, pollingInterval = 200): Promise<void> {
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<DebugPostageBatch> {
// 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)
}
Expand All @@ -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
Expand Down