chore: sync all changes to Gitea
Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
- Config, docs, scripts, and backup manifests - Submodule refs unchanged (m = modified content in submodules) Made-with: Cursor
This commit is contained in:
@@ -1,262 +0,0 @@
|
||||
/**
|
||||
* RPC Handler - Handles JSON-RPC 2.0 requests and routes them appropriately
|
||||
*/
|
||||
|
||||
import { BesuClient } from '../clients/besu-client';
|
||||
import { TxInterceptor } from '../interceptors/tx-interceptor';
|
||||
import { Web3SignerClient } from '../clients/web3signer-client';
|
||||
|
||||
export interface JsonRpcRequest {
|
||||
jsonrpc: string;
|
||||
method: string;
|
||||
params?: any[];
|
||||
id: string | number | null;
|
||||
}
|
||||
|
||||
export interface JsonRpcResponse {
|
||||
jsonrpc: string;
|
||||
result?: any;
|
||||
error?: {
|
||||
code: number;
|
||||
message: string;
|
||||
data?: any;
|
||||
};
|
||||
id: string | number | null;
|
||||
}
|
||||
|
||||
// Dangerous methods that should be denied by default
|
||||
// These are admin/debug methods that expose sensitive information
|
||||
const DENIED_METHODS = [
|
||||
'admin_',
|
||||
'debug_',
|
||||
'txpool_',
|
||||
'miner_',
|
||||
];
|
||||
|
||||
// Private network consensus methods (CLIQUE, IBFT, QBFT)
|
||||
// These are allowed to pass through - they are used for private network management
|
||||
const PRIVATE_NETWORK_METHODS = [
|
||||
'clique_',
|
||||
'ibft_',
|
||||
'qbft_',
|
||||
'perm_',
|
||||
];
|
||||
|
||||
// Methods that should be intercepted
|
||||
const INTERCEPTED_METHODS = ['eth_sendTransaction'];
|
||||
|
||||
export class RpcHandler {
|
||||
private besuClient: BesuClient;
|
||||
private txInterceptor: TxInterceptor;
|
||||
private web3SignerClient?: Web3SignerClient;
|
||||
private chainId: number;
|
||||
private allowPrivateNetworkMethods: boolean;
|
||||
|
||||
constructor(
|
||||
besuClient: BesuClient,
|
||||
txInterceptor: TxInterceptor,
|
||||
chainId: number,
|
||||
allowPrivateNetworkMethods: boolean = true,
|
||||
web3SignerClient?: Web3SignerClient
|
||||
) {
|
||||
this.besuClient = besuClient;
|
||||
this.txInterceptor = txInterceptor;
|
||||
this.web3SignerClient = web3SignerClient;
|
||||
this.chainId = chainId;
|
||||
this.allowPrivateNetworkMethods = allowPrivateNetworkMethods;
|
||||
}
|
||||
|
||||
private isMethodDenied(method: string): boolean {
|
||||
// Always deny admin/debug methods
|
||||
if (DENIED_METHODS.some(prefix => method.startsWith(prefix))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Conditionally deny private network methods based on configuration
|
||||
if (!this.allowPrivateNetworkMethods) {
|
||||
return PRIVATE_NETWORK_METHODS.some(prefix => method.startsWith(prefix));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private isPrivateNetworkMethod(method: string): boolean {
|
||||
return PRIVATE_NETWORK_METHODS.some(prefix => method.startsWith(prefix));
|
||||
}
|
||||
|
||||
private isMethodIntercepted(method: string): boolean {
|
||||
return INTERCEPTED_METHODS.includes(method);
|
||||
}
|
||||
|
||||
async handleRequest(request: JsonRpcRequest): Promise<JsonRpcResponse> {
|
||||
// Validate JSON-RPC version
|
||||
if (request.jsonrpc !== '2.0') {
|
||||
return {
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32600,
|
||||
message: 'Invalid Request',
|
||||
data: 'jsonrpc must be "2.0"',
|
||||
},
|
||||
id: request.id,
|
||||
};
|
||||
}
|
||||
|
||||
// Validate method
|
||||
if (!request.method || typeof request.method !== 'string') {
|
||||
return {
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32600,
|
||||
message: 'Invalid Request',
|
||||
data: 'method is required and must be a string',
|
||||
},
|
||||
id: request.id,
|
||||
};
|
||||
}
|
||||
|
||||
// Check if method is denied
|
||||
if (this.isMethodDenied(request.method)) {
|
||||
const isPrivateMethod = this.isPrivateNetworkMethod(request.method);
|
||||
const reason = isPrivateMethod
|
||||
? 'Private network methods are disabled (set ALLOW_PRIVATE_NETWORK_METHODS=true)'
|
||||
: 'Method is denied for security reasons';
|
||||
|
||||
return {
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32601,
|
||||
message: 'Method not found',
|
||||
data: `Method ${request.method} is not allowed: ${reason}`,
|
||||
},
|
||||
id: request.id,
|
||||
};
|
||||
}
|
||||
|
||||
// Handle intercepted methods
|
||||
if (this.isMethodIntercepted(request.method)) {
|
||||
return this.handleInterceptedMethod(request);
|
||||
}
|
||||
|
||||
// Pass through to Besu
|
||||
return this.handlePassThrough(request);
|
||||
}
|
||||
|
||||
private async handleInterceptedMethod(request: JsonRpcRequest): Promise<JsonRpcResponse> {
|
||||
if (request.method === 'eth_sendTransaction') {
|
||||
if (!request.params || !Array.isArray(request.params) || request.params.length === 0) {
|
||||
return {
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32602,
|
||||
message: 'Invalid params',
|
||||
data: 'eth_sendTransaction requires a transaction object parameter',
|
||||
},
|
||||
id: request.id,
|
||||
};
|
||||
}
|
||||
|
||||
const txObject = request.params[0];
|
||||
const fromAddress = txObject.from;
|
||||
|
||||
// Smart interception: Check if address has key in Web3Signer
|
||||
// If no key, pass through to Besu (user wallet like MetaMask)
|
||||
// If key exists, intercept and sign via Web3Signer (service wallet)
|
||||
if (this.web3SignerClient && fromAddress) {
|
||||
try {
|
||||
const hasKey = await this.web3SignerClient.hasKey(fromAddress);
|
||||
|
||||
if (!hasKey) {
|
||||
// User wallet (MetaMask, etc.) - no key in Web3Signer
|
||||
// Pass through to Besu - user will sign locally
|
||||
// Note: Besu doesn't support unsigned eth_sendTransaction,
|
||||
// but this allows the proper error to come from Besu
|
||||
console.log(`Address ${fromAddress} has no key in Web3Signer, passing through to Besu`);
|
||||
return this.handlePassThrough(request);
|
||||
}
|
||||
|
||||
// Service wallet - has key in Web3Signer, intercept and sign
|
||||
console.log(`Address ${fromAddress} has key in Web3Signer, intercepting and signing`);
|
||||
} catch (error: any) {
|
||||
// If key check fails, log warning but still try to intercept
|
||||
// (fallback to original behavior)
|
||||
console.warn(`Failed to check if address has key: ${error.message}, attempting interception`);
|
||||
}
|
||||
} else if (!this.web3SignerClient) {
|
||||
// No Web3Signer client configured, pass through to Besu
|
||||
console.log('Web3Signer not configured, passing through to Besu');
|
||||
return this.handlePassThrough(request);
|
||||
}
|
||||
|
||||
// Intercept and sign via Web3Signer (service wallet)
|
||||
try {
|
||||
const txHash = await this.txInterceptor.interceptAndSign(txObject);
|
||||
return {
|
||||
jsonrpc: '2.0',
|
||||
result: txHash,
|
||||
id: request.id,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32000,
|
||||
message: 'Server error',
|
||||
data: error.message || 'Transaction processing failed',
|
||||
},
|
||||
id: request.id,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Should not reach here
|
||||
return {
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32601,
|
||||
message: 'Method not found',
|
||||
},
|
||||
id: request.id,
|
||||
};
|
||||
}
|
||||
|
||||
private async handlePassThrough(request: JsonRpcRequest): Promise<JsonRpcResponse> {
|
||||
try {
|
||||
const params = request.params || [];
|
||||
const result = await this.besuClient.callRpc(request.method, params);
|
||||
|
||||
return {
|
||||
jsonrpc: '2.0',
|
||||
result,
|
||||
id: request.id,
|
||||
};
|
||||
} catch (error: any) {
|
||||
// Map common errors to JSON-RPC error codes
|
||||
let errorCode = -32000; // Server error
|
||||
let errorMessage = error.message || 'Internal error';
|
||||
|
||||
if (error.message?.includes('Method not found')) {
|
||||
errorCode = -32601;
|
||||
errorMessage = 'Method not found';
|
||||
} else if (error.message?.includes('Invalid params')) {
|
||||
errorCode = -32602;
|
||||
errorMessage = 'Invalid params';
|
||||
}
|
||||
|
||||
return {
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: errorCode,
|
||||
message: errorMessage,
|
||||
data: error.message,
|
||||
},
|
||||
id: request.id,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async handleBatch(requests: JsonRpcRequest[]): Promise<JsonRpcResponse[]> {
|
||||
const promises = requests.map(req => this.handleRequest(req));
|
||||
return Promise.all(promises);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user