diff --git a/apps/extension/src/service-worker.ts b/apps/extension/src/service-worker.ts index 93f98076d0..be0a223cc8 100644 --- a/apps/extension/src/service-worker.ts +++ b/apps/extension/src/service-worker.ts @@ -38,6 +38,8 @@ import { FullViewingKey, WalletId, } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb'; +import { fvkCtx } from '@penumbra-zone/services/ctx/full-viewing-key'; +import { WalletJson } from '@penumbra-zone/types/src/wallet'; /** * When a user first onboards with the extension, they won't have chosen a gRPC @@ -63,24 +65,24 @@ const waitUntilGrpcEndpointExists = async () => { return grpcEndpointPromise.promise; }; -const startServices = async () => { +const startServices = async (wallet: WalletJson) => { const grpcEndpoint = await localExtStorage.get('grpcEndpoint'); - const wallet0 = (await localExtStorage.get('wallets'))[0]; - if (!wallet0) throw new Error('No wallet found'); - const services = new Services({ idbVersion: IDB_VERSION, grpcEndpoint, - walletId: WalletId.fromJsonString(wallet0.id), - fullViewingKey: FullViewingKey.fromJsonString(wallet0.fullViewingKey), + walletId: WalletId.fromJsonString(wallet.id), + fullViewingKey: FullViewingKey.fromJsonString(wallet.fullViewingKey), }); await services.initialize(); return services; }; const getServiceHandler = async () => { - const services = await backOff(startServices, { + const wallet0 = (await localExtStorage.get('wallets'))[0]; + if (!wallet0) throw new Error('No wallet found'); + + const services = await backOff(() => startServices(wallet0), { retry: (e, attemptNumber) => { if (process.env['NODE_ENV'] === 'development') console.warn("Prax couldn't start ", attemptNumber, e); @@ -111,6 +113,7 @@ const getServiceHandler = async () => { contextValues.set(stakingClientCtx, stakingClient); contextValues.set(servicesCtx, services); contextValues.set(approverCtx, approveTransaction); + contextValues.set(fvkCtx, FullViewingKey.fromJsonString(wallet0.fullViewingKey)); return Promise.resolve({ ...req, contextValues }); }, diff --git a/packages/services/src/ctx/full-viewing-key.ts b/packages/services/src/ctx/full-viewing-key.ts new file mode 100644 index 0000000000..fad8784f63 --- /dev/null +++ b/packages/services/src/ctx/full-viewing-key.ts @@ -0,0 +1,4 @@ +import { createContextKey } from '@connectrpc/connect'; +import { FullViewingKey } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb'; + +export const fvkCtx = createContextKey(undefined); diff --git a/packages/services/src/custody-service/authorize/index.test.ts b/packages/services/src/custody-service/authorize/index.test.ts index cf34f4ec8f..4cca582dfe 100644 --- a/packages/services/src/custody-service/authorize/index.test.ts +++ b/packages/services/src/custody-service/authorize/index.test.ts @@ -2,7 +2,13 @@ import { beforeEach, describe, expect, Mock, test, vi } from 'vitest'; import { createContextValues, createHandlerContext, HandlerContext } from '@connectrpc/connect'; import { approverCtx } from '../../ctx/approver'; import { extLocalCtx, extSessionCtx, servicesCtx } from '../../ctx/prax'; -import { IndexedDbMock, MockExtLocalCtx, MockExtSessionCtx, MockServices } from '../../test-utils'; +import { + IndexedDbMock, + MockExtLocalCtx, + MockExtSessionCtx, + MockServices, + testFullViewingKey, +} from '../../test-utils'; import { authorize } from '.'; import { AuthorizeRequest } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/custody/v1/custody_pb'; import { CustodyService } from '@buf/penumbra-zone_penumbra.connectrpc_es/penumbra/custody/v1/custody_connect'; @@ -13,6 +19,7 @@ import { import { Services } from '@penumbra-zone/services-context'; import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; import { UserChoice } from '@penumbra-zone/types/src/user-choice'; +import { fvkCtx } from '../../ctx/full-viewing-key'; describe('Authorize request handler', () => { let mockServices: MockServices; @@ -36,7 +43,7 @@ describe('Authorize request handler', () => { mockServices = { getWalletServices: vi.fn(() => - Promise.resolve({ indexedDb: mockIndexedDb, viewServer: { fullViewingKey: 'fvk' } }), + Promise.resolve({ indexedDb: mockIndexedDb }), ) as MockServices['getWalletServices'], }; @@ -92,7 +99,8 @@ describe('Authorize request handler', () => { .set(extLocalCtx, mockExtLocalCtx as unknown) .set(approverCtx, mockApproverCtx as unknown) .set(extSessionCtx, mockExtSessionCtx as unknown) - .set(servicesCtx, mockServices as unknown as Services), + .set(servicesCtx, mockServices as unknown as Services) + .set(fvkCtx, testFullViewingKey), }); for (const record of testAssetsMetadata) { diff --git a/packages/services/src/custody-service/authorize/index.ts b/packages/services/src/custody-service/authorize/index.ts index 80b782bd97..77b16015a0 100644 --- a/packages/services/src/custody-service/authorize/index.ts +++ b/packages/services/src/custody-service/authorize/index.ts @@ -1,5 +1,5 @@ import type { Impl } from '..'; -import { extLocalCtx, extSessionCtx, servicesCtx } from '../../ctx/prax'; +import { extLocalCtx, extSessionCtx } from '../../ctx/prax'; import { approverCtx } from '../../ctx/approver'; import { generateSpendKey } from '@penumbra-zone/wasm/src/keys'; import { authorizePlan } from '@penumbra-zone/wasm/src/build'; @@ -10,11 +10,12 @@ import { UserChoice } from '@penumbra-zone/types/src/user-choice'; import { assertSwapClaimAddressesBelongToCurrentUser } from './assert-swap-claim-addresses-belong-to-current-user'; import { isControlledAddress } from '@penumbra-zone/wasm/src/address'; import { AuthorizeRequest } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/custody/v1/custody_pb'; +import { fvkCtx } from '../../ctx/full-viewing-key'; export const authorize: Impl['authorize'] = async (req, ctx) => { if (!req.plan) throw new ConnectError('No plan included in request', Code.InvalidArgument); - await assertValidRequest(req, ctx); + assertValidRequest(req, ctx); const approveReq = ctx.values.get(approverCtx); const sess = ctx.values.get(extSessionCtx); @@ -62,10 +63,11 @@ export const authorize: Impl['authorize'] = async (req, ctx) => { * * Add more assertions to this function as needed. */ -const assertValidRequest = async (req: AuthorizeRequest, ctx: HandlerContext): Promise => { - const walletServices = await ctx.values.get(servicesCtx).getWalletServices(); - const { fullViewingKey } = walletServices.viewServer; - +const assertValidRequest = (req: AuthorizeRequest, ctx: HandlerContext): void => { + const fullViewingKey = ctx.values.get(fvkCtx); + if (!fullViewingKey) { + throw new ConnectError('Cannot access full viewing key', Code.Unauthenticated); + } assertSwapClaimAddressesBelongToCurrentUser(req.plan!, address => isControlledAddress(fullViewingKey, address), ); diff --git a/packages/services/src/view-service/address-by-index.test.ts b/packages/services/src/view-service/address-by-index.test.ts index b840cbd263..d2e2edacfd 100644 --- a/packages/services/src/view-service/address-by-index.test.ts +++ b/packages/services/src/view-service/address-by-index.test.ts @@ -5,33 +5,22 @@ import { } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1/view_pb'; import { createContextValues, createHandlerContext, HandlerContext } from '@connectrpc/connect'; import { ViewService } from '@buf/penumbra-zone_penumbra.connectrpc_es/penumbra/view/v1/view_connect'; -import { servicesCtx } from '../ctx/prax'; import { addressByIndex } from './address-by-index'; import { Address } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb'; -import type { ServicesInterface } from '@penumbra-zone/types/src/services'; import { testFullViewingKey } from '../test-utils'; +import { fvkCtx } from '../ctx/full-viewing-key'; describe('AddressByIndex request handler', () => { - let mockServices: ServicesInterface; let mockCtx: HandlerContext; beforeEach(() => { - mockServices = { - getWalletServices: () => - Promise.resolve({ - viewServer: { - fullViewingKey: testFullViewingKey, - }, - }), - } as ServicesInterface; - mockCtx = createHandlerContext({ service: ViewService, method: ViewService.methods.addressByIndex, protocolName: 'mock', requestMethod: 'MOCK', url: '/mock', - contextValues: createContextValues().set(servicesCtx, mockServices), + contextValues: createContextValues().set(fvkCtx, testFullViewingKey), }); }); diff --git a/packages/services/src/view-service/address-by-index.ts b/packages/services/src/view-service/address-by-index.ts index 3b8232e50f..59615048bd 100644 --- a/packages/services/src/view-service/address-by-index.ts +++ b/packages/services/src/view-service/address-by-index.ts @@ -1,14 +1,14 @@ import type { Impl } from '.'; -import { servicesCtx } from '../ctx/prax'; import { getAddressByIndex } from '@penumbra-zone/wasm/src/keys'; - -export const addressByIndex: Impl['addressByIndex'] = async (req, ctx) => { - const services = ctx.values.get(servicesCtx); - const { - viewServer: { fullViewingKey }, - } = await services.getWalletServices(); - +import { fvkCtx } from '../ctx/full-viewing-key'; +import { Code, ConnectError } from '@connectrpc/connect'; + +export const addressByIndex: Impl['addressByIndex'] = (req, ctx) => { + const fullViewingKey = ctx.values.get(fvkCtx); + if (!fullViewingKey) { + throw new ConnectError('Cannot access full viewing key', Code.Unauthenticated); + } const address = getAddressByIndex(fullViewingKey, req.addressIndex?.account ?? 0); return { address }; diff --git a/packages/services/src/view-service/authorize-and-build.ts b/packages/services/src/view-service/authorize-and-build.ts index ae21c761d8..58a2ed7f4d 100644 --- a/packages/services/src/view-service/authorize-and-build.ts +++ b/packages/services/src/view-service/authorize-and-build.ts @@ -4,6 +4,7 @@ import { optimisticBuild } from './util/build-tx'; import { custodyAuthorize } from './util/custody-authorize'; import { getWitness } from '@penumbra-zone/wasm/src/build'; import { Code, ConnectError } from '@connectrpc/connect'; +import { fvkCtx } from '../ctx/full-viewing-key'; export const authorizeAndBuild: Impl['authorizeAndBuild'] = async function* ( { transactionPlan }, @@ -12,10 +13,12 @@ export const authorizeAndBuild: Impl['authorizeAndBuild'] = async function* ( const services = ctx.values.get(servicesCtx); if (!transactionPlan) throw new ConnectError('No tx plan in request', Code.InvalidArgument); - const { - indexedDb, - viewServer: { fullViewingKey }, - } = await services.getWalletServices(); + const { indexedDb } = await services.getWalletServices(); + const fullViewingKey = ctx.values.get(fvkCtx); + if (!fullViewingKey) { + throw new ConnectError('Cannot access full viewing key', Code.Unauthenticated); + } + const sct = await indexedDb.getStateCommitmentTree(); const witnessData = getWitness(transactionPlan, sct); diff --git a/packages/services/src/view-service/balances.test.ts b/packages/services/src/view-service/balances.test.ts index e46861c8d8..f57ca0566f 100644 --- a/packages/services/src/view-service/balances.test.ts +++ b/packages/services/src/view-service/balances.test.ts @@ -12,7 +12,7 @@ import { import { createContextValues, createHandlerContext, HandlerContext } from '@connectrpc/connect'; import { beforeEach, describe, expect, test, vi } from 'vitest'; -import { Services } from '@penumbra-zone/services-context/src/index'; +import { Services } from '@penumbra-zone/services-context'; import { IndexedDbMock, MockServices, TendermintMock, testFullViewingKey } from '../test-utils'; import { AssetId, @@ -29,6 +29,7 @@ import { import { getAddressIndex } from '@penumbra-zone/getters/src/address-view'; import { base64ToUint8Array } from '@penumbra-zone/types/src/base64'; import { multiplyAmountByNumber } from '@penumbra-zone/types/src/amount'; +import { fvkCtx } from '../ctx/full-viewing-key'; const assertOnlyUniqueAssetIds = (responses: BalancesResponse[], accountId: number) => { const account0Res = responses.filter( @@ -67,10 +68,6 @@ describe('Balances request handler', () => { assetMetadata: vi.fn(), }; - const mockViewServer = { - fullViewingKey: testFullViewingKey, - }; - mockTendermint = { latestBlockHeight: vi.fn(), }; @@ -80,7 +77,6 @@ describe('Balances request handler', () => { getWalletServices: vi.fn(() => Promise.resolve({ indexedDb: mockIndexedDb, - viewServer: mockViewServer, querier: { shieldedPool: mockShieldedPool, tendermint: mockTendermint, @@ -95,7 +91,9 @@ describe('Balances request handler', () => { protocolName: 'mock', requestMethod: 'MOCK', url: '/mock', - contextValues: createContextValues().set(servicesCtx, mockServices as unknown as Services), + contextValues: createContextValues() + .set(servicesCtx, mockServices as unknown as Services) + .set(fvkCtx, testFullViewingKey), }); for (const record of testData) { diff --git a/packages/services/src/view-service/ephemeral-address.test.ts b/packages/services/src/view-service/ephemeral-address.test.ts index 05c9aefcd1..8e143052fb 100644 --- a/packages/services/src/view-service/ephemeral-address.test.ts +++ b/packages/services/src/view-service/ephemeral-address.test.ts @@ -5,33 +5,22 @@ import { } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1/view_pb'; import { createContextValues, createHandlerContext, HandlerContext } from '@connectrpc/connect'; import { ViewService } from '@buf/penumbra-zone_penumbra.connectrpc_es/penumbra/view/v1/view_connect'; -import { servicesCtx } from '../ctx/prax'; import { Address } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb'; import { ephemeralAddress } from './ephemeral-address'; -import type { ServicesInterface } from '@penumbra-zone/types/src/services'; import { testFullViewingKey } from '../test-utils'; +import { fvkCtx } from '../ctx/full-viewing-key'; describe('EphemeralAddress request handler', () => { - let mockServices: ServicesInterface; let mockCtx: HandlerContext; beforeEach(() => { - mockServices = { - getWalletServices: () => - Promise.resolve({ - viewServer: { - fullViewingKey: testFullViewingKey, - }, - }), - } as ServicesInterface; - mockCtx = createHandlerContext({ service: ViewService, method: ViewService.methods.ephemeralAddress, protocolName: 'mock', requestMethod: 'MOCK', url: '/mock', - contextValues: createContextValues().set(servicesCtx, mockServices), + contextValues: createContextValues().set(fvkCtx, testFullViewingKey), }); }); @@ -43,8 +32,8 @@ describe('EphemeralAddress request handler', () => { expect(ephemeralAddressResponse.address).toBeInstanceOf(Address); }); - test('should get an error if addressIndex is missing', async () => { - await expect(ephemeralAddress(new EphemeralAddressRequest(), mockCtx)).rejects.toThrow( + test('should get an error if addressIndex is missing', () => { + expect(() => ephemeralAddress(new EphemeralAddressRequest(), mockCtx)).toThrowError( 'Missing address index', ); }); diff --git a/packages/services/src/view-service/ephemeral-address.ts b/packages/services/src/view-service/ephemeral-address.ts index 684799f488..0ea2e0a4ac 100644 --- a/packages/services/src/view-service/ephemeral-address.ts +++ b/packages/services/src/view-service/ephemeral-address.ts @@ -1,17 +1,17 @@ import type { Impl } from '.'; -import { servicesCtx } from '../ctx/prax'; import { getEphemeralByIndex } from '@penumbra-zone/wasm/src/keys'; +import { fvkCtx } from '../ctx/full-viewing-key'; +import { Code, ConnectError } from '@connectrpc/connect'; -export const ephemeralAddress: Impl['ephemeralAddress'] = async (req, ctx) => { - const services = ctx.values.get(servicesCtx); - const { - viewServer: { fullViewingKey }, - } = await services.getWalletServices(); - +export const ephemeralAddress: Impl['ephemeralAddress'] = (req, ctx) => { if (!req.addressIndex) { throw new Error('Missing address index'); } + const fullViewingKey = ctx.values.get(fvkCtx); + if (!fullViewingKey) { + throw new ConnectError('Cannot access full viewing key', Code.Unauthenticated); + } const address = getEphemeralByIndex(fullViewingKey, req.addressIndex.account); return { address }; diff --git a/packages/services/src/view-service/index-by-address.test.ts b/packages/services/src/view-service/index-by-address.test.ts index 63cc640426..db0029308b 100644 --- a/packages/services/src/view-service/index-by-address.test.ts +++ b/packages/services/src/view-service/index-by-address.test.ts @@ -2,36 +2,25 @@ import { beforeEach, describe, expect, test } from 'vitest'; import { IndexByAddressRequest } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1/view_pb'; import { createContextValues, createHandlerContext, HandlerContext } from '@connectrpc/connect'; import { ViewService } from '@buf/penumbra-zone_penumbra.connectrpc_es/penumbra/view/v1/view_connect'; -import { servicesCtx } from '../ctx/prax'; import { Address } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb'; import { indexByAddress } from './index-by-address'; import { getAddressByIndex, getEphemeralByIndex } from '@penumbra-zone/wasm/src/keys'; -import type { ServicesInterface } from '@penumbra-zone/types/src/services'; import { bech32ToFullViewingKey } from '@penumbra-zone/bech32/src/full-viewing-key'; import { testFullViewingKey } from '../test-utils'; +import { fvkCtx } from '../ctx/full-viewing-key'; describe('IndexByAddress request handler', () => { - let mockServices: ServicesInterface; let mockCtx: HandlerContext; let testAddress: Address; beforeEach(() => { - mockServices = { - getWalletServices: () => - Promise.resolve({ - viewServer: { - fullViewingKey: testFullViewingKey, - }, - }), - } as ServicesInterface; - mockCtx = createHandlerContext({ service: ViewService, method: ViewService.methods.indexByAddress, protocolName: 'mock', requestMethod: 'MOCK', url: '/mock', - contextValues: createContextValues().set(servicesCtx, mockServices), + contextValues: createContextValues().set(fvkCtx, testFullViewingKey), }); testAddress = getAddressByIndex(testFullViewingKey, 0); diff --git a/packages/services/src/view-service/index-by-address.ts b/packages/services/src/view-service/index-by-address.ts index 810606e326..708fd3be29 100644 --- a/packages/services/src/view-service/index-by-address.ts +++ b/packages/services/src/view-service/index-by-address.ts @@ -1,17 +1,16 @@ import type { Impl } from '.'; -import { servicesCtx } from '../ctx/prax'; import { getAddressIndexByAddress } from '@penumbra-zone/wasm/src/address'; import { Code, ConnectError } from '@connectrpc/connect'; +import { fvkCtx } from '../ctx/full-viewing-key'; -export const indexByAddress: Impl['indexByAddress'] = async (req, ctx) => { +export const indexByAddress: Impl['indexByAddress'] = (req, ctx) => { if (!req.address) throw new ConnectError('no address given in request', Code.InvalidArgument); - const services = ctx.values.get(servicesCtx); - const { - viewServer: { fullViewingKey }, - } = await services.getWalletServices(); - + const fullViewingKey = ctx.values.get(fvkCtx); + if (!fullViewingKey) { + throw new ConnectError('Cannot access full viewing key', Code.Unauthenticated); + } const addressIndex = getAddressIndexByAddress(fullViewingKey, req.address); if (!addressIndex) return {}; diff --git a/packages/services/src/view-service/transaction-info-by-hash.test.ts b/packages/services/src/view-service/transaction-info-by-hash.test.ts index 6613d3845a..d36e45f755 100644 --- a/packages/services/src/view-service/transaction-info-by-hash.test.ts +++ b/packages/services/src/view-service/transaction-info-by-hash.test.ts @@ -6,15 +6,15 @@ import { import { createContextValues, createHandlerContext, HandlerContext } from '@connectrpc/connect'; import { ViewService } from '@buf/penumbra-zone_penumbra.connectrpc_es/penumbra/view/v1/view_connect'; import { servicesCtx } from '../ctx/prax'; -import { IndexedDbMock, MockServices, TendermintMock, ViewServerMock } from '../test-utils'; +import { IndexedDbMock, MockServices, TendermintMock, testFullViewingKey } from '../test-utils'; import { transactionInfoByHash } from './transaction-info-by-hash'; import { TransactionId } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/txhash/v1/txhash_pb'; -import type { Services } from '@penumbra-zone/services-context/src/index'; +import type { Services } from '@penumbra-zone/services-context'; import { Transaction, TransactionPerspective, } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/transaction/v1/transaction_pb'; -import { bech32ToFullViewingKey } from '@penumbra-zone/bech32/src/full-viewing-key'; +import { fvkCtx } from '../ctx/full-viewing-key'; const mockTransactionInfo = vi.hoisted(() => vi.fn()); vi.mock('@penumbra-zone/wasm/src/transaction', () => ({ @@ -23,7 +23,6 @@ vi.mock('@penumbra-zone/wasm/src/transaction', () => ({ describe('TransactionInfoByHash request handler', () => { let mockServices: MockServices; let mockIndexedDb: IndexedDbMock; - let mockViewServer: ViewServerMock; let mockCtx: HandlerContext; let mockTendermint: TendermintMock; @@ -34,11 +33,6 @@ describe('TransactionInfoByHash request handler', () => { getTransaction: vi.fn(), constants: vi.fn(), }; - mockViewServer = { - fullViewingKey: bech32ToFullViewingKey( - 'penumbrafullviewingkey1vzfytwlvq067g2kz095vn7sgcft47hga40atrg5zu2crskm6tyyjysm28qg5nth2fqmdf5n0q530jreumjlsrcxjwtfv6zdmfpe5kqsa5lg09', - ), - }; mockTendermint = { getTransaction: vi.fn(), }; @@ -46,7 +40,6 @@ describe('TransactionInfoByHash request handler', () => { getWalletServices: vi.fn(() => Promise.resolve({ indexedDb: mockIndexedDb, - viewServer: mockViewServer, querier: { tendermint: mockTendermint, }, @@ -60,7 +53,9 @@ describe('TransactionInfoByHash request handler', () => { protocolName: 'mock', requestMethod: 'MOCK', url: '/mock', - contextValues: createContextValues().set(servicesCtx, mockServices as unknown as Services), + contextValues: createContextValues() + .set(servicesCtx, mockServices as unknown as Services) + .set(fvkCtx, testFullViewingKey), }); mockTransactionInfo.mockReturnValueOnce({ txp: transactionPerspective, diff --git a/packages/services/src/view-service/transaction-info-by-hash.ts b/packages/services/src/view-service/transaction-info-by-hash.ts index 85a7305ff9..52b899762c 100644 --- a/packages/services/src/view-service/transaction-info-by-hash.ts +++ b/packages/services/src/view-service/transaction-info-by-hash.ts @@ -3,16 +3,18 @@ import { servicesCtx } from '../ctx/prax'; import { Code, ConnectError } from '@connectrpc/connect'; import { generateTransactionInfo } from '@penumbra-zone/wasm/src/transaction'; import { TransactionInfo } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1/view_pb'; +import { fvkCtx } from '../ctx/full-viewing-key'; export const transactionInfoByHash: Impl['transactionInfoByHash'] = async (req, ctx) => { - const services = ctx.values.get(servicesCtx); - const { - indexedDb, - querier, - viewServer: { fullViewingKey }, - } = await services.getWalletServices(); if (!req.id) throw new ConnectError('Missing transaction ID in request', Code.InvalidArgument); + const services = ctx.values.get(servicesCtx); + const { indexedDb, querier } = await services.getWalletServices(); + const fullViewingKey = ctx.values.get(fvkCtx); + if (!fullViewingKey) { + throw new ConnectError('Cannot access full viewing key', Code.Unauthenticated); + } + // Check database for transaction first // if not in database, query tendermint for public info on the transaction const { transaction, height } = diff --git a/packages/services/src/view-service/transaction-info.test.ts b/packages/services/src/view-service/transaction-info.test.ts index 1ecb2e5379..250add9142 100644 --- a/packages/services/src/view-service/transaction-info.test.ts +++ b/packages/services/src/view-service/transaction-info.test.ts @@ -10,9 +10,10 @@ import { TransactionInfoRequest, TransactionInfoResponse, } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1/view_pb'; -import { IndexedDbMock, MockServices, testFullViewingKey, ViewServerMock } from '../test-utils'; -import { Services } from '@penumbra-zone/services-context/src/index'; +import { IndexedDbMock, MockServices, testFullViewingKey } from '../test-utils'; +import { Services } from '@penumbra-zone/services-context'; import { transactionInfo } from './transaction-info'; +import { fvkCtx } from '../ctx/full-viewing-key'; const mockTransactionInfo = vi.hoisted(() => vi.fn()); vi.mock('@penumbra-zone/wasm/src/transaction', () => ({ @@ -23,7 +24,6 @@ describe('TransactionInfo request handler', () => { let mockServices: MockServices; let mockCtx: HandlerContext; let mockIndexedDb: IndexedDbMock; - let mockViewServer: ViewServerMock; let req: TransactionInfoRequest; beforeEach(() => { @@ -39,13 +39,9 @@ describe('TransactionInfo request handler', () => { constants: vi.fn(), }; - mockViewServer = { - fullViewingKey: testFullViewingKey, - }; - mockServices = { getWalletServices: vi.fn(() => - Promise.resolve({ indexedDb: mockIndexedDb, viewServer: mockViewServer }), + Promise.resolve({ indexedDb: mockIndexedDb }), ) as MockServices['getWalletServices'], }; @@ -55,7 +51,9 @@ describe('TransactionInfo request handler', () => { protocolName: 'mock', requestMethod: 'MOCK', url: '/mock', - contextValues: createContextValues().set(servicesCtx, mockServices as unknown as Services), + contextValues: createContextValues() + .set(servicesCtx, mockServices as unknown as Services) + .set(fvkCtx, testFullViewingKey), }); mockTransactionInfo.mockReturnValue({ diff --git a/packages/services/src/view-service/transaction-info.ts b/packages/services/src/view-service/transaction-info.ts index 931943b021..f3c543a22e 100644 --- a/packages/services/src/view-service/transaction-info.ts +++ b/packages/services/src/view-service/transaction-info.ts @@ -2,13 +2,17 @@ import type { Impl } from '.'; import { servicesCtx } from '../ctx/prax'; import { TransactionInfo } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1/view_pb'; import { generateTransactionInfo } from '@penumbra-zone/wasm/src/transaction'; +import { fvkCtx } from '../ctx/full-viewing-key'; +import { Code, ConnectError } from '@connectrpc/connect'; export const transactionInfo: Impl['transactionInfo'] = async function* (req, ctx) { const services = ctx.values.get(servicesCtx); - const { - indexedDb, - viewServer: { fullViewingKey }, - } = await services.getWalletServices(); + const { indexedDb } = await services.getWalletServices(); + + const fullViewingKey = ctx.values.get(fvkCtx); + if (!fullViewingKey) { + throw new ConnectError('Cannot access full viewing key', Code.Unauthenticated); + } for await (const txRecord of indexedDb.iterateTransactions()) { // filter transactions between startHeight and endHeight, inclusive diff --git a/packages/services/src/view-service/transaction-planner/index.test.ts b/packages/services/src/view-service/transaction-planner/index.test.ts index 195e1e154f..1cc6128d27 100644 --- a/packages/services/src/view-service/transaction-planner/index.test.ts +++ b/packages/services/src/view-service/transaction-planner/index.test.ts @@ -3,13 +3,14 @@ import { TransactionPlannerRequest } from '@buf/penumbra-zone_penumbra.bufbuild_ import { createContextValues, createHandlerContext, HandlerContext } from '@connectrpc/connect'; import { ViewService } from '@buf/penumbra-zone_penumbra.connectrpc_es/penumbra/view/v1/view_connect'; import { servicesCtx } from '../../ctx/prax'; -import { IndexedDbMock, MockServices, testFullViewingKey, ViewServerMock } from '../../test-utils'; +import { IndexedDbMock, MockServices, testFullViewingKey } from '../../test-utils'; import type { Services } from '@penumbra-zone/services-context'; import { FmdParameters } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/shielded_pool/v1/shielded_pool_pb'; import { AppParameters } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/app/v1/app_pb'; import { SctParameters } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/sct/v1/sct_pb'; import { GasPrices } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/fee/v1/fee_pb'; import { transactionPlanner } from '.'; +import { fvkCtx } from '../../ctx/full-viewing-key'; const mockPlanTransaction = vi.hoisted(() => vi.fn()); vi.mock('@penumbra-zone/wasm/src/planner', () => ({ @@ -18,7 +19,6 @@ vi.mock('@penumbra-zone/wasm/src/planner', () => ({ describe('TransactionPlanner request handler', () => { let mockServices: MockServices; let mockIndexedDb: IndexedDbMock; - let mockViewServer: ViewServerMock; let mockCtx: HandlerContext; let req: TransactionPlannerRequest; @@ -31,14 +31,11 @@ describe('TransactionPlanner request handler', () => { getGasPrices: vi.fn(), constants: vi.fn(), }; - mockViewServer = { - fullViewingKey: testFullViewingKey, - }; + mockServices = { getWalletServices: vi.fn(() => Promise.resolve({ indexedDb: mockIndexedDb, - viewServer: mockViewServer, }), ) as MockServices['getWalletServices'], }; @@ -49,7 +46,9 @@ describe('TransactionPlanner request handler', () => { protocolName: 'mock', requestMethod: 'MOCK', url: '/mock', - contextValues: createContextValues().set(servicesCtx, mockServices as unknown as Services), + contextValues: createContextValues() + .set(servicesCtx, mockServices as unknown as Services) + .set(fvkCtx, testFullViewingKey), }); req = new TransactionPlannerRequest({}); diff --git a/packages/services/src/view-service/transaction-planner/index.ts b/packages/services/src/view-service/transaction-planner/index.ts index 4f427ec0d6..4a462d0066 100644 --- a/packages/services/src/view-service/transaction-planner/index.ts +++ b/packages/services/src/view-service/transaction-planner/index.ts @@ -4,13 +4,16 @@ import { planTransaction } from '@penumbra-zone/wasm/src/planner'; import { Code, ConnectError } from '@connectrpc/connect'; import { assertSwapAssetsAreNotTheSame } from './assert-swap-assets-are-not-the-same'; import { TransactionPlannerRequest } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1/view_pb'; +import { fvkCtx } from '../../ctx/full-viewing-key'; export const transactionPlanner: Impl['transactionPlanner'] = async (req, ctx) => { const services = ctx.values.get(servicesCtx); - const { - indexedDb, - viewServer: { fullViewingKey }, - } = await services.getWalletServices(); + const { indexedDb } = await services.getWalletServices(); + + const fullViewingKey = ctx.values.get(fvkCtx); + if (!fullViewingKey) { + throw new ConnectError('Cannot access full viewing key', Code.Unauthenticated); + } assertValidRequest(req); diff --git a/packages/services/src/view-service/witness-and-build.ts b/packages/services/src/view-service/witness-and-build.ts index a1ae060694..e30e295525 100644 --- a/packages/services/src/view-service/witness-and-build.ts +++ b/packages/services/src/view-service/witness-and-build.ts @@ -7,6 +7,7 @@ import { getWitness } from '@penumbra-zone/wasm/src/build'; import { Code, ConnectError } from '@connectrpc/connect'; import { AuthorizationData } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/transaction/v1/transaction_pb'; +import { fvkCtx } from '../ctx/full-viewing-key'; export const witnessAndBuild: Impl['witnessAndBuild'] = async function* ( { authorizationData, transactionPlan }, @@ -15,10 +16,12 @@ export const witnessAndBuild: Impl['witnessAndBuild'] = async function* ( const services = ctx.values.get(servicesCtx); if (!transactionPlan) throw new ConnectError('No tx plan', Code.InvalidArgument); - const { - indexedDb, - viewServer: { fullViewingKey }, - } = await services.getWalletServices(); + const { indexedDb } = await services.getWalletServices(); + const fullViewingKey = ctx.values.get(fvkCtx); + if (!fullViewingKey) { + throw new ConnectError('Cannot access full viewing key', Code.Unauthenticated); + } + const sct = await indexedDb.getStateCommitmentTree(); const witnessData = getWitness(transactionPlan, sct); diff --git a/packages/types/src/servers.ts b/packages/types/src/servers.ts index e17169b3d1..2975104943 100644 --- a/packages/types/src/servers.ts +++ b/packages/types/src/servers.ts @@ -1,10 +1,8 @@ import { ScanBlockResult } from './state-commitment-tree'; import { CompactBlock } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/compact_block/v1/compact_block_pb'; import { MerkleRoot } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/crypto/tct/v1/tct_pb'; -import { FullViewingKey } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb'; export interface ViewServerInterface { - fullViewingKey: FullViewingKey; scanBlock(compactBlock: CompactBlock): Promise; flushUpdates(): ScanBlockResult; resetTreeToStored(): Promise;