Skip to content

Commit

Permalink
feat(private-rpc): improved rpc logic
Browse files Browse the repository at this point in the history
improved rpc logic to pipe non specifically handled requests.
  • Loading branch information
calvogenerico committed Nov 6, 2024
1 parent 9f87731 commit aa94397
Show file tree
Hide file tree
Showing 7 changed files with 358 additions and 34 deletions.
2 changes: 1 addition & 1 deletion packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
"jsdom": "19.0.0",
"playwright": "1.27.0",
"postcss": "^8.4.12",
"prettier-plugin-tailwindcss": "^0.2.2",
"prettier-plugin-tailwindcss": "^0.6.8",
"sass": "^1.49.9",
"start-server-and-test": "^1.14.0",
"storybook-vue3-router": "^2.2.1",
Expand Down
1 change: 0 additions & 1 deletion packages/private-rpc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
"nanoid": "^5.0.8",
"nochoices": "^1.1.4",
"pg": "^8.13.1",
"typed-rpc": "^6.1.1",
"viem": "^2.21.32",
"zod": "^3.23.8"
},
Expand Down
24 changes: 24 additions & 0 deletions packages/private-rpc/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,27 @@ export class HttpError extends Error implements FastifyError {
this.statusCode = status;
}
}

export class ExternalRpcError extends Error {
private code: number;
private data: unknown;

constructor(code: number, message: string, data: unknown) {
super('rpc error');
this.code = code;
this.message = message;
this.data = data;
}

getErrorCode(): number {
return this.code;
}

getErrorMessage(): string {
return this.message;
}

getErrorData(): unknown {
return this.data;
}
}
16 changes: 9 additions & 7 deletions packages/private-rpc/src/routes/rpc-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { WebServer } from '@/build-app';
import { z } from 'zod';
import { getUserByToken } from '@/query/user';
import { HttpError } from '@/errors';
import { handleRpc } from 'typed-rpc/server';
import { RpcService } from '@/rpc-service';
import { RpcCallHandler } from '@/rpc/rpc-service-2';
import { allHandlers } from '@/rpc/rpc-method-handlers';

const rpcSchema = { schema: { params: z.object({ token: z.string() }) } };

Expand All @@ -13,10 +13,12 @@ export function rpcRoutes(app: WebServer) {
(maybe) => maybe.expect(new HttpError('Unauthorized', 401)),
);

const res = await handleRpc(
req.body,
new RpcService(user.address, app.context.targetRpc, app.context.allRules),
);
reply.send(res);
const handler = new RpcCallHandler(allHandlers, {
currentUser: user.address,
targetRpcUrl: app.context.targetRpc,
rules: app.context.allRules,
});

reply.send(await handler.handle(req.body));
});
}
27 changes: 2 additions & 25 deletions packages/private-rpc/src/rpc-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,7 @@ import {
} from 'viem';
import { JsonValue } from 'typed-rpc/server';
import { RulesType } from '@/permissions';

class RpcError extends Error {
private code: number;
private data: unknown;

constructor(code: number, message: string, data: unknown) {
super('rpc error');
this.code = code;
this.message = message;
this.data = data;
}

getErrorCode(): number {
return this.code;
}

getErrorMessage(): string {
return this.message;
}

getErrorData(): unknown {
return this.data;
}
}
import { ExternalRpcError } from '@/errors';

async function doRpcRequest(
url: string,
Expand All @@ -44,7 +21,7 @@ async function doRpcRequest(

if (!res.ok) {
const errorBody = await res.json();
throw new RpcError(
throw new ExternalRpcError(
errorBody?.error?.code,
errorBody?.error?.message,
errorBody?.error?.data,
Expand Down
136 changes: 136 additions & 0 deletions packages/private-rpc/src/rpc/rpc-method-handlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import {
delegateCall,
JSONLike,
MethodHandler,
RequestContext,
} from '@/rpc/rpc-service-2';
import {
isAddressEqual,
parseTransaction,
recoverTransactionAddress,
} from 'viem';
import { z } from 'zod';
import { hexSchema } from '@/db/hex-row';

const callReqSchema = z.object({
from: hexSchema.optional(),
to: hexSchema,
gas: hexSchema.optional(),
gas_price: hexSchema.optional(),
max_fee_per_gas: hexSchema.optional(),
max_priority_fee_per_gas: z.number().optional(),
value: hexSchema.optional(),
data: hexSchema.optional(),
input: hexSchema.optional(),
nonce: hexSchema.optional(),
transaction_type: z.number().optional(),
access_list: z.any().optional(),
customData: z.any().optional(),
});
// type CallRequest = z.infer<typeof callReqSchema>;

const blockVarianteSchema = z.union([
hexSchema,
z.literal('earliest'),
z.literal('latest'),
z.literal('safe'),
z.literal('finalized'),
z.literal('pending'),
]);

const eth_call: MethodHandler = {
name: 'eth_call',
async handle(
context: RequestContext,
method: string,
params: unknown[],
id: number | string,
): Promise<JSONLike> {
const call = callReqSchema.parse(params[0]);
const blockVariant = blockVarianteSchema.optional().parse(params[1]);

if (
call.from === undefined ||
!isAddressEqual(call.from, context.currentUser)
) {
throw new Error('Wrong caller');
}

if (call.data === undefined) {
return delegateCall(
context.targetRpcUrl,
method,
[call, blockVariant],
id,
);
}

if (
!context.rules[call.to]?.canRead(context.currentUser, call.data ?? '0x')
) {
throw new Error('Unhautorized');
}

const jsonLikePromise = await delegateCall(
context.targetRpcUrl,
method,
[call, blockVariant],
id,
);
return jsonLikePromise;
},
};

const whoAmI = {
name: 'who_am_i',
async handle(
context: RequestContext,
_method: string,
_params: unknown[],
_id: number | string,
) {
return context.currentUser;
},
};

const zks_sendRawTransactionWithDetailedOutput = {
name: 'zks_sendRawTransactionWithDetailedOutput',
async handle(
context: RequestContext,
method: string,
params: unknown[],
id: number | string,
) {
const rawTx = hexSchema.parse(params[0]);
const tx = parseTransaction(rawTx);
const address = await recoverTransactionAddress({
serializedTransaction: rawTx as any,
});

if (!isAddressEqual(address, context.currentUser)) {
throw new Error('Wrong caller');
}

if (!tx.to || !tx.data) {
throw new Error('no target or no data');
}

if (!context.rules[tx.to]?.canWrite(context.currentUser, tx.data)) {
throw new Error('Unhautorized');
}

return delegateCall(context.targetRpcUrl, method, [rawTx], id);
},
};

const eth_sendRawTransaction = {
...zks_sendRawTransactionWithDetailedOutput,
name: 'eth_sendRawTransaction',
};

export const allHandlers = [
eth_call,
whoAmI,
zks_sendRawTransactionWithDetailedOutput,
eth_sendRawTransaction,
];
Loading

0 comments on commit aa94397

Please sign in to comment.