Skip to content

Commit

Permalink
534 add debug endpoints in thor client (#545)
Browse files Browse the repository at this point in the history
* feat: types for traceTransactionClause function

* feat: start debug with traceTransactionClause function

* feat: start debug with traceTransactionClause function tests

* feat: test for traceTransactionClause function and improve input type

* feat: improve types to support /debug/tracers/call endpoint

* feat: add other 2 debu endpoints

* feat: implement traceContractCall

* feat: traceContractCall test and some modifications on types

* feat: retrieveStorageRange with tests
  • Loading branch information
rodolfopietro97 authored Feb 9, 2024
1 parent e1d488d commit 1144487
Show file tree
Hide file tree
Showing 18 changed files with 1,338 additions and 1 deletion.
220 changes: 220 additions & 0 deletions packages/network/src/thor-client/debug/debug-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import { type ThorClient } from '../thor-client';
import {
type ContractCallTraceContractTargetInput,
type ContractCallTraceTransactionOptionsInput,
type RetrieveStorageRangeInputOptions,
type RetrieveStorageRangeReturnType,
type TracerConfig,
type TraceReturnType,
type TracerName,
type TransactionTraceTarget
} from './types';
import { thorest } from '../../utils';
import { assert, DATA } from '@vechain/vechain-sdk-errors';
import { addressUtils, dataUtils } from '@vechain/vechain-sdk-core';

/** The `DebugModule` class encapsulates functionality to handle Debug
* on the VechainThor blockchain.
*/
class DebugModule {
/**
* Initializes a new instance of the `Thor` class.
* @param thor - The Thor instance used to interact with the vechain blockchain API.
*/
constructor(readonly thor: ThorClient) {}

/**
* Trace transaction clause.
*
* This endpoint allows you to create a tracer for a specific clause.
* Tracers are instrumental in monitoring and analyzing the execution flow within the EVM.
* You can customize the tracer using various options to tailor it to your specific debugging needs.
*
* @param input - The input for the trace transaction clause. It has:
* * target - The target of the tracer. It is a combination of blockID, transaction (transaction ID or index into block), and clauseIndex.
* * config - The configuration of the tracer. It is specific to the name of the tracer.
* @param name - The name of the tracer to use. It determines Output and Input configuration.
*
* @throws{InvalidDataTypeError} - If the input is invalid.
*/
public async traceTransactionClause(
input: {
target: TransactionTraceTarget;
config?: TracerConfig<typeof name>;
},
name?: TracerName
): Promise<TraceReturnType<typeof name>> {
// Validate target. If invalid, assert
this.validateTarget(input.target, 'traceTransactionClause');

// Parse target
const parsedTarget = `${input.target.blockID}/${input.target.transaction}/${input.target.clauseIndex}`;

// Send request
return (await this.thor.httpClient.http(
'POST',
thorest.debug.post.TRACE_TRANSACTION_CLAUSE(),
{
query: {},
body: {
target: parsedTarget,
name,
config: input.config
},
headers: {}
}
)) as TraceReturnType<typeof name>;
}

/**
* Trace a contract call.
*
* This endpoint enables clients to create a tracer for a specific function call.
* You can customize the tracer using various options to suit your debugging requirements.
*
* @param input - The input for the trace contract call. It has:
* * contractInput - The contract call information.
* * config - The configuration of the tracer. It is specific to the name of the tracer.
* * transactionOptions - The transaction options.
* @param name - The name of the tracer to use. It determines Output and Input configuration.
*
* @throws{InvalidDataTypeError} - If the input is invalid.
*/
public async traceContractCall(
input: {
contractInput?: ContractCallTraceContractTargetInput;
transactionOptions?: ContractCallTraceTransactionOptionsInput;
config?: TracerConfig<typeof name>;
},
name?: TracerName
): Promise<TraceReturnType<typeof name>> {
// Validate contractInput
if (
input.contractInput?.to !== undefined &&
input.contractInput.to !== null
) {
assert(
addressUtils.isAddress(input.contractInput.to),
DATA.INVALID_DATA_TYPE,
`Invalid address '${input.contractInput.to}' given as input for traceContractCall.`,
{ address: input.contractInput.to }
);
}

if (input.contractInput?.data !== undefined)
assert(
dataUtils.isHexString(input.contractInput.data, true),
DATA.INVALID_DATA_TYPE,
`Invalid data '${input.contractInput?.data}' given as input for traceContractCall.`,
{ data: input.contractInput?.data }
);

if (input.contractInput?.value !== undefined)
assert(
dataUtils.isHexString(input.contractInput.value, true),
DATA.INVALID_DATA_TYPE,
`Invalid value '${input.contractInput?.value}' given as input for traceContractCall.`,
{ value: input.contractInput?.value }
);

// Send request
return (await this.thor.httpClient.http(
'POST',
thorest.debug.post.TRACE_CONTRACT_CALL(),
{
query: {},
body: {
to: input.contractInput?.to,
data: input.contractInput?.data,
value: input.contractInput?.value,
name,
gas: input.transactionOptions?.gas,
gasPrice: input.transactionOptions?.gasPrice,
caller: input.transactionOptions?.caller,
provedWork: input.transactionOptions?.provedWork,
gasPayer: input.transactionOptions?.gasPayer,
expiration: input.transactionOptions?.expiration,
blockRef: input.transactionOptions?.blockRef,
config: input.config
},
headers: {}
}
)) as TraceReturnType<typeof name>;
}

public async retrieveStorageRange(input: {
target: TransactionTraceTarget;
options?: RetrieveStorageRangeInputOptions;
}): Promise<RetrieveStorageRangeReturnType> {
// Validate target. If invalid, assert
this.validateTarget(input.target, 'retrieveStorageRange');

// Parse target
const parsedTarget = `${input.target.blockID}/${input.target.transaction}/${input.target.clauseIndex}`;

// Send request
return (await this.thor.httpClient.http(
'POST',
thorest.debug.post.RETRIEVE_STORAGE_RANGE(),
{
query: {},
body: {
target: parsedTarget,
address: input.options?.address,
keyStart: input.options?.keyStart,
maxResult: input.options?.maxResult
},
headers: {}
}
)) as RetrieveStorageRangeReturnType;
}

/**
* Validate target of traceTransactionClause and retrieveStorageRange.
*
* @param target - Target of traceTransactionClause and retrieveStorageRange to validate.
* @param functionName - The name of the function.
*
* @private
*
* @throws{InvalidDataTypeError} - If the input is invalid.
*/
private validateTarget(
target: TransactionTraceTarget,
functionName: string
): void {
// Validate target - blockID
assert(
dataUtils.isThorId(target.blockID, true),
DATA.INVALID_DATA_TYPE,
`Invalid block ID '${target.blockID}' given as input for ${functionName}.`,
{ blockId: target.blockID }
);

// Validate target - transaction
if (typeof target.transaction === 'string')
assert(
dataUtils.isThorId(target.transaction, true),
DATA.INVALID_DATA_TYPE,
`Invalid transaction id '${target.transaction}' given as input for ${functionName}.`,
{ transaction: target.transaction }
);
else
assert(
target.transaction >= 0,
DATA.INVALID_DATA_TYPE,
`Invalid transaction index '${target.transaction}' given as input for ${functionName}.`,
{ transaction: target.transaction }
);

// Validate target - clauseIndex
assert(
target.clauseIndex >= 0,
DATA.INVALID_DATA_TYPE,
`Invalid clause index '${target.clauseIndex}' given as input for ${functionName}.`,
{ clauseIndex: target.clauseIndex }
);
}
}

export { DebugModule };
2 changes: 2 additions & 0 deletions packages/network/src/thor-client/debug/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './debug-module';
export * from './types.d';
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Config for the '4byte' name type
*/
type FourByteNameConfig = Record<string, unknown>;

/**
* Return type for the '4byte' name type
*/
type FourByteNameReturnType = Record<string, number>;

export { type FourByteNameConfig, type FourByteNameReturnType };
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Config for the 'bigram' name type
*/
type BigramNameConfig = Record<string, unknown>;

/**
* Return type for the 'bigram' name type
*/
type BigramNameReturnType = Record<string, number>;

export { type BigramNameConfig, type BigramNameReturnType };
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Config for the 'call' name type
*/
type CallNameConfig = Record<string, unknown>;

/**
* Return type for the 'call' name type
*/
interface CallNameReturnType {
from: string;
gas: string;
gasUsed: string;
to: string;
input: string;
output: string;
// Trace clause type (/debug/tracers endpoint)
calls?: Array<{
from: string;
gas: string;
gasUsed: string;
to: string;
input: string;
output: string;
type: string;
}>;
// Trace contract type (/debug/tracers/call endpoint)
value?: string;
type?: string;
}

export { type CallNameConfig, type CallNameReturnType };
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Config for the default ('' or null) name type
*/
type DefaultNameConfig = Record<string, unknown>;

/**
* Return type for the default ('' or null) name type
*/
interface DefaultNameReturnType {
gas: number;
failed: boolean;
returnValue: string;
structLogs: Array<{
pc: number;
op: string;
gas: number;
gasCost: number;
depth: number;
stack: string[];
}>;
}

export { type DefaultNameConfig, type DefaultNameReturnType };
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* Config for the 'evmdis' name type
*/
type EVMDisNameConfig = Record<string, unknown>;

/**
* Return type for the 'evmdis' name type
*/
type EVMDisNameReturnType = Array<{
op: number;
depth: number;
result: string[];
len: number;
}>;

export { type EVMDisNameConfig, type EVMDisNameReturnType };
10 changes: 10 additions & 0 deletions packages/network/src/thor-client/debug/types-by-name/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export * from './4byte-name-types.d';
export * from './bigram-name-types.d';
export * from './call-name-types.d';
export * from './default-name-types.d';
export * from './evmdis-name-types.d';
export * from './noop-name-types.d';
export * from './opcount-name-types.d';
export * from './prestate-name-types.d';
export * from './trigram-name-types.d';
export * from './unigram-name-types.d';
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Config for the 'noop' name type
*/
type NoopNameConfig = Record<string, unknown>;

/**
* Return type for the 'noop' name type
*/
type NoopNameReturnType = Record<string, unknown>;

export { type NoopNameConfig, type NoopNameReturnType };
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Config for the 'opcount' name type
*/
type OPCountNameConfig = Record<string, unknown>;

/**
* Return type for the 'opcount' name type
*/
type OPCountNameReturnType = number;

export { type OPCountNameConfig, type OPCountNameReturnType };
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Config for the 'prestate' name type
*/
type PreStateNameConfig = Record<string, unknown>;

/**
* Return type for the 'prestate' name type
*/
type PreStateNameReturnType = Record<
string,
{
balance: string;
energy: string;
code?: string;
storage?: Record<string, string>;
}
>;

export { type PreStateNameConfig, type PreStateNameReturnType };
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Config for the 'trigram' name type
*/
type TrigramNameConfig = Record<string, unknown>;

/**
* Return type for the 'trigram' name type
*/
type TrigramNameReturnType = Record<string, number>;

export { type TrigramNameConfig, type TrigramNameReturnType };
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Config for the 'unigram' name type
*/
type UnigramNameConfig = Record<string, unknown>;

/**
* Return type for the 'unigram' name type
*/
type UnigramNameReturnType = Record<string, number>;

export { type UnigramNameConfig, type UnigramNameReturnType };
Loading

1 comment on commit 1144487

@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% (2410/2410) 100% (468/468) 100% (514/514)
Title Tests Skipped Failures Errors Time
core 406 0 💤 0 ❌ 0 🔥 1m 9s ⏱️
network 233 0 💤 0 ❌ 0 🔥 2m 51s ⏱️
errors 43 0 💤 0 ❌ 0 🔥 9.179s ⏱️

Please sign in to comment.