Skip to content

Commit

Permalink
Merge pull request #31 from vechain/debug_namespace
Browse files Browse the repository at this point in the history
implement debug_traceTransaction and debug_traceCall
  • Loading branch information
zzGHzz authored Sep 6, 2023
2 parents 4d8cbf5 + 8768b1f commit 869dcad
Show file tree
Hide file tree
Showing 9 changed files with 441 additions and 8 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ const provider = thor.ethers.modifyProvider(
)
)
```
Obtaining a singner
Obtaining a signer
```ts
const signer = provider.getSigner(address)
```
Expand Down Expand Up @@ -179,10 +179,12 @@ Supported subscription type: `newHeads`, `logs`
Equivalent to `eth_chainId`
##### `web3_clientVersion`
Returning string `thor`
##### `debug_traceTransaction`
##### `debug_traceCall`

## Implementation Notes
1. Fields `blockHash` and `transactionHash` return the values of [`blockId`](https://docs.vechain.org/thor/learn/block.html#block) and [`transactionId`](https://docs.vechain.org/thor/learn/transaction-model.html#transaction-id) defined in the Thor protocol, respectively
2. APIs `eth_estimateGas`, `eth_call` and `eth_getTransactionReceipt` only return information associated with the first [clause](https://docs.vechain.org/thor/learn/transaction-model.html#clauses) in a transaction
2. APIs `eth_estimateGas`, `eth_call`, `eth_getTransactionReceipt`, `debug_traceTransaction` and `debug_traceCall` only return information associated with the first [clause](https://docs.vechain.org/thor/learn/transaction-model.html#clauses) in a transaction
3. Unsupported returning fields (all set to zero):
* `cumulativeGasUsed`
* `difficulty`
Expand All @@ -195,6 +197,6 @@ Returning string `thor`
## License
This software is licensed under the
[GNU Lesser General Public License v3.0](https://www.gnu.org/licenses/lgpl-3.0.html), also included
in *LICENSE##### file in repository.
in *LICENSE* file in repository.
## References
[1] [https://eth.wiki/json-rpc/API](https://eth.wiki/json-rpc/API).
3 changes: 3 additions & 0 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,8 @@ export const EthJsonRpcMethods = [
'eth_subscribe',
'eth_unsubscribe',

'debug_traceTransaction',
'debug_traceCall',

'evm_mine'
]
28 changes: 28 additions & 0 deletions src/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export class Formatter {
this._inputFormatters['eth_getLogs'] = this._getLogs;
this._inputFormatters['eth_subscribe'] = this._subscribe;
this._inputFormatters['eth_sendRawTransaction'] = this._sendRawTransaction;
this._inputFormatters['debug_traceCall'] = this._traceCall;
}

formatInput = (method: string, params?: any[]): any[] => {
Expand Down Expand Up @@ -155,6 +156,7 @@ export class Formatter {
data: data,
}],
gas: o1.gas ? hexToNumber(o1.gas) : undefined,
gasPrice: o1.gasPrice,
caller: o1.from,
}

Expand Down Expand Up @@ -233,6 +235,32 @@ export class Formatter {
return [out];
}

private _traceCall = (params: any[]) => {
// trace call needs net, bypass if net not set
if (!this._ifSetNet) {
return params;
}

let [callObj, revision = 'latest', opt] = params;

revision = parseBlockNumber(revision);
if (revision === null) {
const msg = ErrMsg.ArgumentMissingOrInvalid('debug_traceCall', 'revision');
throw new ProviderRpcError(ErrCode.InvalidParams, msg);
}

const arg = {
to: callObj.to || null,
value: callObj.value || '0x0',
data: callObj.data || '0x',
gas: callObj.gas ? hexToNumber(callObj.gas) : undefined,
gasPrice: callObj.gasPrice,
caller: callObj.from,
};

return [arg, revision, opt];
}

private _subscribe = (params: any[]) => {
const name: string = params[0];
switch (name) {
Expand Down
75 changes: 75 additions & 0 deletions src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ export class Provider extends EventEmitter implements IProvider {
if (opt.net) {
this.restful = new Restful(opt.net, this.connex.thor.genesis.id);
this._methodMap['eth_sendRawTransaction'] = this._sendRawTransaction;
this._methodMap['debug_traceTransaction'] = this._traceTransaction;
this._methodMap['debug_traceCall'] = this._traceCall;
}

if (opt.wallet) {
Expand Down Expand Up @@ -591,4 +593,77 @@ export class Provider extends EventEmitter implements IProvider {
return Promise.reject(new ProviderRpcError(ErrCode.InternalError, getErrMsg(err)));
}
}

private _traceTransaction = async (params: any[]) => {
/**
* debug_traceTransaction(txHash, traceOptions)
* traceOptions: {
* tracer: '', // name of tracer or custom js tracer code
* config: {} // struct logger config object
* tracerConfig: {} // tracer specific config object
* }
*/
try {
const txId = params[0]
const opts = params[1]
const tx = await this.connex.thor.transaction(txId).get();
if (!tx) { return Promise.reject(new ProviderRpcError(ErrCode.Default, 'Target not found')); }

const blk = (await this.connex.thor.block(tx.meta.blockID).get())!;
const txIndex = blk.transactions.findIndex(elem => elem == txId);


if (opts && opts.tracer) {
return this.restful!.traceClause({
target: `${tx.meta.blockID}/${txIndex}/0`,
name: opts?.tracer,
config: opts?.tracerConfig,
})
} else {
// if tracerConfig.name not specified, it's struct logger
// struct logger config is located at tracerConfig.config
return this.restful!.traceClause({
target: `${tx.meta.blockID}/${txIndex}/0`,
name: opts?.tracer,
config: opts?.config,
})
}
} catch (err: any) {
return Promise.reject(new ProviderRpcError(ErrCode.InternalError, getErrMsg(err)));
}
}

private _traceCall = async (params: any[]) => {
/**
* debug_traceCall(callArgs, blockHashOrNumber ,tracerOptions)
* tracerOptions: {
* tracer: '', // name of tracer or custom js tracer code
* config: {} // struct logger config object
* tracerConfig: {} // tracer specific config object
* }
*/
try {
const callArgs = params[0]
const revision = params[1]
const opts = params[2]

if (opts && opts.tracer) {
return this.restful!.traceCall({
... callArgs,
name: opts?.tracer,
config: opts?.tracerConfig,
}, revision)
} else {
// if tracerConfig.name not specified, it's struct logger
// struct logger config is located at tracerConfig.config
return this.restful!.traceCall({
... callArgs,
name: opts.tracer,
config: opts.config,
}, revision)
}
} catch (err: any) {
return Promise.reject(new ProviderRpcError(ErrCode.InternalError, getErrMsg(err)));
}
}
}
45 changes: 44 additions & 1 deletion src/restful.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';

import { ErrCode } from './error';
import { Net, ExplainArg } from './types';
import { Net, ExplainArg, TraceClauseOption, TraceCallOption } from './types';
import { decodeRevertReason, getErrMsg } from './utils';
import { ProviderRpcError } from './eip1193';

Expand Down Expand Up @@ -136,4 +136,47 @@ export class Restful {
return Promise.reject(new ProviderRpcError(ErrCode.InternalError, getErrMsg(err)));
}
}

traceClause = async (opts: TraceClauseOption) =>{
try {
const httpParams: Net.Params = {
body: opts,
validateResponseHeader: this._headerValidator
}

const ret: object = await this._net.http(
"POST",
'debug/tracers',
httpParams
);


return ret;
} catch (err: any) {
return Promise.reject(new ProviderRpcError(ErrCode.InternalError, getErrMsg(err)));
}
}

traceCall = async (opts: TraceCallOption, revision?: string) => {
try {
const httpParams: Net.Params = {
body: opts,
validateResponseHeader: this._headerValidator
}
if (revision) {
httpParams.query = { "revision": revision };
}

const ret: object = await this._net.http(
"POST",
'debug/tracers/call',
httpParams
);


return ret;
} catch (err: any) {
return Promise.reject(new ProviderRpcError(ErrCode.InternalError, getErrMsg(err)));
}
}
}
20 changes: 19 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ export type TxObj = {
value?: string;
data?: string;
gas?: string;

gasPrice?: string;
input?: string; // Added to support requests from web3.js
}

Expand All @@ -200,4 +200,22 @@ export type ConvertedFilterOpts = {
export type DelegateOpt = {
url: string;
signer?: string;
}

export interface TracerOption {
name: string;
config: object;
}

export interface TraceClauseOption extends TracerOption {
target: string;
}

export interface TraceCallOption extends TracerOption{
to: string | null
value: string
data: string
caller?: string;
gas?: number;
gasPrice?: string;
}
3 changes: 2 additions & 1 deletion test/docker-compose-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: "3"

services:
thor-solo:
image: vechain/thor:v2.0.0
image: vechain/thor:v2.1.0
container_name: thor-solo
# Install curl for our healthcheck, then start thor solo
entrypoint:
Expand All @@ -11,6 +11,7 @@ services:
"-c",
"apk update && apk upgrade && apk add curl && thor solo --on-demand --data-dir /data/thor --api-addr 0.0.0.0:8669 --api-cors '*' --api-backtrace-limit -1 --verbosity 4"
]
user: root
ports:
- "8669:8669"
- "11235:11235"
Expand Down
Loading

0 comments on commit 869dcad

Please sign in to comment.