From fc159392bb0305653847e095ce5caa75d7aee378 Mon Sep 17 00:00:00 2001 From: Travis Vachon Date: Wed, 27 Mar 2024 16:32:33 -0700 Subject: [PATCH] fix: updates from Alan's review this addresses most of the feedback: - fail to start if w3up client principal is not passed - move function to test helpers - make w3up service DID configurable --- packages/api/src/bindings.d.ts | 4 +++ packages/api/src/config.js | 2 ++ packages/api/src/utils/context.js | 4 +++ packages/api/src/utils/w3up.js | 41 ++++------------------- packages/api/test/nfts-upload.spec.js | 10 ++++-- packages/api/test/utils/w3up-testing.js | 43 +++++++++++++++++++++++-- 6 files changed, 66 insertions(+), 38 deletions(-) diff --git a/packages/api/src/bindings.d.ts b/packages/api/src/bindings.d.ts index 0cbf62c935..8af47c0c21 100644 --- a/packages/api/src/bindings.d.ts +++ b/packages/api/src/bindings.d.ts @@ -101,6 +101,9 @@ export interface ServiceConfiguration { /** w3up connection URL (e.g. https://up.web3.storage) */ W3UP_URL?: string + /** w3up service DID (e.g. did:web:web3.storage) */ + W3UP_DID?: string + /** base64 encoded multiformats ed25519 secretKey */ W3_NFTSTORAGE_PRINCIPAL?: string @@ -148,6 +151,7 @@ export interface RouteContext { r2Uploader: Uploader ucanService: Service auth?: Auth + W3UP_DID?: string W3UP_URL?: string W3_NFTSTORAGE_PRINCIPAL?: string W3_NFTSTORAGE_PROOF?: string diff --git a/packages/api/src/config.js b/packages/api/src/config.js index fa022896fe..a835b7afde 100644 --- a/packages/api/src/config.js +++ b/packages/api/src/config.js @@ -63,6 +63,7 @@ export function serviceConfigFromVariables(vars) { COMMITHASH: vars.NFT_STORAGE_COMMITHASH || NFT_STORAGE_COMMITHASH, W3UP_URL: vars.W3UP_URL, + W3UP_DID: vars.W3UP_DID, W3_NFTSTORAGE_PRINCIPAL: vars.W3_NFTSTORAGE_PRINCIPAL, W3_NFTSTORAGE_PROOF: vars.W3_NFTSTORAGE_PROOF, W3_NFTSTORAGE_SPACE: vars.W3_NFTSTORAGE_SPACE, @@ -131,6 +132,7 @@ export function loadConfigVariables() { 'S3_ENDPOINT', 'SLACK_USER_REQUEST_WEBHOOK_URL', 'W3UP_URL', + 'W3UP_DID', 'W3_NFTSTORAGE_SPACE', 'W3_NFTSTORAGE_PRINCIPAL', 'W3_NFTSTORAGE_PROOF', diff --git a/packages/api/src/utils/context.js b/packages/api/src/utils/context.js index f2f604b01a..2cbfcc2e3a 100644 --- a/packages/api/src/utils/context.js +++ b/packages/api/src/utils/context.js @@ -8,6 +8,7 @@ import pkg from '../../package.json' import { Service } from 'ucan-storage/service' import { LinkdexApi } from './linkdex.js' import { createW3upClientFromConfig } from './w3up.js' +import { DID } from '@ucanto/core' /** * Obtains a route context object. @@ -66,6 +67,7 @@ export async function getContext(event, params) { const w3upConfig = { W3UP_URL: config.W3UP_URL, + W3UP_DID: config.W3UP_DID, W3_NFTSTORAGE_PRINCIPAL: config.W3_NFTSTORAGE_PRINCIPAL, W3_NFTSTORAGE_PROOF: config.W3_NFTSTORAGE_PROOF, W3_NFTSTORAGE_SPACE: config.W3_NFTSTORAGE_SPACE, @@ -75,12 +77,14 @@ export async function getContext(event, params) { let w3up if ( config.W3UP_URL && + config.W3UP_DID && config.W3_NFTSTORAGE_PRINCIPAL && config.W3_NFTSTORAGE_PROOF ) { try { const w3upWIP = await createW3upClientFromConfig({ url: config.W3UP_URL, + did: DID.parse(config.W3UP_DID).did(), principal: config.W3_NFTSTORAGE_PRINCIPAL, proof: config.W3_NFTSTORAGE_PROOF, }) diff --git a/packages/api/src/utils/w3up.js b/packages/api/src/utils/w3up.js index fa6dce84ca..8aff4a3758 100644 --- a/packages/api/src/utils/w3up.js +++ b/packages/api/src/utils/w3up.js @@ -4,9 +4,8 @@ import { StoreMemory } from '@web3-storage/access/stores/store-memory' import { CID } from 'multiformats/cid' import { base64 } from 'multiformats/bases/base64' import { identity } from 'multiformats/hashes/identity' -import { CarReader, CarWriter } from '@ipld/car' +import { CarReader } from '@ipld/car' import { importDAG } from '@ucanto/core/delegation' -import * as ucanto from '@ucanto/core' import * as W3upClient from '@web3-storage/w3up-client' import { connect } from '@ucanto/client' import { CAR, HTTP } from '@ucanto/transport' @@ -17,7 +16,10 @@ import { CAR, HTTP } from '@ucanto/transport' * @param {string|undefined} [env.proof] */ export async function getW3upClient({ principal, proof } = {}) { - const signer = principal ? ed25519.parse(principal) : await ed25519.generate() + if (!principal) { + throw new Error('could not get w3up client, no principal was passed') + } + const signer = ed25519.parse(principal) const store = new StoreMemory() const w3up = await W3UP.create({ principal: signer, store }) if (proof) { @@ -72,47 +74,18 @@ export async function readProofFromBytes(bytes) { } } -/** - * @param {import('@ucanto/interface').Delegation} delegation - delegation to encode - */ -export async function encodeDelegationAsCid(delegation) { - const { writer, out } = CarWriter.create() - /** @type {Array} */ - const carChunks = [] - await Promise.all([ - // write delegation blocks - (async () => { - for (const block of delegation.export()) { - // @ts-expect-error different Block types - await writer.put(block) - } - await writer.close() - })(), - // read out - (async () => { - for await (const chunk of out) { - carChunks.push(chunk) - } - })(), - ]) - // now get car chunks - const car = new Blob(carChunks) - const bytes = new Uint8Array(await car.arrayBuffer()) - const cid = CID.createV1(ucanto.CAR.code, identity.digest(bytes)) - return cid -} - /** * @param {object} options * @param {string} options.url * @param {string} options.principal * @param {string} options.proof + * @param {import('@ucanto/interface').DID} options.did */ export async function createW3upClientFromConfig(options) { const url = new URL(options.url) const principal = ed25519.parse(options.principal) const connection = connect({ - id: { did: () => 'did:web:web3.storage' }, + id: { did: () => options.did }, codec: CAR.outbound, channel: HTTP.open({ url, diff --git a/packages/api/test/nfts-upload.spec.js b/packages/api/test/nfts-upload.spec.js index 0a33f27417..283c940f20 100644 --- a/packages/api/test/nfts-upload.spec.js +++ b/packages/api/test/nfts-upload.spec.js @@ -29,21 +29,26 @@ import { createCarCid } from '../src/utils/car.js' import { createServer } from 'node:http' import { ed25519 } from '@ucanto/principal' import { delegate } from '@ucanto/core' -import { encodeDelegationAsCid } from '../src/utils/w3up.js' import { base64 } from 'multiformats/bases/base64' -import { createMockW3up, locate } from './utils/w3up-testing.js' +import { + createMockW3up, + locate, + encodeDelegationAsCid, +} from './utils/w3up-testing.js' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const nftStorageSpace = ed25519.generate() const nftStorageApiPrincipal = ed25519.generate() const nftStorageAccountEmailAllowListedForW3up = 'test+w3up@dev.nft.storage' +const mockW3upDID = 'did:web:test.web3.storage' let mockW3upStoreAddCount = 0 let mockW3upUploadAddCount = 0 const mockW3up = Promise.resolve( (async function () { const server = createServer( await createMockW3up({ + did: mockW3upDID, async onHandleStoreAdd(invocation) { mockW3upStoreAddCount++ }, @@ -68,6 +73,7 @@ test.before(async (t) => { overrides: { LINKDEX_URL: linkdexUrl, W3UP_URL: locate((await mockW3up).server).url.toString(), + W3UP_DID: mockW3upDID, W3_NFTSTORAGE_SPACE: (await nftStorageSpace).did(), W3_NFTSTORAGE_PRINCIPAL: ed25519.format(await nftStorageApiPrincipal), W3_NFTSTORAGE_PROOF: ( diff --git a/packages/api/test/utils/w3up-testing.js b/packages/api/test/utils/w3up-testing.js index b55df0e7a6..0df210ff6f 100644 --- a/packages/api/test/utils/w3up-testing.js +++ b/packages/api/test/utils/w3up-testing.js @@ -3,14 +3,51 @@ import * as Server from '@ucanto/server' import * as CAR from '@ucanto/transport/car' import * as consumers from 'stream/consumers' import { ed25519 } from '@ucanto/principal' +import { CarWriter } from '@ipld/car' +import * as ucanto from '@ucanto/core' +import { CID } from 'multiformats/cid' +import { identity } from 'multiformats/hashes/identity' + +/** + * @param {import('@ucanto/interface').Delegation} delegation - delegation to encode + */ +export async function encodeDelegationAsCid(delegation) { + const { writer, out } = CarWriter.create() + /** @type {Array} */ + const carChunks = [] + await Promise.all([ + // write delegation blocks + (async () => { + for (const block of delegation.export()) { + // @ts-expect-error different Block types + await writer.put(block) + } + await writer.close() + })(), + // read out + (async () => { + for await (const chunk of out) { + carChunks.push(chunk) + } + })(), + ]) + // now get car chunks + const car = new Blob(carChunks) + const bytes = new Uint8Array(await car.arrayBuffer()) + const cid = CID.createV1(ucanto.CAR.code, identity.digest(bytes)) + return cid +} /** * create a RequestListener that can be a mock up.web3.storage * @param {object} [options] - options + * @param {string} options.did * @param {(invocation: import('@ucanto/server').ProviderInput>) => Promise} [options.onHandleStoreAdd] - called at start of store/add handler * @param {(invocation: import('@ucanto/server').ProviderInput>) => Promise} [options.onHandleUploadAdd] - called at start of upload/add handler */ -export async function createMockW3up(options = {}) { +export async function createMockW3up( + options = { did: 'did:web:test.web3.storage' } +) { const service = { filecoin: { offer: Server.provide(Filecoin.offer, async (invocation) => { @@ -47,7 +84,9 @@ export async function createMockW3up(options = {}) { }), }, } - const serverId = (await ed25519.generate()).withDID('did:web:web3.storage') + const serverId = (await ed25519.generate()).withDID( + ucanto.DID.parse(options.did).did() + ) const server = Server.create({ id: serverId, service,