Skip to content

Commit cc49b40

Browse files
Cafe137rolandlor
andauthored
feat: add generic extend storage method (#1061)
* feat: adds erasure code handling and encryption handling * test: add expected argument in a test * feat: add extend storage method * feat: improve storage methods and testing * chore: make lint happy --------- Co-authored-by: Roland Seres <roland.seres90@gmail.com>
1 parent 0ac68d4 commit cc49b40

6 files changed

Lines changed: 142 additions & 153 deletions

File tree

src/bee.ts

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1746,6 +1746,33 @@ export class Bee {
17461746

17471747
return getStampCost(depth, amount)
17481748
}
1749+
async extendStorage(
1750+
postageBatchId: BatchId | Uint8Array | string,
1751+
size: Size,
1752+
duration: Duration,
1753+
options?: BeeRequestOptions,
1754+
encryption?: boolean,
1755+
erasureCodeLevel?: RedundancyLevel,
1756+
) {
1757+
const batch = await this.getPostageBatch(postageBatchId, options)
1758+
const depth = getDepthForSize(size, encryption, erasureCodeLevel)
1759+
const chainState = await this.getChainState(options)
1760+
const depthDelta = depth - batch.depth
1761+
const multiplier = depthDelta === 0 ? 1n : 2n ** BigInt(depthDelta)
1762+
const additionalAmount = getAmountForDuration(duration, chainState.currentPrice, this.network === 'gnosis' ? 5 : 15)
1763+
const targetAmount = duration.isZero()
1764+
? BigInt(batch.amount) * multiplier
1765+
: (BigInt(batch.amount) + additionalAmount) * multiplier
1766+
const amountDelta = targetAmount - BigInt(batch.amount)
1767+
1768+
const transactionId = await this.topUpBatch(batch.batchID, amountDelta, options)
1769+
1770+
if (depthDelta) {
1771+
return this.diluteBatch(batch.batchID, depth, options)
1772+
}
1773+
1774+
return transactionId
1775+
}
17491776

17501777
async extendStorageSize(
17511778
postageBatchId: BatchId | Uint8Array | string,
@@ -1762,7 +1789,7 @@ export class Bee {
17621789
throw new BeeArgumentError('New depth has to be greater than the original depth', depth)
17631790
}
17641791

1765-
await this.topUpBatch(batch.batchID, BigInt(batch.amount) * 2n ** BigInt(delta - 1) + 1n, options)
1792+
await this.topUpBatch(batch.batchID, BigInt(batch.amount) * (2n ** BigInt(delta) - 1n) + 1n, options)
17661793

17671794
return this.diluteBatch(batch.batchID, depth, options)
17681795
}
@@ -1789,13 +1816,15 @@ export class Bee {
17891816
): Promise<BZZ> {
17901817
const batch = await this.getPostageBatch(postageBatchId, options)
17911818
const chainState = await this.getChainState(options)
1792-
const amount = getAmountForDuration(duration, chainState.currentPrice, this.network === 'gnosis' ? 5 : 15)
1819+
const amount = duration.isZero()
1820+
? 0n
1821+
: getAmountForDuration(duration, chainState.currentPrice, this.network === 'gnosis' ? 5 : 15)
17931822
const depth = getDepthForSize(size, encryption, erasureCodeLevel)
17941823

1795-
const currentValue = getStampCost(batch.depth, batch.amount)
1796-
const newValue = getStampCost(depth, amount)
1824+
const currentCost = getStampCost(batch.depth, batch.amount)
1825+
const newCost = getStampCost(depth, BigInt(batch.amount) + amount)
17971826

1798-
return newValue.minus(currentValue)
1827+
return newCost.minus(currentCost)
17991828
}
18001829

18011830
async getSizeExtensionCost(
@@ -1813,10 +1842,10 @@ export class Bee {
18131842
throw new BeeArgumentError('New depth has to be greater than the original depth', depth)
18141843
}
18151844

1816-
const currentPaid = getStampCost(batch.depth, batch.amount)
1817-
const newPaid = getStampCost(depth, batch.amount)
1845+
const currentCost = getStampCost(batch.depth, batch.amount)
1846+
const newCost = getStampCost(depth, batch.amount)
18181847

1819-
return newPaid.minus(currentPaid)
1848+
return newCost.minus(currentCost)
18201849
}
18211850

18221851
async getDurationExtensionCost(

src/types/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -862,4 +862,3 @@ export const capacityBreakpoints = {
862862
],
863863
},
864864
}
865-

src/utils/duration.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import { Dates } from 'cafe-utility'
22

33
export class Duration {
44
private seconds: number
5+
static ZERO: Duration = new Duration(0)
56

67
private constructor(seconds: number) {
78
this.seconds = Math.ceil(seconds)
89

9-
if (seconds <= 0) {
10-
throw Error('Duration must be greater than 0')
10+
if (seconds < 0) {
11+
throw Error('Duration cannot be negative')
1112
}
1213
}
1314

@@ -66,4 +67,8 @@ export class Duration {
6667
represent(): string {
6768
return Dates.secondsToHumanTime(this.seconds)
6869
}
70+
71+
isZero(): boolean {
72+
return this.seconds === 0
73+
}
6974
}

test/integration/stamp.ux.spec.ts

Lines changed: 64 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Bee, Size, Utils } from '../../src'
22
import { Duration } from '../../src/utils/duration'
3-
import { mocked } from '../mocks'
3+
4+
const bee = new Bee('http://localhost:16337')
45

56
test('Utils.getDepthForSize', () => {
67
expect(Utils.getDepthForSize(Size.fromGigabytes(0))).toBe(17)
@@ -21,86 +22,79 @@ test('Utils.getAmountForDuration', () => {
2122
})
2223

2324
test('bee.getStorageCost', async () => {
24-
await mocked(async bee => {
25-
const bzz = await bee.getStorageCost(Size.fromGigabytes(4), Duration.fromDays(1))
26-
expect(bzz.toSignificantDigits(3)).toBe('0.192')
27-
})
25+
const bzz = await bee.getStorageCost(Size.fromGigabytes(1), Duration.fromDays(2))
26+
expect(bzz.toSignificantDigits(4)).toBe('0.1739')
27+
})
28+
29+
test('bee.getDurationExtensionCost for 1GB/2days', async () => {
30+
const batchId = await bee.buyStorage(Size.fromGigabytes(1), Duration.fromDays(2))
31+
const bzz = await bee.getDurationExtensionCost(batchId, Duration.fromDays(2))
32+
expect(bzz.toSignificantDigits(4)).toBe('0.1739')
33+
})
34+
35+
test('bee.getSizeExtensionCost for 1GB/2days', async () => {
36+
const batchId = await bee.buyStorage(Size.fromGigabytes(1), Duration.fromDays(2))
37+
const bzz = await bee.getSizeExtensionCost(batchId, Size.fromGigabytes(3))
38+
expect(bzz.toSignificantDigits(4)).toBe('0.1739')
39+
})
40+
41+
test('bee.getExtensionCost (size) for 1GB/2days', async () => {
42+
const batchId = await bee.buyStorage(Size.fromGigabytes(1), Duration.fromDays(2))
43+
const bzz = await bee.getExtensionCost(batchId, Size.fromGigabytes(3), Duration.ZERO)
44+
expect(bzz.toSignificantDigits(4)).toBe('0.1739')
45+
})
46+
47+
test('bee.getExtensionCost (duration) for 1GB/2days', async () => {
48+
const batchId = await bee.buyStorage(Size.fromGigabytes(1), Duration.fromDays(2))
49+
const bzz = await bee.getExtensionCost(batchId, Size.fromGigabytes(1), Duration.fromDays(2))
50+
expect(bzz.toSignificantDigits(4)).toBe('0.1739')
2851
})
2952

3053
test('bee.getDurationExtensionCost', async () => {
31-
await mocked(async bee => {
32-
const cost = await bee.getDurationExtensionCost(
33-
'f8b2ad296d64824a8fe51a33ff15fe8668df13a20ad3d4eea4bb97ca600029aa',
34-
Duration.fromDays(31),
35-
)
36-
expect(cost.toSignificantDigits(3)).toBe('11.934')
37-
})
54+
const batchId = await bee.buyStorage(Size.fromGigabytes(8), Duration.fromDays(1))
55+
const cost = await bee.getDurationExtensionCost(batchId, Duration.fromDays(31))
56+
expect(cost.toSignificantDigits(3)).toBe('10.784')
3857
})
3958

4059
test('bee.getSizeExtensionCost', async () => {
41-
await mocked(async bee => {
42-
const cost = await bee.getSizeExtensionCost(
43-
'f8b2ad296d64824a8fe51a33ff15fe8668df13a20ad3d4eea4bb97ca600029aa',
44-
Size.fromGigabytes(19),
45-
)
46-
expect(cost.toSignificantDigits(3)).toBe('72.011')
47-
48-
await expect(async () =>
49-
bee.getSizeExtensionCost(
50-
'f8b2ad296d64824a8fe51a33ff15fe8668df13a20ad3d4eea4bb97ca600029aa',
51-
Size.fromGigabytes(1),
52-
),
53-
).rejects.toThrow('New depth has to be greater than the original depth')
54-
})
60+
const batchId = await bee.buyStorage(Size.fromGigabytes(8), Duration.fromDays(31))
61+
const cost = await bee.getSizeExtensionCost(batchId, Size.fromGigabytes(100))
62+
expect(cost.toSignificantDigits(3)).toBe('75.492')
63+
64+
await expect(async () => bee.getSizeExtensionCost(batchId, Size.fromGigabytes(1))).rejects.toThrow(
65+
'New depth has to be greater than the original depth',
66+
)
5567
})
5668

5769
test('bee.getExtensionCost', async () => {
58-
await mocked(async (bee: Bee) => {
59-
const cost = await bee.getExtensionCost(
60-
'f8b2ad296d64824a8fe51a33ff15fe8668df13a20ad3d4eea4bb97ca600029aa',
61-
Size.fromGigabytes(18),
62-
Duration.fromYears(1),
63-
)
64-
expect(cost.toDecimalString()).toBe('68.5035408119037952')
65-
})
70+
const batchId = await bee.buyStorage(Size.fromGigabytes(8), Duration.fromDays(31))
71+
const cost = await bee.getExtensionCost(batchId, Size.fromGigabytes(18), Duration.fromYears(1))
72+
expect(cost.toDecimalString()).toBe('126.9807081070788608')
6673
})
6774

68-
test('bee.buyStorage with extensions', async () => {
69-
const calls = await mocked(async (bee: Bee) => {
70-
const batchId = await bee.buyStorage(Size.fromGigabytes(1), Duration.fromDays(1))
71-
await bee.buyStorage(Size.fromGigabytes(1), Duration.fromDays(1), { waitForUsable: false })
72-
await bee.extendStorageDuration(batchId, Duration.fromDays(1))
73-
await bee.extendStorageSize(batchId, Size.fromGigabytes(8))
74-
await bee.extendStorageSize(batchId, Size.fromGigabytes(24))
75-
await expect(async () => bee.extendStorageSize(batchId, Size.fromGigabytes(1))).rejects.toThrow(
76-
'New depth has to be greater than the original depth',
77-
)
78-
})
79-
expect(calls.map(x => `${x.method} ${x.url}`)).toEqual([
80-
// create stamp
81-
'GET /chainstate',
82-
'GET /chainstate',
83-
'POST /stamps/458922241/21',
84-
'GET /stamps/b330000000000000000000000000000000000000000000000000000000000000',
85-
// create stamp, do not wait for usable
86-
'GET /chainstate',
87-
'GET /chainstate',
88-
'POST /stamps/458922241/21',
89-
// extend duration
90-
'GET /stamps/b330000000000000000000000000000000000000000000000000000000000000',
91-
'GET /chainstate',
92-
'PATCH /stamps/topup/b330000000000000000000000000000000000000000000000000000000000000/458922241',
93-
// extend size +1 depth
94-
'GET /stamps/b330000000000000000000000000000000000000000000000000000000000000',
95-
'PATCH /stamps/topup/b330000000000000000000000000000000000000000000000000000000000000/458922241',
96-
'PATCH /stamps/dilute/b330000000000000000000000000000000000000000000000000000000000000/23',
97-
// extend size +2 depth
98-
'GET /stamps/b330000000000000000000000000000000000000000000000000000000000000',
99-
'PATCH /stamps/topup/b330000000000000000000000000000000000000000000000000000000000000/917844481',
100-
'PATCH /stamps/dilute/b330000000000000000000000000000000000000000000000000000000000000/24',
101-
// error case
102-
'GET /stamps/b330000000000000000000000000000000000000000000000000000000000000',
103-
])
75+
test('bee.buyStorage with extensions (extendStorageDuration, extendStorageSize)', async () => {
76+
const batchId = await bee.buyStorage(Size.fromGigabytes(1), Duration.fromDays(1))
77+
await bee.extendStorageDuration(batchId, Duration.fromDays(1))
78+
await bee.extendStorageSize(batchId, Size.fromGigabytes(8))
79+
await bee.extendStorageSize(batchId, Size.fromGigabytes(24))
80+
await expect(async () => bee.extendStorageSize(batchId, Size.fromGigabytes(1))).rejects.toThrow(
81+
'New depth has to be greater than the original depth',
82+
)
83+
const batch = await bee.getPostageBatch(batchId)
84+
expect(batch.depth).toBe(24)
85+
expect(batch.duration.toDays()).toBe(2)
86+
expect(BigInt(batch.amount)).toBe(Utils.getAmountForDuration(Duration.fromDays(2), 24000, 5) + 1n)
87+
})
88+
89+
test('bee.buyStorage with extensions (extendStorage)', async () => {
90+
const batchId = await bee.buyStorage(Size.fromGigabytes(1), Duration.fromDays(1))
91+
await bee.extendStorage(batchId, Size.fromGigabytes(1), Duration.fromDays(1))
92+
await bee.extendStorage(batchId, Size.fromGigabytes(8), Duration.ZERO)
93+
await bee.extendStorage(batchId, Size.fromGigabytes(24), Duration.ZERO)
94+
const batch = await bee.getPostageBatch(batchId)
95+
expect(batch.depth).toBe(24)
96+
expect(batch.duration.toDays()).toBe(2)
97+
expect(BigInt(batch.amount)).toBe(Utils.getAmountForDuration(Duration.fromDays(2), 24000, 5) + 1n)
10498
})
10599

106100
test('getStampEffectiveBytesBreakpoints', () => {

test/mocks.ts

Lines changed: 0 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -5,78 +5,6 @@ let i = 12000
55

66
const responses = new Map<string, string>()
77

8-
responses.set(
9-
'GET /chainstate',
10-
JSON.stringify({ chainTip: 38679237, block: 38679230, totalAmount: '183499080648', currentPrice: '26558' }),
11-
)
12-
13-
responses.set(
14-
'GET /stamps/f8b2ad296d64824a8fe51a33ff15fe8668df13a20ad3d4eea4bb97ca600029aa',
15-
JSON.stringify({
16-
batchID: 'f8b2ad296d64824a8fe51a33ff15fe8668df13a20ad3d4eea4bb97ca600029aa',
17-
utilization: 46,
18-
usable: true,
19-
label: '',
20-
depth: 23,
21-
amount: '85844033282',
22-
bucketDepth: 16,
23-
blockNumber: 37496330,
24-
immutableFlag: true,
25-
exists: true,
26-
batchTTL: 2722136,
27-
}),
28-
)
29-
30-
responses.set(
31-
'POST /stamps/458922241/21',
32-
JSON.stringify({ batchID: 'b330000000000000000000000000000000000000000000000000000000000000' }),
33-
)
34-
35-
responses.set(
36-
'GET /stamps/b330000000000000000000000000000000000000000000000000000000000000',
37-
JSON.stringify({
38-
batchID: 'b330000000000000000000000000000000000000000000000000000000000000',
39-
utilization: 0,
40-
usable: true,
41-
label: '',
42-
depth: 22,
43-
amount: '458922240',
44-
bucketDepth: 16,
45-
blockNumber: 37496330,
46-
immutableFlag: true,
47-
exists: true,
48-
batchTTL: 86400,
49-
}),
50-
)
51-
52-
responses.set(
53-
'PATCH /stamps/topup/b330000000000000000000000000000000000000000000000000000000000000/458922241',
54-
JSON.stringify({
55-
batchID: 'b330000000000000000000000000000000000000000000000000000000000000',
56-
}),
57-
)
58-
59-
responses.set(
60-
'PATCH /stamps/topup/b330000000000000000000000000000000000000000000000000000000000000/917844481',
61-
JSON.stringify({
62-
batchID: 'b330000000000000000000000000000000000000000000000000000000000000',
63-
}),
64-
)
65-
66-
responses.set(
67-
'PATCH /stamps/dilute/b330000000000000000000000000000000000000000000000000000000000000/23',
68-
JSON.stringify({
69-
batchID: 'b330000000000000000000000000000000000000000000000000000000000000',
70-
}),
71-
)
72-
73-
responses.set(
74-
'PATCH /stamps/dilute/b330000000000000000000000000000000000000000000000000000000000000/24',
75-
JSON.stringify({
76-
batchID: 'b330000000000000000000000000000000000000000000000000000000000000',
77-
}),
78-
)
79-
808
responses.set(
819
'POST /bzz?name=filename.txt',
8210
JSON.stringify({

test/unit/extend-storage.spec.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Bee, Duration, Size } from '../../src'
2+
3+
const bee = new Bee('http://localhost:16337')
4+
5+
test('getExtensionCost should equal getSizeExtensionCost when Duration is 0', async () => {
6+
const batch = await bee.buyStorage(Size.fromGigabytes(4), Duration.fromDays(30))
7+
8+
const extensionCost = await bee.getExtensionCost(batch, Size.fromGigabytes(8), Duration.ZERO)
9+
const sizeExtensionCost = await bee.getSizeExtensionCost(batch, Size.fromGigabytes(8))
10+
11+
expect(extensionCost).toEqual(sizeExtensionCost)
12+
expect(extensionCost.toPLURBigInt()).toBeGreaterThan(0)
13+
})
14+
15+
test('getExtensionCost should equal getDurationExtensionCost when depth does not change', async () => {
16+
const batch = await bee.buyStorage(Size.fromGigabytes(4), Duration.fromDays(30))
17+
18+
const extensionCost = await bee.getExtensionCost(batch, Size.fromGigabytes(4), Duration.fromDays(30))
19+
const durationExtensionCost = await bee.getDurationExtensionCost(batch, Duration.fromDays(30))
20+
21+
expect(extensionCost).toEqual(durationExtensionCost)
22+
expect(extensionCost.toPLURBigInt()).toBeGreaterThan(0)
23+
})
24+
25+
test('getExtensionCost should equal getDurationExtensionCost twice plus getSizeExtensionCost', async () => {
26+
const batch = await bee.buyStorage(Size.fromGigabytes(4), Duration.fromDays(30))
27+
28+
const extensionCost = await bee.getExtensionCost(batch, Size.fromGigabytes(8), Duration.fromDays(30))
29+
const sizeExtensionCost = await bee.getSizeExtensionCost(batch, Size.fromGigabytes(8))
30+
const durationExtensionCost = await bee.getDurationExtensionCost(batch, Duration.fromDays(30))
31+
32+
expect(extensionCost).toEqual(sizeExtensionCost.plus(durationExtensionCost).plus(durationExtensionCost))
33+
expect(extensionCost.toPLURBigInt()).toBeGreaterThan(0)
34+
})

0 commit comments

Comments
 (0)