Skip to content

Commit

Permalink
Debug RPC methods (#548)
Browse files Browse the repository at this point in the history
* feat: debug_traceTransaction with tests

* feat: debug_traceCall with tests and rename test of debug_traceTransacion

* fix: removed destroy not needed

* fix: increrased test time

* fix: remove not used variable
  • Loading branch information
rodolfopietro97 authored Feb 13, 2024
1 parent f5bf73c commit b38d7e0
Show file tree
Hide file tree
Showing 10 changed files with 594 additions and 122 deletions.
16 changes: 14 additions & 2 deletions packages/provider/src/providers/vechain-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class VechainProvider extends EventEmitter implements EIP1193ProviderMessage {

/**
* Poll instance for subscriptions
*
* @private
*/
private pollInstance?: EventPoll<SubscriptionEvent[]>;
Expand All @@ -51,7 +52,7 @@ class VechainProvider extends EventEmitter implements EIP1193ProviderMessage {

/**
* Destroys the provider by closing the thorClient and stopping the provider poll instance if present.
* This is due to the fact that thorClient and the provider might be initialized with a polling interval.
* This is because thorClient and the provider might be initialized with a polling interval.
*/
public destroy(): void {
this.thorClient.destroy();
Expand All @@ -60,6 +61,12 @@ class VechainProvider extends EventEmitter implements EIP1193ProviderMessage {
}
}

/**
* This method is used to send a request to the provider.
* Basically, it is a wrapper around the RPCMethodsMap.
*
* @param args - Method and parameters to be used for the request.
*/
public async request(args: EIP1193RequestArguments): Promise<unknown> {
// Check if the method is supported
assert(
Expand Down Expand Up @@ -176,8 +183,13 @@ class VechainProvider extends EventEmitter implements EIP1193ProviderMessage {
);
}

/**
* Fetches the current block details from the vechain node.
*
* @private
*/
private async getCurrentBlock(): Promise<BlockDetail | null> {
// Initialize result to null, indicating no block found initially
// Initialize the result to null, indicating no block found initially
let result: BlockDetail | null = null;

// Proceed only if there are active log subscriptions or a new heads subscription is present
Expand Down
6 changes: 3 additions & 3 deletions packages/provider/src/utils/const/rpc-mapper/rpc-methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ enum RPC_METHODS {
eth_getTransactionByHash = 'eth_getTransactionByHash',
eth_getTransactionCount = 'eth_getTransactionCount',
eth_getTransactionReceipt = 'eth_getTransactionReceipt',
eth_sendTransaction = 'eth_sendTransaction',
eth_sendTransaction = 'eth_sendTransaction', // TEMPORARY COMMENT - TO IMPLEMENT
eth_syncing = 'eth_syncing',
net_version = 'net_version',
web3_clientVersion = 'web3_clientVersion',
eth_subscribe = 'eth_subscribe',
eth_unsubscribe = 'eth_unsubscribe',
debug_traceTransaction = 'debug_traceTransaction', // TEMPORARY COMMENT - TO IMPLEMENT
debug_traceCall = 'debug_traceCall', // TEMPORARY COMMENT - TO IMPLEMENT
debug_traceTransaction = 'debug_traceTransaction',
debug_traceCall = 'debug_traceCall',
evm_mine = 'evm_mine',

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,116 @@
import { type ThorClient } from '@vechain/vechain-sdk-network';
import { buildError, FUNCTION } from '@vechain/vechain-sdk-errors';
import {
assert,
buildProviderError,
DATA,
JSONRPC
} from '@vechain/vechain-sdk-errors';
import type {
TraceReturnType,
TracerName
} from '@vechain/vechain-sdk-network/src/thor-client/debug';

/**
* Type for trace options
*/
interface TraceCallRPC {
tracer: 'callTracer' | 'prestateTracer';
tracerConfig?: { onlyTopCall?: boolean };
}

/**
* Transaction object input type
*/
interface TransactionObjectInput {
from?: string;
to: string;
gas?: string;
gasPrice?: string;
value?: string;
data?: string;
}

/**
* RPC Method debug_traceCall implementation
*
* @param thorClient - The thor client instance to use.
* @param params - The standard array of rpc call parameters.
* @note:
* * params[0]: ...
* * params[1]: ...
* * params[n]: ...
* * params[0]: transaction - object - This describes the transaction info with following properties:
* * from - 20 bytes - Address the transaction is sent from.
* * to - 20 bytes [Required] - Address the transaction is directed to.
* * gas - Hexadecimal value of the gas provided for the transaction execution as hex string.
* * gasPrice - Hexadecimal value of the gasPrice used for each paid gas.
* * value - Hexadecimal of the value sent with this transaction.
* * data - Hash of the method signature and encoded parameters.
* * params[1]: blockNumber - string - The block number parameter. An hexadecimal number or (latest, earliest or pending). (NOT SUPPORTED YET)
* * params[2]: options - object - This describes the options for the trace. It has the following parameters:
* * tracer - string to specify the type of tracer. Currently, it supports callTracer and prestateTracer.
* * tracerConfig - Object to specify configurations for the tracer. It has the following parameters:
* * onlyTopCall - boolean Setting this to true will only trace the main (top-level) call and none of the sub-calls. This avoids extra processing for each call frame if only the top-level call info are required (useful for getting revertReason).
*/
const debugTraceCall = async (
thorClient: ThorClient,
params: unknown[]
): Promise<void> => {
// To avoid eslint error
await Promise.resolve(0);

// Not implemented yet
throw buildError(
FUNCTION.NOT_IMPLEMENTED,
'Method "debug_traceCall" not not implemented yet',
{
params,
thorClient
}
): Promise<TraceReturnType<'call'> | TraceReturnType<'prestate'>> => {
assert(
params.length === 3 &&
typeof params[0] === 'object' &&
typeof params[1] === 'string' &&
typeof params[2] === 'object',
DATA.INVALID_DATA_TYPE,
`Invalid params length, expected:\n* One transaction object containing transaction info with following properties: \n {` +
`\tfrom: 20 bytes - Address the transaction is sent from.` +
`\tto: 20 bytes [Required] - Address the transaction is directed to.` +
`\tgas: Hexadecimal value of the gas provided for the transaction execution as hex string.` +
`\tgasPrice: Hexadecimal value of the gasPrice used for each paid gas.` +
`\tvalue: Hexadecimal of the value sent with this transaction.` +
`\tdata: Hash of the method signature and encoded parameters` +
`}.\n\n* the block number parameter. An hexadecimal number or (latest, earliest or pending).` +
`\n\nAnd lastly, one object containing the options for trace: \n {` +
`\ttracer - string to specify the type of tracer. Currently, it supports callTracer and prestateTracer.` +
`\ttracerConfig - Object to specify configurations for the tracer. It has the following parameters:` +
`\tonlyTopCall - boolean Setting this to true will only trace the main (top-level) call and none of the sub-calls. This avoids extra processing for each call frame if only the top-level call info are required (useful for getting revertReason)`
);

// Init params
const transactionOptions = params[0] as TransactionObjectInput;
const tracerOptions = params[2] as TraceCallRPC;

// Tracer to use
const tracerToUse: TracerName =
tracerOptions.tracer === 'callTracer' ? 'call' : 'prestate';

try {
return (await thorClient.debug.traceContractCall(
{
transactionOptions: {
caller: transactionOptions.from,
gas:
transactionOptions.gas !== undefined
? parseInt(transactionOptions.gas, 16)
: undefined,
gasPrice: transactionOptions.gasPrice
},
contractInput: {
to: transactionOptions.to,
data: transactionOptions.data
},
config: tracerOptions.tracerConfig
},
tracerToUse
)) as TraceReturnType<'call'> | TraceReturnType<'prestate'>;
} catch (e) {
throw buildProviderError(
JSONRPC.INTERNAL_ERROR,
`Method 'debug_traceCall' failed: Error while debug tracer call\n
Params: ${JSON.stringify(params)}\n
URL: ${thorClient.httpClient.baseURL}`,
{
params,
innerError: JSON.stringify(e)
}
);
}
};

export { debugTraceCall };
Original file line number Diff line number Diff line change
@@ -1,32 +1,98 @@
import { type ThorClient } from '@vechain/vechain-sdk-network';
import { buildError, FUNCTION } from '@vechain/vechain-sdk-errors';
import {
assert,
buildProviderError,
DATA,
JSONRPC
} from '@vechain/vechain-sdk-errors';
import { assertValidTransactionID } from '@vechain/vechain-sdk-core';
import type {
TraceReturnType,
TracerName
} from '@vechain/vechain-sdk-network/src/thor-client/debug';
import { ethGetTransactionReceipt } from './eth_getTransactionReceipt';

/**
* Type for trace options
*/
interface TraceOptionsRPC {
tracer: 'callTracer' | 'prestateTracer';
tracerConfig?: { onlyTopCall?: boolean };
// Not supported yet
timeout?: string;
}

/**
* RPC Method debug_traceTransaction implementation
*
* @param thorClient - The thor client instance to use.
* @param params - The standard array of rpc call parameters.
* @note:
* * params[0]: ...
* * params[1]: ...
* * params[n]: ...
* * params[0]: transactionHash - hex string - This describes the transaction hash of the transaction that needs to be traced.
* * params[1]: options - object - This describes the options for the trace. It has the following parameters:
* * tracer - string to specify the type of tracer. Currently, it supports callTracer and prestateTracer.
* * timeout - string - A duration string of decimal numbers that overrides the default timeout of 5 seconds for JavaScript-based tracing calls.
* Max timeout is "10s". Valid time units are "ns", "us", "ms", "s" each with an optional fraction, such as "300ms" or "2s45ms"
* * tracerConfig - Object to specify configurations for the tracer. It has the following parameter:
* * onlyTopCall - boolean Setting this to true will only trace the main (top-level) call and none of the sub-calls.
* This avoids extra processing for each call frame if only the top-level call info are required (useful for getting revertReason).
*
* @see https://docs.alchemy.com/reference/debug-tracetransaction
*/
const debugTraceTransaction = async (
thorClient: ThorClient,
params: unknown[]
): Promise<void> => {
// To avoid eslint error
await Promise.resolve(0);

// Not implemented yet
throw buildError(
FUNCTION.NOT_IMPLEMENTED,
'Method "debug_traceTransaction" not not implemented yet',
{
params,
thorClient
}
): Promise<TraceReturnType<'call'> | TraceReturnType<'prestate'>> => {
// Check input params
assert(
params.length === 2 &&
typeof params[0] === 'string' &&
typeof params[1] === 'object',
DATA.INVALID_DATA_TYPE,
`Invalid params length, expected the transactionHash and 1 object containing the options for trace: \n {` +
`\ttracer - string to specify the type of tracer. Currently, it supports callTracer and prestateTracer.` +
`\ttimeout - string - A duration string of decimal numbers that overrides the default timeout of 5 seconds for JavaScript-based tracing calls. Max timeout is "10s". Valid time units are "ns", "us", "ms", "s" each with an optional fraction, such as "300ms" or "2s45ms"` +
`\ttracerConfig - Object to specify configurations for the tracer. It has the following parameters:` +
`\tonlyTopCall - boolean Setting this to true will only trace the main (top-level) call and none of the sub-calls. This avoids extra processing for each call frame if only the top-level call info are required (useful for getting revertReason)`
);

// Assert valid transaction id
assertValidTransactionID(params[0] as string);

// Init params
const [transactionId, traceOptions] = params as [string, TraceOptionsRPC];

// Tracer to use
const tracerToUse: TracerName =
traceOptions.tracer === 'callTracer' ? 'call' : 'prestate';

try {
const transactionReceipt = await ethGetTransactionReceipt(thorClient, [
transactionId
]);

return (await thorClient.debug.traceTransactionClause(
{
target: {
blockID: transactionReceipt?.blockHash as string,
transaction: transactionReceipt?.transactionHash as string,
clauseIndex: 0
},
config: {}
},
tracerToUse
)) as TraceReturnType<'call'> | TraceReturnType<'prestate'>;
} catch (e) {
throw buildProviderError(
JSONRPC.INTERNAL_ERROR,
`Method 'debug_traceTransaction' failed: Error while debug transaction tracer\n
Params: ${JSON.stringify(params)}\n
URL: ${thorClient.httpClient.baseURL}`,
{
params,
innerError: JSON.stringify(e)
}
);
}
};

export { debugTraceTransaction };
13 changes: 9 additions & 4 deletions packages/provider/src/utils/rpc-mapper/rpc-mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ import { type Wallet } from '@vechain/vechain-sdk-wallet';
import { ethRequestAccounts } from './methods-map/methods/eth_requestAccounts';
import { type LogsRPC } from '../formatter/logs';
import { type VechainProvider } from '../../providers';
import { type TraceReturnType } from '@vechain/vechain-sdk-network/src/thor-client/debug';

/**
* Map of RPC methods to their implementations with our SDK.
Expand Down Expand Up @@ -211,12 +212,16 @@ const RPCMethodsMap = (
return await ethUnsubscribe(params, provider);
},

[RPC_METHODS.debug_traceTransaction]: async (params) => {
await debugTraceTransaction(thorClient, params);
[RPC_METHODS.debug_traceTransaction]: async (
params
): Promise<TraceReturnType<'call'> | TraceReturnType<'prestate'>> => {
return await debugTraceTransaction(thorClient, params);
},

[RPC_METHODS.debug_traceCall]: async (params) => {
await debugTraceCall(thorClient, params);
[RPC_METHODS.debug_traceCall]: async (
params
): Promise<TraceReturnType<'call'> | TraceReturnType<'prestate'>> => {
return await debugTraceCall(thorClient, params);
},

[RPC_METHODS.evm_mine]: async (): Promise<BlocksRPC | null> => {
Expand Down
Loading

1 comment on commit b38d7e0

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test Coverage

Summary

Lines Statements Branches Functions
Coverage: 100%
100% (2488/2488) 100% (510/510) 100% (525/525)
Title Tests Skipped Failures Errors Time
core 409 0 💤 0 ❌ 0 🔥 1m 8s ⏱️
network 234 0 💤 0 ❌ 0 🔥 2m 31s ⏱️
errors 43 0 💤 0 ❌ 0 🔥 9.548s ⏱️

Please sign in to comment.