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: support marshaled stamps #959

Merged
merged 2 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 7 additions & 13 deletions src/bee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import * as debugStatus from './modules/debug/status'
import * as debugTag from './modules/debug/tag'
import * as transactions from './modules/debug/transactions'
import { EnvelopeResponse, postEnvelope } from './modules/envelope'
import { postEnvelope } from './modules/envelope'
import { createFeedManifest } from './modules/feed'
import * as grantee from './modules/grantee'
import * as pinning from './modules/pinning'
Expand All @@ -45,6 +45,7 @@
CollectionUploadOptions,
Data,
DebugStatus,
Envelope,
ExtendedTag,
FeedReader,
FeedWriter,
Expand Down Expand Up @@ -276,12 +277,11 @@
* @see [Bee API reference - `POST /chunks`](https://docs.ethswarm.org/api/#tag/Chunk/paths/~1chunks/post)
*/
async uploadChunk(
postageBatchId: string | BatchId,
stamp: BatchId | Uint8Array | string,
data: Uint8Array,
options?: UploadOptions,
requestOptions?: BeeRequestOptions,
): Promise<UploadResult> {
assertBatchId(postageBatchId)
assertRequestOptions(requestOptions)

if (!(data instanceof Uint8Array)) {
Expand All @@ -300,7 +300,7 @@
assertUploadOptions(options)
}

return chunk.upload(this.getRequestOptionsForCall(requestOptions), data, postageBatchId, options)
return chunk.upload(this.getRequestOptionsForCall(requestOptions), data, stamp, options)
}

/**
Expand Down Expand Up @@ -438,7 +438,7 @@
ReferenceType.MANIFEST,
)
} else if (isReadable(data) && options?.tag && !options.size) {
// TODO: Needed until https://github.com/ethersphere/bee/issues/2317 is resolved

Check warning on line 441 in src/bee.ts

View workflow job for this annotation

GitHub Actions / check (16.x)

Unexpected 'todo' comment: 'TODO: Needed until...'
const result = await bzz.uploadFile(
this.getRequestOptionsForCall(requestOptions),
data,
Expand Down Expand Up @@ -827,7 +827,7 @@
await this.makeFeedReader(type, canonicalTopic, canonicalOwner).download()

return true
} catch (e: any) {

Check warning on line 830 in src/bee.ts

View workflow job for this annotation

GitHub Actions / check (16.x)

Unexpected any. Specify a different type
if (e?.status === 404 || e?.status === 500) {
return false
}
Expand Down Expand Up @@ -1030,15 +1030,14 @@
* @see [Bee API reference - `POST /feeds`](https://docs.ethswarm.org/api/#tag/Feed/paths/~1feeds~1{owner}~1{topic}/post)
*/
async createFeedManifest(
postageBatchId: string | BatchId,
stamp: BatchId | Uint8Array | string,
type: FeedType,
topic: Topic | Uint8Array | string,
owner: EthAddress | Uint8Array | string,
options?: BeeRequestOptions,
): Promise<FeedManifestResult> {
assertRequestOptions(options)
assertFeedType(type)
assertBatchId(postageBatchId)

const canonicalTopic = makeTopic(topic)
const canonicalOwner = makeHexEthAddress(owner)
Expand All @@ -1047,7 +1046,7 @@
this.getRequestOptionsForCall(options),
canonicalOwner,
canonicalTopic,
postageBatchId,
stamp,
)

return addCidConversionFunction({ reference }, ReferenceType.FEED)
Expand Down Expand Up @@ -1234,16 +1233,11 @@

return {
...this.makeSOCReader(canonicalSigner.address, options),

upload: uploadSingleOwnerChunkData.bind(null, this.getRequestOptionsForCall(options), canonicalSigner),
}
}

async createEnvelope(
postageBatchId: BatchId,
reference: Reference,
options?: BeeRequestOptions,
): Promise<EnvelopeResponse> {
async createEnvelope(postageBatchId: BatchId, reference: Reference, options?: BeeRequestOptions): Promise<Envelope> {
assertRequestOptions(options)

return postEnvelope(this.getRequestOptionsForCall(options), postageBatchId, reference)
Expand Down Expand Up @@ -1937,7 +1931,7 @@
if (stamp.usable) {
return
}
} catch (error: any) {}

Check warning on line 1934 in src/bee.ts

View workflow job for this annotation

GitHub Actions / check (16.x)

Unexpected any. Specify a different type

Check warning on line 1934 in src/bee.ts

View workflow job for this annotation

GitHub Actions / check (16.x)

Empty block statement

await System.sleepMillis(TIME_STEP)
}
Expand Down
1 change: 0 additions & 1 deletion src/chunk/cac.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export interface Chunk {
readonly data: Uint8Array
span(): Bytes<8>
payload(): FlexBytes<1, 4096>

address(): PlainBytesReference
}

Expand Down
10 changes: 4 additions & 6 deletions src/chunk/soc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { BeeError } from '../utils/error'
import { EthAddress } from '../utils/eth'
import { keccak256Hash } from '../utils/hash'
import { bytesToHex } from '../utils/hex'
import { assertAddress } from '../utils/type'
import { bmtHash } from './bmt'
import { Chunk, MAX_PAYLOAD_SIZE, MIN_PAYLOAD_SIZE, assertValidChunkData, makeContentAddressedChunk } from './cac'
import { recoverAddress, sign } from './signer'
Expand Down Expand Up @@ -136,15 +135,15 @@ export async function makeSingleOwnerChunk(
export async function uploadSingleOwnerChunk(
requestOptions: BeeRequestOptions,
chunk: SingleOwnerChunk,
postageBatchId: BatchId,
stamp: BatchId | Uint8Array | string,
options?: UploadOptions,
): Promise<UploadResult> {
const owner = bytesToHex(chunk.owner())
const identifier = bytesToHex(chunk.identifier())
const signature = bytesToHex(chunk.signature())
const data = Binary.concatBytes(chunk.span(), chunk.payload())

return socAPI.upload(requestOptions, owner, identifier, signature, data, postageBatchId, options)
return socAPI.upload(requestOptions, owner, identifier, signature, data, stamp, options)
}

/**
Expand All @@ -160,16 +159,15 @@ export async function uploadSingleOwnerChunk(
export async function uploadSingleOwnerChunkData(
requestOptions: BeeRequestOptions,
signer: Signer,
postageBatchId: BatchId | string,
stamp: BatchId | Uint8Array | string,
identifier: Identifier,
data: Uint8Array,
options?: UploadOptions,
): Promise<UploadResult> {
assertAddress(postageBatchId)
const cac = makeContentAddressedChunk(data)
const soc = await makeSingleOwnerChunk(cac, identifier, signer)

return uploadSingleOwnerChunk(requestOptions, soc, postageBatchId, options)
return uploadSingleOwnerChunk(requestOptions, soc, stamp, options)
}

/**
Expand Down
8 changes: 4 additions & 4 deletions src/modules/chunk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ const endpoint = 'chunks'
* Upload expects the chuck data to be set accordingly.
*
* @param requestOptions Options for making requests
* @param data Chunk data to be uploaded
* @param postageBatchId Postage BatchId that will be assigned to uploaded data
* @param data Chunk data to be uploaded
* @param stamp BatchId or marshaled stamp to be used for the upload
* @param options Additional options like tag, encryption, pinning
*/
export async function upload(
requestOptions: BeeRequestOptions,
data: Uint8Array,
postageBatchId: BatchId,
stamp: BatchId | Uint8Array | string,
options?: UploadOptions,
): Promise<UploadResult> {
const response = await http<ReferenceResponse>(requestOptions, {
Expand All @@ -38,7 +38,7 @@ export async function upload(
data,
headers: {
'content-type': 'application/octet-stream',
...extractUploadHeaders(postageBatchId, options),
...extractUploadHeaders(stamp, options),
},
responseType: 'json',
})
Expand Down
23 changes: 8 additions & 15 deletions src/modules/envelope.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
import { Types } from 'cafe-utility'
import type { BatchId, BeeRequestOptions, Reference } from '../types'
import { Binary, Types } from 'cafe-utility'
import type { BatchId, BeeRequestOptions, Envelope, Reference } from '../types'
import { http } from '../utils/http'

const ENVELOPE_ENDPOINT = 'envelope'

export interface EnvelopeResponse {
issuer: string
index: string
timestamp: string
signature: string
}

export async function postEnvelope(
requestOptions: BeeRequestOptions,
postageBatchId: BatchId,
reference: Reference,
): Promise<EnvelopeResponse> {
const response = await http<EnvelopeResponse>(requestOptions, {
): Promise<Envelope> {
const { data } = await http<Envelope>(requestOptions, {
method: 'post',
responseType: 'json',
url: `${ENVELOPE_ENDPOINT}/${reference}`,
Expand All @@ -26,9 +19,9 @@ export async function postEnvelope(
})

return {
issuer: Types.asHexString(response.data.issuer, { name: 'issuer', byteLength: 20 }),
index: Types.asHexString(response.data.index, { name: 'index', byteLength: 8 }),
timestamp: Types.asHexString(response.data.timestamp, { name: 'timestamp', byteLength: 8 }),
signature: Types.asHexString(response.data.signature, { name: 'signature', byteLength: 65 }),
issuer: Binary.hexToUint8Array(Types.asHexString(data.issuer, { name: 'issuer', byteLength: 20 })),
index: Binary.hexToUint8Array(Types.asHexString(data.index, { name: 'index', byteLength: 8 })),
timestamp: Binary.hexToUint8Array(Types.asHexString(data.timestamp, { name: 'timestamp', byteLength: 8 })),
signature: Binary.hexToUint8Array(Types.asHexString(data.signature, { name: 'signature', byteLength: 65 })),
}
}
4 changes: 2 additions & 2 deletions src/modules/feed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@
requestOptions: BeeRequestOptions,
owner: HexEthAddress,
topic: Topic,
postageBatchId: BatchId,
stamp: BatchId | Uint8Array | string,
): Promise<Reference> {
const response = await http<ReferenceResponse>(requestOptions, {
method: 'post',
responseType: 'json',
url: `${feedEndpoint}/${owner}/${topic}`,
headers: extractUploadHeaders(postageBatchId),
headers: extractUploadHeaders(stamp),
})

return response.data.reference
Expand Down Expand Up @@ -105,7 +105,7 @@
const response = await http<ReferenceResponse>(requestOptions, {
responseType: 'json',
url: `${feedEndpoint}/${owner}/${topic}`,
params: options as any,

Check warning on line 108 in src/modules/feed.ts

View workflow job for this annotation

GitHub Actions / check (16.x)

Unexpected any. Specify a different type
})

return {
Expand Down
4 changes: 2 additions & 2 deletions src/modules/soc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export async function upload(
identifier: string,
signature: string,
data: Uint8Array,
postageBatchId: BatchId,
stamp: BatchId | Uint8Array | string,
options?: UploadOptions,
): Promise<UploadResult> {
const response = await http<ReferenceResponse>(requestOptions, {
Expand All @@ -31,7 +31,7 @@ export async function upload(
data,
headers: {
'content-type': 'application/octet-stream',
...extractUploadHeaders(postageBatchId, options),
...extractUploadHeaders(stamp, options),
},
responseType: 'json',
params: { sig: signature },
Expand Down
9 changes: 8 additions & 1 deletion src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ export interface SOCWriter extends SOCReader {
* @param options Upload options
*/
upload: (
postageBatchId: string | BatchId,
stamp: BatchId | Uint8Array | string,
identifier: Identifier,
data: Uint8Array,
options?: UploadOptions,
Expand Down Expand Up @@ -617,6 +617,13 @@ export interface PostageBatchOptions {
waitForUsableTimeout?: number
}

export interface Envelope {
issuer: Uint8Array
index: Uint8Array
timestamp: Uint8Array
signature: Uint8Array
}

/**
* With this type a number should be represented in a string
*/
Expand Down
18 changes: 13 additions & 5 deletions src/utils/headers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Binary } from 'cafe-utility'
import { BatchId, DownloadRedundancyOptions, FileHeaders, UploadOptions, UploadRedundancyOptions } from '../types'
import { BeeError } from './error'

Expand Down Expand Up @@ -44,13 +45,20 @@ export function readFileHeaders(headers: Record<string, string>): FileHeaders {
}
}

export function extractUploadHeaders(postageBatchId: BatchId, options?: UploadOptions): Record<string, string> {
if (!postageBatchId) {
throw new BeeError('Postage BatchID has to be specified!')
export function extractUploadHeaders(
stamp: BatchId | Uint8Array | string,
options?: UploadOptions,
): Record<string, string> {
if (!stamp) {
throw new BeeError('Stamp has to be specified!')
}

const headers: Record<string, string> = {
'swarm-postage-batch-id': postageBatchId,
const headers: Record<string, string> = {}

if (stamp instanceof Uint8Array) {
headers['swarm-postage-stamp'] = Binary.uint8ArrayToHex(stamp)
} else {
headers['swarm-postage-batch-id'] = stamp
}

if (options?.act) {
Expand Down
32 changes: 31 additions & 1 deletion src/utils/stamps.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { NumberString } from '../types'
import { Binary } from 'cafe-utility'
import { BatchId, Envelope, NumberString } from '../types'

/**
* Utility function that calculates usage of postage batch based on its utilization, depth and bucket depth.
Expand Down Expand Up @@ -114,3 +115,32 @@ export function getAmountForTtl(days: number): NumberString {
export function getDepthForCapacity(gigabytes: number): number {
return gigabytes <= 1 ? 18 : Math.ceil(Math.log2(Math.ceil(gigabytes)) + 18)
}

export function convertEnvelopeToMarshaledStamp(batchID: BatchId, envelope: Envelope): Uint8Array {
return marshalStamp(envelope.signature, Binary.hexToUint8Array(batchID), envelope.timestamp, envelope.index)
}

export function marshalStamp(
signature: Uint8Array,
batchID: Uint8Array,
timestamp: Uint8Array,
index: Uint8Array,
): Uint8Array {
if (signature.length !== 65) {
throw Error('invalid signature length')
}

if (batchID.length !== 32) {
throw Error('invalid batch ID length')
}

if (timestamp.length !== 8) {
throw Error('invalid timestamp length')
}

if (index.length !== 8) {
throw Error('invalid index length')
}

return Binary.concatBytes(batchID, index, timestamp, signature)
}
4 changes: 0 additions & 4 deletions src/utils/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,6 @@ export function isReadable(obj: unknown): obj is Readable {
return typeof Readable !== 'undefined' && obj instanceof Readable
}

export function isUint8Array(obj: unknown): obj is Uint8Array {
return obj instanceof Uint8Array
}

export function isInteger(value: unknown): value is number | NumberString {
return (
(typeof value === 'string' && /^-?(0|[1-9][0-9]*)$/g.test(value)) ||
Expand Down
27 changes: 17 additions & 10 deletions test/integration/bee-class.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { System } from 'cafe-utility'
import { Binary, System } from 'cafe-utility'
import { Bee, BytesReference, Collection, PssSubscription } from '../../src'
import { makeContentAddressedChunk } from '../../src/chunk/cac'
import { makeSigner } from '../../src/chunk/signer'
import { uploadSingleOwnerChunkData } from '../../src/chunk/soc'
import * as bzz from '../../src/modules/bzz'
import { REFERENCE_HEX_LENGTH } from '../../src/types'
import { Reference, REFERENCE_HEX_LENGTH } from '../../src/types'
import { makeBytes } from '../../src/utils/bytes'
import { HexString, bytesToHex } from '../../src/utils/hex'
import { bytesToHex, HexString } from '../../src/utils/hex'
import { convertEnvelopeToMarshaledStamp } from '../../src/utils/stamps'
import {
beeKyOptions,
beePeerUrl,
Expand Down Expand Up @@ -613,14 +615,19 @@ describe('Bee class', () => {
})
})

describe('Envelope', () => {
describe('POST /envelope', () => {
it('should post envelope', async function () {
const uploadResult = await bee.uploadData(getPostageBatch(), 'Envelope')
const envelopeResult = await bee.createEnvelope(getPostageBatch(), uploadResult.reference)
expect(envelopeResult.index).toHaveLength(16)
expect(envelopeResult.issuer).toHaveLength(40)
expect(envelopeResult.signature).toHaveLength(130)
expect(envelopeResult.timestamp).toHaveLength(16)
const payload = new TextEncoder().encode('Marshaled upload')
const cac = makeContentAddressedChunk(payload)
const address = Binary.uint8ArrayToHex(cac.address()) as Reference
const envelope = await bee.createEnvelope(getPostageBatch(), address)
expect(envelope.index).toHaveLength(8)
expect(envelope.issuer).toHaveLength(20)
expect(envelope.signature).toHaveLength(65)
expect(envelope.timestamp).toHaveLength(8)
const stamp = convertEnvelopeToMarshaledStamp(getPostageBatch(), envelope)
const chunk = await bee.uploadChunk(stamp, cac.data)
expect(chunk.reference).toBe(address)
})
})

Expand Down
Loading