Skip to content

Commit 0128f63

Browse files
committed
debug_traceBlockByNumber
1 parent 186e3bb commit 0128f63

File tree

10 files changed

+200
-117
lines changed

10 files changed

+200
-117
lines changed

action/protocol/execution/evm/context.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,26 @@ package evm
33
import (
44
"context"
55

6+
"github.com/iotexproject/iotex-core/v2/action"
67
"github.com/iotexproject/iotex-core/v2/action/protocol"
78
"github.com/iotexproject/iotex-core/v2/pkg/log"
89
)
910

1011
type (
1112
helperContextKey struct{}
1213

14+
tracerContextKey struct{}
15+
1316
// HelperContext is the context for EVM helper
1417
HelperContext struct {
1518
GetBlockHash GetBlockHash
1619
GetBlockTime GetBlockTime
1720
DepositGasFunc protocol.DepositGas
1821
}
22+
23+
TracerContext struct {
24+
CaptureTx func([]byte, *action.Receipt)
25+
}
1926
)
2027

2128
// WithHelperCtx returns a new context with helper context
@@ -31,3 +38,12 @@ func mustGetHelperCtx(ctx context.Context) HelperContext {
3138
}
3239
return hc
3340
}
41+
42+
func WithTracerCtx(ctx context.Context, tctx TracerContext) context.Context {
43+
return context.WithValue(ctx, tracerContextKey{}, tctx)
44+
}
45+
46+
func GetTracerCtx(ctx context.Context) (TracerContext, bool) {
47+
tc, ok := ctx.Value(tracerContextKey{}).(TracerContext)
48+
return tc, ok
49+
}

action/protocol/execution/evm/evm.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,9 @@ func ExecuteContract(
332332
receipt.SetExecutionRevertMsg(revertMsg)
333333
}
334334
log.S().Debugf("Receipt: %+v, %v", receipt, err)
335+
if tCtx, ok := GetTracerCtx(ctx); ok && tCtx.CaptureTx != nil {
336+
tCtx.CaptureTx(retval, receipt)
337+
}
335338
return retval, receipt, nil
336339
}
337340

api/coreservice.go

Lines changed: 80 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@ type (
190190
Track(ctx context.Context, start time.Time, method string, size int64, success bool)
191191
// BlobSidecarsByHeight returns blob sidecars by height
192192
BlobSidecarsByHeight(height uint64) ([]*apitypes.BlobSidecarResult, error)
193+
// TraceBlockByNumber returns the trace result of a block by its height
194+
TraceBlockByNumber(ctx context.Context, height uint64, config *tracers.TraceConfig) ([][]byte, []*action.Receipt, any, error)
193195

194196
// Historical methods
195197
BalanceAt(ctx context.Context, addr address.Address, height uint64) (string, error)
@@ -2139,6 +2141,14 @@ func (core *coreService) TraceCall(ctx context.Context,
21392141
})
21402142
}
21412143

2144+
func (core *coreService) TraceBlockByNumber(ctx context.Context, height uint64, config *tracers.TraceConfig) ([][]byte, []*action.Receipt, any, error) {
2145+
blk, err := core.dao.GetBlockByHeight(height)
2146+
if err != nil {
2147+
return nil, nil, nil, err
2148+
}
2149+
return core.traceBlock(ctx, blk, config)
2150+
}
2151+
21422152
// Track tracks the api call
21432153
func (core *coreService) Track(ctx context.Context, start time.Time, method string, size int64, success bool) {
21442154
if core.apiStats == nil {
@@ -2154,44 +2164,21 @@ func (core *coreService) Track(ctx context.Context, start time.Time, method stri
21542164
}
21552165

21562166
func (core *coreService) traceTx(ctx context.Context, txctx *tracers.Context, config *tracers.TraceConfig, simulateFn func(ctx context.Context) ([]byte, *action.Receipt, error)) ([]byte, *action.Receipt, any, error) {
2157-
var (
2158-
tracer vm.EVMLogger
2159-
err error
2160-
)
2161-
switch {
2162-
case config == nil:
2163-
tracer = logger.NewStructLogger(nil)
2164-
case config.Tracer != nil:
2165-
// Define a meaningful timeout of a single transaction trace
2166-
timeout := defaultTraceTimeout
2167-
if config.Timeout != nil {
2168-
if timeout, err = time.ParseDuration(*config.Timeout); err != nil {
2169-
return nil, nil, nil, err
2170-
}
2171-
}
2172-
t, err := tracers.DefaultDirectory.New(*config.Tracer, txctx, config.TracerConfig)
2173-
if err != nil {
2174-
return nil, nil, nil, err
2175-
}
2176-
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
2177-
defer cancel()
2178-
go func() {
2179-
<-deadlineCtx.Done()
2180-
if errors.Is(deadlineCtx.Err(), context.DeadlineExceeded) {
2181-
t.Stop(errors.New("execution timeout"))
2182-
}
2183-
}()
2184-
tracer = t
2167+
ctx, tracer, err := core.traceContext(ctx, txctx, config)
2168+
retval, receipt, err := simulateFn(ctx)
2169+
return retval, receipt, tracer, err
2170+
}
21852171

2186-
default:
2187-
tracer = logger.NewStructLogger(config.Config)
2172+
func (core *coreService) traceContext(ctx context.Context, txctx *tracers.Context, config *tracers.TraceConfig) (context.Context, *evmTracer, error) {
2173+
tracer := newEVMTracer(txctx, config)
2174+
if err := tracer.Reset(); err != nil {
2175+
return nil, nil, status.Error(codes.InvalidArgument, fmt.Sprintf("failed to reset tracer: %v", err))
21882176
}
21892177
ctx = protocol.WithVMConfigCtx(ctx, vm.Config{
21902178
Tracer: tracer,
21912179
NoBaseFee: true,
21922180
})
2193-
retval, receipt, err := simulateFn(ctx)
2194-
return retval, receipt, tracer, err
2181+
return ctx, tracer, nil
21952182
}
21962183

21972184
func (core *coreService) simulateExecution(
@@ -2262,6 +2249,67 @@ func (core *coreService) getStateManager(ctx context.Context, height uint64) (co
22622249
return ctx, ws, err
22632250
}
22642251

2252+
func (core *coreService) traceBlock(ctx context.Context, blk *block.Block, config *tracers.TraceConfig) ([][]byte, []*action.Receipt, any, error) {
2253+
ctx, err := core.bc.ContextAtHeight(ctx, blk.Height())
2254+
if err != nil {
2255+
return nil, nil, nil, err
2256+
}
2257+
g := core.bc.Genesis()
2258+
ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
2259+
BlockHeight: blk.Height(),
2260+
BlockTimeStamp: blk.Timestamp(),
2261+
GasLimit: g.BlockGasLimitByHeight(blk.Height()),
2262+
Producer: blk.PublicKey().Address(),
2263+
})
2264+
ctx = protocol.WithRegistry(ctx, core.registry)
2265+
ctx = protocol.WithFeatureCtx(ctx)
2266+
retvals := make([][]byte, 0)
2267+
receipts := make([]*action.Receipt, 0)
2268+
results := make([]*blockTraceResult, 0)
2269+
ctx, tracer, err := core.traceContext(ctx, new(tracers.Context), config)
2270+
if err != nil {
2271+
return nil, nil, nil, err
2272+
}
2273+
ctx = evm.WithTracerCtx(ctx, evm.TracerContext{
2274+
CaptureTx: func(retval []byte, receipt *action.Receipt) {
2275+
defer tracer.Reset()
2276+
var res any
2277+
switch innerTracer := tracer.EVMLogger.(type) {
2278+
case *logger.StructLogger:
2279+
res = &debugTraceTransactionResult{
2280+
Failed: receipt.Status != uint64(iotextypes.ReceiptStatus_Success),
2281+
Revert: receipt.ExecutionRevertMsg(),
2282+
ReturnValue: byteToHex(retval),
2283+
StructLogs: fromLoggerStructLogs(innerTracer.StructLogs()),
2284+
Gas: receipt.GasConsumed,
2285+
}
2286+
case tracers.Tracer:
2287+
res, err = innerTracer.GetResult()
2288+
if err != nil {
2289+
log.L().Error("failed to get tracer result", zap.Error(err))
2290+
return
2291+
}
2292+
default:
2293+
log.L().Error("unknown tracer type", zap.Any("tracer", innerTracer))
2294+
return
2295+
}
2296+
results = append(results, &blockTraceResult{
2297+
TxHash: receipt.ActionHash,
2298+
Result: res,
2299+
})
2300+
retvals = append(retvals, retval)
2301+
receipts = append(receipts, receipt)
2302+
},
2303+
})
2304+
ws, err := core.sf.WorkingSetAtHeight(ctx, blk.Height()-1, blk.Actions...)
2305+
if err != nil {
2306+
return nil, nil, nil, err
2307+
}
2308+
defer ws.Close()
2309+
2310+
return retvals, receipts, results, nil
2311+
}
2312+
22652313
func filterReceipts(receipts []*action.Receipt, actHash hash.Hash256) *action.Receipt {
22662314
for _, r := range receipts {
22672315
if r.ActionHash == actHash {

api/coreservice_test.go

Lines changed: 2 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,7 @@ func TestTraceTransaction(t *testing.T) {
623623
require.Equal(uint64(1), receipt.Status)
624624
require.Equal(uint64(0x2710), receipt.GasConsumed)
625625
require.Empty(receipt.ExecutionRevertMsg())
626-
require.Equal(0, len(traces.(*logger.StructLogger).StructLogs()))
626+
require.Equal(0, len(traces.(*evmTracer).EVMLogger.(*logger.StructLogger).StructLogs()))
627627
}
628628

629629
func TestTraceCall(t *testing.T) {
@@ -661,7 +661,7 @@ func TestTraceCall(t *testing.T) {
661661
require.Equal(uint64(1), receipt.Status)
662662
require.Equal(uint64(0x2710), receipt.GasConsumed)
663663
require.Empty(receipt.ExecutionRevertMsg())
664-
require.Equal(0, len(traces.(*logger.StructLogger).StructLogs()))
664+
require.Equal(0, len(traces.(*evmTracer).EVMLogger.(*logger.StructLogger).StructLogs()))
665665
}
666666

667667
func TestProofAndCompareReverseActions(t *testing.T) {
@@ -1085,83 +1085,3 @@ func TestTrack(t *testing.T) {
10851085
cs.Track(nil, time.Now(), "", 0, true)
10861086
})
10871087
}
1088-
1089-
func TestTraceTx(t *testing.T) {
1090-
require := require.New(t)
1091-
ctrl := gomock.NewController(t)
1092-
defer ctrl.Finish()
1093-
1094-
var (
1095-
bc = mock_blockchain.NewMockBlockchain(ctrl)
1096-
cs = &coreService{
1097-
bc: bc,
1098-
}
1099-
ctx = context.Background()
1100-
)
1101-
1102-
t.Run("ConfigIsNil", func(t *testing.T) {
1103-
p := NewPatches()
1104-
defer p.Reset()
1105-
1106-
p = p.ApplyFuncReturn(logger.NewStructLogger, nil)
1107-
p = p.ApplyFuncReturn(protocol.WithVMConfigCtx, ctx)
1108-
p = p.ApplyFuncReturn(protocol.WithBlockCtx, ctx)
1109-
p = p.ApplyFuncReturn(genesis.WithGenesisContext, ctx)
1110-
p = p.ApplyFuncReturn(protocol.WithBlockchainCtx, ctx)
1111-
p = p.ApplyFuncReturn(protocol.WithFeatureCtx, ctx)
1112-
retval, receipt, tracer, err := cs.traceTx(ctx, nil, nil, func(ctx context.Context) ([]byte, *action.Receipt, error) {
1113-
return nil, nil, nil
1114-
})
1115-
require.NoError(err)
1116-
require.Empty(retval)
1117-
require.Empty(receipt)
1118-
require.Empty(tracer)
1119-
})
1120-
1121-
t.Run("TracerIsNotNil", func(t *testing.T) {
1122-
1123-
t.Run("FailedToParseDuration", func(t *testing.T) {
1124-
p := NewPatches()
1125-
defer p.Reset()
1126-
1127-
p = p.ApplyFuncReturn(time.ParseDuration, nil, errors.New(t.Name()))
1128-
1129-
testStr := "TestTracer"
1130-
_, _, _, err := cs.traceTx(ctx, nil, &tracers.TraceConfig{Tracer: &testStr, Timeout: &testStr}, func(ctx context.Context) ([]byte, *action.Receipt, error) {
1131-
return nil, nil, nil
1132-
})
1133-
require.ErrorContains(err, t.Name())
1134-
})
1135-
1136-
t.Run("FailedToNewTracer", func(t *testing.T) {
1137-
p := NewPatches()
1138-
defer p.Reset()
1139-
1140-
p = p.ApplyMethodReturn(&tracers.DefaultDirectory, "New", nil, errors.New(t.Name()))
1141-
testStr := "TestTracer"
1142-
_, _, _, err := cs.traceTx(ctx, nil, &tracers.TraceConfig{Tracer: &testStr}, func(ctx context.Context) ([]byte, *action.Receipt, error) {
1143-
return nil, nil, nil
1144-
})
1145-
require.ErrorContains(err, t.Name())
1146-
})
1147-
})
1148-
1149-
t.Run("TracerIsNil", func(t *testing.T) {
1150-
p := NewPatches()
1151-
defer p.Reset()
1152-
1153-
p = p.ApplyFuncReturn(logger.NewStructLogger, nil)
1154-
p = p.ApplyFuncReturn(protocol.WithVMConfigCtx, ctx)
1155-
p = p.ApplyFuncReturn(protocol.WithBlockCtx, ctx)
1156-
p = p.ApplyFuncReturn(genesis.WithGenesisContext, ctx)
1157-
p = p.ApplyFuncReturn(protocol.WithBlockchainCtx, ctx)
1158-
p = p.ApplyFuncReturn(protocol.WithFeatureCtx, ctx)
1159-
retval, receipt, tracer, err := cs.traceTx(ctx, nil, &tracers.TraceConfig{}, func(ctx context.Context) ([]byte, *action.Receipt, error) {
1160-
return nil, nil, nil
1161-
})
1162-
require.NoError(err)
1163-
require.Empty(retval)
1164-
require.Empty(receipt)
1165-
require.Empty(tracer)
1166-
})
1167-
}

api/grpcserver.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -696,7 +696,7 @@ func (svr *gRPCHandler) TraceTransactionStructLogs(ctx context.Context, in *iote
696696
}
697697
structLogs := make([]*iotextypes.TransactionStructLog, 0)
698698
//grpc not support javascript tracing, so we only return native traces
699-
traces := tracer.(*logger.StructLogger)
699+
traces := tracer.(*evmTracer).EVMLogger.(*logger.StructLogger)
700700
for _, log := range traces.StructLogs() {
701701
var stack []string
702702
for _, s := range log.Stack {

api/grpcserver_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1025,7 +1025,7 @@ func TestGrpcServer_TraceTransactionStructLogs(t *testing.T) {
10251025
core := NewMockCoreService(ctrl)
10261026
grpcSvr := newGRPCHandler(core)
10271027

1028-
core.EXPECT().TraceTransaction(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil, logger.NewStructLogger(nil), nil)
1028+
core.EXPECT().TraceTransaction(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil, &evmTracer{EVMLogger: logger.NewStructLogger(nil)}, nil)
10291029
resp, err := grpcSvr.TraceTransactionStructLogs(context.Background(), &iotexapi.TraceTransactionStructLogsRequest{
10301030
ActionHash: "_actionHash",
10311031
})

api/mock_apicoreservice.go

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/web3server.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,11 +254,12 @@ func (svr *web3Handler) handleWeb3Req(ctx context.Context, web3Req *gjson.Result
254254
res, err = svr.unsubscribe(web3Req)
255255
case "eth_getBlobSidecars":
256256
res, err = svr.getBlobSidecars(web3Req)
257-
//TODO: enable debug api after archive mode is supported
258257
case "debug_traceTransaction":
259258
res, err = svr.traceTransaction(ctx, web3Req)
260259
case "debug_traceCall":
261260
res, err = svr.traceCall(ctx, web3Req)
261+
case "debug_traceBlockByNumber":
262+
res, err = svr.traceBlockByNumber(ctx, web3Req)
262263
case "eth_coinbase", "eth_getUncleCountByBlockHash", "eth_getUncleCountByBlockNumber",
263264
"eth_sign", "eth_signTransaction", "eth_sendTransaction", "eth_getUncleByBlockHashAndIndex",
264265
"eth_getUncleByBlockNumberAndIndex", "eth_pendingTransactions":
@@ -1171,6 +1172,18 @@ func (svr *web3Handler) traceCall(ctx context.Context, in *gjson.Result) (interf
11711172
}
11721173
}
11731174

1175+
func (svr *web3Handler) traceBlockByNumber(ctx context.Context, in *gjson.Result) (any, error) {
1176+
blkParam, tracerParam := in.Get("params.0"), in.Get("params.1")
1177+
blkNum, err := parseBlockNumber(&blkParam)
1178+
if err != nil {
1179+
return nil, errors.Wrap(err, "failed to parse block number")
1180+
}
1181+
height, _ := blockNumberToHeight(blkNum)
1182+
tracer := parseTracerConfig(&tracerParam)
1183+
_, _, results, err := svr.coreService.TraceBlockByNumber(ctx, height, tracer)
1184+
return results, err
1185+
}
1186+
11741187
func (svr *web3Handler) unimplemented() (interface{}, error) {
11751188
return nil, errNotImplemented
11761189
}

api/web3server_marshal.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ type (
8888
StructLogs []apitypes.StructLog `json:"structLogs"`
8989
}
9090

91+
blockTraceResult struct {
92+
TxHash hash.Hash256 `json:"txHash"`
93+
Result any `json:"result"`
94+
}
95+
9196
feeHistoryResult struct {
9297
OldestBlock string `json:"oldestBlock"`
9398
BaseFeePerGas []string `json:"baseFeePerGas"`
@@ -455,3 +460,13 @@ func (obj *streamResponse) MarshalJSON() ([]byte, error) {
455460
},
456461
})
457462
}
463+
464+
func (obj *blockTraceResult) MarshalJSON() ([]byte, error) {
465+
return json.Marshal(&struct {
466+
TxHash string `json:"txHash"`
467+
Result any `json:"result"`
468+
}{
469+
TxHash: "0x" + hex.EncodeToString(obj.TxHash[:]),
470+
Result: obj.Result,
471+
})
472+
}

0 commit comments

Comments
 (0)