Skip to content

Commit

Permalink
fix: reuse http put receipt if available
Browse files Browse the repository at this point in the history
  • Loading branch information
joaosa committed Jun 4, 2024
1 parent 3bfd2b4 commit 32fc658
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 13 deletions.
23 changes: 12 additions & 11 deletions packages/upload-client/src/blob.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,24 +280,25 @@ export async function add(
}

// Invoke `conclude` with `http/put` receipt
const derivedSigner = ed25519.from(
/** @type {import('@ucanto/interface').SignerArchive<import('@ucanto/interface').DID, typeof ed25519.signatureCode>} */
(nextTasks.put.task.facts[0]['keys'])
)

const httpPutReceipt = await Receipt.issue({
issuer: derivedSigner,
ran: nextTasks.put.task.cid,
result: { ok: {} },
})
let { receipt: httpPutReceipt } = nextTasks.put
if (!httpPutReceipt?.out.ok) {
const derivedSigner = ed25519.from(
/** @type {import('@ucanto/interface').SignerArchive<import('@ucanto/interface').DID, typeof ed25519.signatureCode>} */
(nextTasks.put.task.facts[0]['keys'])
)
httpPutReceipt = await Receipt.issue({
issuer: derivedSigner,
ran: nextTasks.put.task.cid,
result: { ok: {} },
})
}
const httpPutConcludeInvocation = createConcludeInvocation(
issuer,
// @ts-expect-error object of type unknown
audience,
httpPutReceipt
)
const ucanConclude = await httpPutConcludeInvocation.execute(conn)

if (!ucanConclude.out.ok) {
throw new Error(`failed ${BlobCapabilities.add.can} invocation`, {
cause: result.out.error,
Expand Down
66 changes: 66 additions & 0 deletions packages/upload-client/test/blob.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
setupBlobAdd4xxResponse,
setupBlobAdd5xxResponse,
setupBlobAddWithAcceptReceiptSuccessResponse,
setupBlobAddWithHttpPutReceiptSuccessResponse,
receiptsEndpoint,
} from './helpers/utils.js'
import { fetchWithUploadProgress } from '../src/fetch-with-upload-progress.js'
Expand Down Expand Up @@ -374,6 +375,71 @@ describe('Blob.add', () => {
assert.ok(site.capabilities[0].nb.content.multihash.bytes)
})

it('reuses the http/put receipt when it is already available', async () => {
const space = await Signer.generate()
const agent = await Signer.generate()
const bytes = await randomBytes(128)
const bytesHash = await sha256.digest(bytes)

const proofs = [
await BlobCapabilities.add.delegate({
issuer: space,
audience: agent,
with: space.did(),
expiration: Infinity,
}),
]

const service = mockService({
ucan: {
conclude: provide(UCAN.conclude, () => {
return { ok: { time: Date.now() } }
}),
},
space: {
blob: {
// @ts-ignore Argument of type
add: provide(BlobCapabilities.add, ({ invocation }) => {
return setupBlobAddWithHttpPutReceiptSuccessResponse(
{ issuer: space, audience: agent, with: space, proofs },
invocation
)
}),
},
},
})

const server = Server.create({
id: serviceSigner,
service,
codec: CAR.inbound,
validateAuthorization,
})
const connection = Client.connect({
id: serviceSigner,
codec: CAR.outbound,
channel: server,
})

const { site, multihash } = await Blob.add(
{ issuer: agent, with: space.did(), proofs, audience: serviceSigner },
bytes,
{
connection,
receiptsEndpoint,
}
)

assert(multihash)
assert.deepEqual(multihash, bytesHash)

assert(site)
assert.equal(site.capabilities[0].can, Assert.location.can)
// we're not verifying this as it's a mocked value
// @ts-ignore nb unknown
assert.ok(site.capabilities[0].nb.content.multihash.bytes)
})

it('throws for bucket URL client error 4xx', async () => {
const space = await Signer.generate()
const agent = await Signer.generate()
Expand Down
39 changes: 37 additions & 2 deletions packages/upload-client/test/helpers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const setupBlobAddSuccessResponse = async function (
'http://localhost:9200',
options,
invocation,
false,
false
)
}
Expand All @@ -37,6 +38,7 @@ export const setupBlobAdd4xxResponse = async function (
'http://localhost:9400',
options,
invocation,
false,
false
)
}
Expand All @@ -51,6 +53,7 @@ export const setupBlobAdd5xxResponse = async function (
'http://localhost:9500',
options,
invocation,
false,
false
)
}
Expand All @@ -65,12 +68,29 @@ export const setupBlobAddWithAcceptReceiptSuccessResponse = async function (
'http://localhost:9200',
options,
invocation,
false,
true
)
}

export const setupBlobAddWithHttpPutReceiptSuccessResponse = async function (
// @ts-ignore
options,
// @ts-ignore
invocation
) {
return setupBlobAddResponse(
'http://localhost:9200',
options,
invocation,
true,
false
)
}

/**
* @param {string} url
* @param {boolean} hasHttpPutReceipt
* @param {boolean} hasAcceptReceipt
*/
const setupBlobAddResponse = async function (
Expand All @@ -79,6 +99,7 @@ const setupBlobAddResponse = async function (
{ issuer, with: space, proofs, audience },
// @ts-ignore
invocation,
hasHttpPutReceipt,
hasAcceptReceipt
) {
const blob = invocation.capabilities[0].nb.blob
Expand Down Expand Up @@ -134,6 +155,18 @@ const setupBlobAddResponse = async function (
expiration: Infinity,
})
.delegate()
const blobPutReceipt = !hasHttpPutReceipt
? await Receipt.issue({
issuer,
ran: blobPutTask.cid,
result: { error: new Error() },
})
: await generateAcceptReceipt(blobPutTask.cid.toString())
const blobConcludePut = await createConcludeInvocation(
issuer,
audience,
blobPutReceipt
).delegate()

const blobAcceptTask = await W3sBlobCapabilities.accept
.invoke({
Expand All @@ -149,13 +182,14 @@ const setupBlobAddResponse = async function (
})
.delegate()

const blobAcceptReceipt = hasAcceptReceipt
// FIXME not generating the right kind of receipt here, but it should be enough for mocking
const blobAcceptReceipt = !hasAcceptReceipt
? await Receipt.issue({
issuer,
ran: blobAcceptTask.cid,
result: { error: new Error() },
})
: await generateAcceptReceipt(invocation.cid.toString())
: await generateAcceptReceipt(blobAcceptTask.cid.toString())
const blobConcludeAccept = await createConcludeInvocation(
issuer,
audience,
Expand All @@ -170,6 +204,7 @@ const setupBlobAddResponse = async function (
.fork(blobAllocateTask)
.fork(blobConcludeAllocate)
.fork(blobPutTask)
.fork(blobConcludePut)
.fork(blobAcceptTask)
.fork(blobConcludeAccept)
}
Expand Down

0 comments on commit 32fc658

Please sign in to comment.