From 42435a4220b11b0ab2ae740c9d60de03f48bc802 Mon Sep 17 00:00:00 2001 From: jsvisa Date: Thu, 31 Oct 2024 15:50:25 +0800 Subject: [PATCH 1/5] api/debug: support debug with txhash Signed-off-by: jsvisa api/debug: blockId should use tx's instead Signed-off-by: jsvisa fix tests Signed-off-by: jsvisa --- api/debug/debug.go | 33 ++++++++++++++++++++------------- api/debug/debug_test.go | 6 +++--- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/api/debug/debug.go b/api/debug/debug.go index 5ff54f1dc..1e4cb5a74 100644 --- a/api/debug/debug.go +++ b/api/debug/debug.go @@ -359,17 +359,23 @@ func (d *Debug) handleDebugStorage(w http.ResponseWriter, req *http.Request) err func (d *Debug) parseTarget(target string) (blockID thor.Bytes32, txIndex uint64, clauseIndex uint32, err error) { parts := strings.Split(target, "/") - if len(parts) != 3 { + if len(parts) == 2 { + // this is a fake blockID, reset in the tx retrieve process + blockID = d.repo.BestBlockSummary().Header.ID() + } else if len(parts) == 3 { + blockID, err = thor.ParseBytes32(parts[0]) + if err != nil { + return thor.Bytes32{}, 0, 0, utils.BadRequest(errors.WithMessage(err, "target[0]")) + } + parts = parts[1:] + } else { return thor.Bytes32{}, 0, 0, utils.BadRequest(errors.New("target:" + target + " unsupported")) } - blockID, err = thor.ParseBytes32(parts[0]) - if err != nil { - return thor.Bytes32{}, 0, 0, utils.BadRequest(errors.WithMessage(err, "target[0]")) - } - if len(parts[1]) == 64 || len(parts[1]) == 66 { - txID, err := thor.ParseBytes32(parts[1]) + + if len(parts[0]) == 64 || len(parts[0]) == 66 { + txID, err := thor.ParseBytes32(parts[0]) if err != nil { - return thor.Bytes32{}, 0, 0, utils.BadRequest(errors.WithMessage(err, "target[1]")) + return thor.Bytes32{}, 0, 0, utils.BadRequest(errors.WithMessage(err, "target[0,1]")) } txMeta, err := d.repo.NewChain(blockID).GetTransactionMeta(txID) @@ -379,19 +385,20 @@ func (d *Debug) parseTarget(target string) (blockID thor.Bytes32, txIndex uint64 } return thor.Bytes32{}, 0, 0, err } + blockID = txMeta.BlockID txIndex = txMeta.Index } else { - i, err := strconv.ParseUint(parts[1], 0, 0) + i, err := strconv.ParseUint(parts[0], 0, 0) if err != nil { - return thor.Bytes32{}, 0, 0, utils.BadRequest(errors.WithMessage(err, "target[1]")) + return thor.Bytes32{}, 0, 0, utils.BadRequest(errors.WithMessage(err, "target[0,1]")) } txIndex = i } - i, err := strconv.ParseUint(parts[2], 0, 0) + i, err := strconv.ParseUint(parts[1], 0, 0) if err != nil { - return thor.Bytes32{}, 0, 0, utils.BadRequest(errors.WithMessage(err, "target[2]")) + return thor.Bytes32{}, 0, 0, utils.BadRequest(errors.WithMessage(err, "target[1,2]")) } else if i > math.MaxUint32 { - return thor.Bytes32{}, 0, 0, utils.BadRequest(errors.New("invalid target[2]")) + return thor.Bytes32{}, 0, 0, utils.BadRequest(errors.New("invalid target[1,2]")) } clauseIndex = uint32(i) return diff --git a/api/debug/debug_test.go b/api/debug/debug_test.go index 1275a9030..a02617bea 100644 --- a/api/debug/debug_test.go +++ b/api/debug/debug_test.go @@ -187,7 +187,7 @@ func testTraceClauseWithBadTxID(t *testing.T) { Target: fmt.Sprintf("%s/badTxId/x", blk.Header().ID()), } res := httpPostAndCheckResponseStatus(t, "/debug/tracers", traceClauseOption, 400) - assert.Equal(t, `target[1]: strconv.ParseUint: parsing "badTxId": invalid syntax`, strings.TrimSpace(res)) + assert.Equal(t, `target[0,1]: strconv.ParseUint: parsing "badTxId": invalid syntax`, strings.TrimSpace(res)) } func testTraceClauseWithNonExistingTx(t *testing.T) { @@ -207,7 +207,7 @@ func testTraceClauseWithBadClauseIndex(t *testing.T) { Target: fmt.Sprintf("%s/%s/x", blk.Header().ID(), transaction.ID()), } res := httpPostAndCheckResponseStatus(t, "/debug/tracers", traceClauseOption, 400) - assert.Equal(t, `target[2]: strconv.ParseUint: parsing "x": invalid syntax`, strings.TrimSpace(res)) + assert.Equal(t, `target[1,2]: strconv.ParseUint: parsing "x": invalid syntax`, strings.TrimSpace(res)) // Clause index is out of range traceClauseOption = &TraceClauseOption{ @@ -215,7 +215,7 @@ func testTraceClauseWithBadClauseIndex(t *testing.T) { Target: fmt.Sprintf("%s/%s/%d", blk.Header().ID(), transaction.ID(), uint64(math.MaxUint64)), } res = httpPostAndCheckResponseStatus(t, "/debug/tracers", traceClauseOption, 400) - assert.Equal(t, `invalid target[2]`, strings.TrimSpace(res)) + assert.Equal(t, `invalid target[1,2]`, strings.TrimSpace(res)) } func testTraceClauseWithCustomTracer(t *testing.T) { From 00dc2a5e56fc91eeaaac72f64b90a91411572f5a Mon Sep 17 00:00:00 2001 From: jsvisa Date: Sat, 30 Nov 2024 11:13:12 +0800 Subject: [PATCH 2/5] debug: add test Signed-off-by: jsvisa --- api/debug/debug_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/api/debug/debug_test.go b/api/debug/debug_test.go index a02617bea..42a0b8993 100644 --- a/api/debug/debug_test.go +++ b/api/debug/debug_test.go @@ -265,6 +265,26 @@ func testTraceClause(t *testing.T) { assert.Equal(t, expectedExecutionResult, parsedExecutionRes) } +func testTraceClauseWithoutBlockId(t *testing.T) { + traceClauseOption := &TraceClauseOption{ + Name: "structLogger", + Target: fmt.Sprintf("%s/1", transaction.ID()), + } + expectedExecutionResult := &logger.ExecutionResult{ + Gas: 0, + Failed: false, + ReturnValue: "", + StructLogs: make([]logger.StructLogRes, 0), + } + res := httpPostAndCheckResponseStatus(t, "/debug/tracers", traceClauseOption, 200) + + var parsedExecutionRes *logger.ExecutionResult + if err := json.Unmarshal([]byte(res), &parsedExecutionRes); err != nil { + t.Fatal(err) + } + assert.Equal(t, expectedExecutionResult, parsedExecutionRes) +} + func testTraceClauseWithTxIndexOutOfBound(t *testing.T) { traceClauseOption := &TraceClauseOption{ Name: "structLogger", From 70b66b3252a44c29aef6527f0f08831990cbf425 Mon Sep 17 00:00:00 2001 From: tony Date: Thu, 5 Dec 2024 17:04:07 +0000 Subject: [PATCH 3/5] improve parseTarget Signed-off-by: jsvisa --- api/debug/debug.go | 138 ++++++++++++++++++++++++---------------- api/debug/debug_test.go | 15 +++-- 2 files changed, 92 insertions(+), 61 deletions(-) diff --git a/api/debug/debug.go b/api/debug/debug.go index 1e4cb5a74..497518982 100644 --- a/api/debug/debug.go +++ b/api/debug/debug.go @@ -75,22 +75,8 @@ func New( } } -func (d *Debug) prepareClauseEnv(ctx context.Context, blockID thor.Bytes32, txIndex uint64, clauseIndex uint32) (*runtime.Runtime, *runtime.TransactionExecutor, thor.Bytes32, error) { - block, err := d.repo.GetBlock(blockID) - if err != nil { - if d.repo.IsNotFound(err) { - return nil, nil, thor.Bytes32{}, utils.Forbidden(errors.New("block not found")) - } - return nil, nil, thor.Bytes32{}, err - } - txs := block.Transactions() - if txIndex >= uint64(len(txs)) { - return nil, nil, thor.Bytes32{}, utils.Forbidden(errors.New("tx index out of range")) - } - txID := txs[txIndex].ID() - if clauseIndex >= uint32(len(txs[txIndex].Clauses())) { - return nil, nil, thor.Bytes32{}, utils.Forbidden(errors.New("clause index out of range")) - } +// prepareClauseEnv prepares the runtime environment for the specified clause. +func (d *Debug) prepareClauseEnv(ctx context.Context, block *block.Block, txID thor.Bytes32, clauseIndex uint32) (*runtime.Runtime, *runtime.TransactionExecutor, thor.Bytes32, error) { rt, err := consensus.New( d.repo, d.stater, @@ -99,17 +85,29 @@ func (d *Debug) prepareClauseEnv(ctx context.Context, blockID thor.Bytes32, txIn if err != nil { return nil, nil, thor.Bytes32{}, err } - for i, tx := range txs { - if uint64(i) > txIndex { - break + + var found bool + txs := block.Transactions() + for _, tx := range txs { + if txID == tx.ID() { + found = true + if clauseIndex >= uint32(len(tx.Clauses())) { + return nil, nil, thor.Bytes32{}, utils.Forbidden(errors.New("clause index out of range")) + } } + } + if !found { + return nil, nil, thor.Bytes32{}, utils.Forbidden(errors.New("transaction not found")) + } + + for _, tx := range block.Transactions() { txExec, err := rt.PrepareTransaction(tx) if err != nil { return nil, nil, thor.Bytes32{}, err } clauseCounter := uint32(0) for txExec.HasNextClause() { - if txIndex == uint64(i) && clauseIndex == clauseCounter { + if tx.ID() == txID && clauseIndex == clauseCounter { return rt, txExec, txID, nil } exec, _ := txExec.PrepareNext() @@ -127,18 +125,27 @@ func (d *Debug) prepareClauseEnv(ctx context.Context, blockID thor.Bytes32, txIn default: } } + + // no env created, that means tx was reverted at an early clause return nil, nil, thor.Bytes32{}, utils.Forbidden(errors.New("early reverted")) } // trace an existed clause -func (d *Debug) traceClause(ctx context.Context, tracer tracers.Tracer, blockID thor.Bytes32, txIndex uint64, clauseIndex uint32) (interface{}, error) { - rt, txExec, txID, err := d.prepareClauseEnv(ctx, blockID, txIndex, clauseIndex) +func (d *Debug) traceClause(ctx context.Context, tracer tracers.Tracer, block *block.Block, txID thor.Bytes32, clauseIndex uint32) (interface{}, error) { + rt, txExec, txID, err := d.prepareClauseEnv(ctx, block, txID, clauseIndex) if err != nil { return nil, err } + var txIndex uint64 = math.MaxUint64 + for i, tx := range block.Transactions() { + if tx.ID() == txID { + txIndex = uint64(i) + break + } + } tracer.SetContext(&tracers.Context{ - BlockID: blockID, + BlockID: block.Header().ID(), BlockTime: rt.Context().Time, TxID: txID, TxIndex: txIndex, @@ -178,11 +185,11 @@ func (d *Debug) handleTraceClause(w http.ResponseWriter, req *http.Request) erro return utils.Forbidden(err) } - blockID, txIndex, clauseIndex, err := d.parseTarget(opt.Target) + block, txID, clauseIndex, err := d.parseTarget(opt.Target) if err != nil { return err } - res, err := d.traceClause(req.Context(), tracer, blockID, txIndex, clauseIndex) + res, err := d.traceClause(req.Context(), tracer, block, txID, clauseIndex) if err != nil { return err } @@ -291,8 +298,8 @@ func (d *Debug) traceCall(ctx context.Context, tracer tracers.Tracer, header *bl return tracer.GetResult() } -func (d *Debug) debugStorage(ctx context.Context, contractAddress thor.Address, blockID thor.Bytes32, txIndex uint64, clauseIndex uint32, keyStart []byte, maxResult int) (*StorageRangeResult, error) { - rt, _, _, err := d.prepareClauseEnv(ctx, blockID, txIndex, clauseIndex) +func (d *Debug) debugStorage(ctx context.Context, contractAddress thor.Address, block *block.Block, txID thor.Bytes32, clauseIndex uint32, keyStart []byte, maxResult int) (*StorageRangeResult, error) { + rt, _, _, err := d.prepareClauseEnv(ctx, block, txID, clauseIndex) if err != nil { return nil, err } @@ -357,48 +364,71 @@ func (d *Debug) handleDebugStorage(w http.ResponseWriter, req *http.Request) err return utils.WriteJSON(w, res) } -func (d *Debug) parseTarget(target string) (blockID thor.Bytes32, txIndex uint64, clauseIndex uint32, err error) { +func (d *Debug) parseTarget(target string) (block *block.Block, txID thor.Bytes32, clauseIndex uint32, err error) { + // target can be `${blockID}/${txID|txIndex}/${clauseIndex}` or `${txID}/${clauseIndex}` parts := strings.Split(target, "/") - if len(parts) == 2 { - // this is a fake blockID, reset in the tx retrieve process - blockID = d.repo.BestBlockSummary().Header.ID() - } else if len(parts) == 3 { - blockID, err = thor.ParseBytes32(parts[0]) - if err != nil { - return thor.Bytes32{}, 0, 0, utils.BadRequest(errors.WithMessage(err, "target[0]")) - } - parts = parts[1:] - } else { - return thor.Bytes32{}, 0, 0, utils.BadRequest(errors.New("target:" + target + " unsupported")) + if len(parts) != 3 && len(parts) != 2 { + return nil, thor.Bytes32{}, 0, utils.BadRequest(errors.New("target:" + target + " unsupported")) } - if len(parts[0]) == 64 || len(parts[0]) == 66 { - txID, err := thor.ParseBytes32(parts[0]) + if len(parts) == 2 { + txID, err = thor.ParseBytes32(parts[0]) if err != nil { - return thor.Bytes32{}, 0, 0, utils.BadRequest(errors.WithMessage(err, "target[0,1]")) + return nil, thor.Bytes32{}, 0, utils.BadRequest(errors.WithMessage(err, "target([0]")) } - - txMeta, err := d.repo.NewChain(blockID).GetTransactionMeta(txID) + txMeta, err := d.repo.NewBestChain().GetTransactionMeta(txID) if err != nil { if d.repo.IsNotFound(err) { - return thor.Bytes32{}, 0, 0, utils.Forbidden(errors.New("transaction not found")) + return nil, thor.Bytes32{}, 0, utils.Forbidden(errors.New("transaction not found")) } - return thor.Bytes32{}, 0, 0, err + return nil, thor.Bytes32{}, 0, err + } + block, err = d.repo.GetBlock(txMeta.BlockID) + if err != nil { + return nil, thor.Bytes32{}, 0, err } - blockID = txMeta.BlockID - txIndex = txMeta.Index } else { - i, err := strconv.ParseUint(parts[0], 0, 0) + blockID, err := thor.ParseBytes32(parts[0]) if err != nil { - return thor.Bytes32{}, 0, 0, utils.BadRequest(errors.WithMessage(err, "target[0,1]")) + return nil, thor.Bytes32{}, 0, utils.BadRequest(errors.WithMessage(err, "target[0]")) + } + block, err = d.repo.GetBlock(blockID) + if err != nil { + return nil, thor.Bytes32{}, 0, err + } + if len(parts[1]) == 64 || len(parts[1]) == 66 { + txID, err = thor.ParseBytes32(parts[1]) + if err != nil { + return nil, thor.Bytes32{}, 0, utils.BadRequest(errors.WithMessage(err, "target[1]")) + } + + var found bool + for _, tx := range block.Transactions() { + if tx.ID() == txID { + found = true + break + } + } + if !found { + return nil, thor.Bytes32{}, 0, utils.Forbidden(errors.New("transaction not found")) + } + } else { + i, err := strconv.ParseUint(parts[1], 0, 0) + if err != nil { + return nil, thor.Bytes32{}, 0, utils.BadRequest(errors.WithMessage(err, "target[1]")) + } + if i >= uint64(len(block.Transactions())) { + return nil, thor.Bytes32{}, 0, utils.Forbidden(errors.New("tx index out of range")) + } + txID = block.Transactions()[i].ID() } - txIndex = i } - i, err := strconv.ParseUint(parts[1], 0, 0) + + i, err := strconv.ParseUint(parts[len(parts)-1], 0, 0) if err != nil { - return thor.Bytes32{}, 0, 0, utils.BadRequest(errors.WithMessage(err, "target[1,2]")) + return nil, thor.Bytes32{}, 0, utils.BadRequest(errors.WithMessage(err, fmt.Sprintf("target[%d]", len(parts)-1))) } else if i > math.MaxUint32 { - return thor.Bytes32{}, 0, 0, utils.BadRequest(errors.New("invalid target[1,2]")) + return nil, thor.Bytes32{}, 0, utils.BadRequest(fmt.Errorf("invalid target[%d]", len(parts)-1)) } clauseIndex = uint32(i) return diff --git a/api/debug/debug_test.go b/api/debug/debug_test.go index 42a0b8993..37a7841d6 100644 --- a/api/debug/debug_test.go +++ b/api/debug/debug_test.go @@ -6,7 +6,6 @@ package debug import ( - "context" "encoding/json" "fmt" "math/big" @@ -176,9 +175,11 @@ func testTraceClauseWithBadBlockID(t *testing.T) { } func testTraceClauseWithNonExistingBlockID(t *testing.T) { - _, _, _, err := debug.prepareClauseEnv(context.Background(), datagen.RandomHash(), 1, 1) - - assert.Error(t, err) + traceClauseOption := &TraceClauseOption{ + Name: "structLogger", + Target: fmt.Sprintf("%s/x/x", datagen.RandomHash()), + } + httpPostAndCheckResponseStatus(t, "/debug/tracers", traceClauseOption, 500) } func testTraceClauseWithBadTxID(t *testing.T) { @@ -187,7 +188,7 @@ func testTraceClauseWithBadTxID(t *testing.T) { Target: fmt.Sprintf("%s/badTxId/x", blk.Header().ID()), } res := httpPostAndCheckResponseStatus(t, "/debug/tracers", traceClauseOption, 400) - assert.Equal(t, `target[0,1]: strconv.ParseUint: parsing "badTxId": invalid syntax`, strings.TrimSpace(res)) + assert.Equal(t, `target[1]: strconv.ParseUint: parsing "badTxId": invalid syntax`, strings.TrimSpace(res)) } func testTraceClauseWithNonExistingTx(t *testing.T) { @@ -207,7 +208,7 @@ func testTraceClauseWithBadClauseIndex(t *testing.T) { Target: fmt.Sprintf("%s/%s/x", blk.Header().ID(), transaction.ID()), } res := httpPostAndCheckResponseStatus(t, "/debug/tracers", traceClauseOption, 400) - assert.Equal(t, `target[1,2]: strconv.ParseUint: parsing "x": invalid syntax`, strings.TrimSpace(res)) + assert.Equal(t, `target[2]: strconv.ParseUint: parsing "x": invalid syntax`, strings.TrimSpace(res)) // Clause index is out of range traceClauseOption = &TraceClauseOption{ @@ -215,7 +216,7 @@ func testTraceClauseWithBadClauseIndex(t *testing.T) { Target: fmt.Sprintf("%s/%s/%d", blk.Header().ID(), transaction.ID(), uint64(math.MaxUint64)), } res = httpPostAndCheckResponseStatus(t, "/debug/tracers", traceClauseOption, 400) - assert.Equal(t, `invalid target[1,2]`, strings.TrimSpace(res)) + assert.Equal(t, `invalid target[2]`, strings.TrimSpace(res)) } func testTraceClauseWithCustomTracer(t *testing.T) { From c3f66cccf7e024ed9a196b8a11aa0be75f74322a Mon Sep 17 00:00:00 2001 From: tony Date: Fri, 6 Dec 2024 09:15:21 +0000 Subject: [PATCH 4/5] update doc Signed-off-by: jsvisa --- api/doc/thor.yaml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/api/doc/thor.yaml b/api/doc/thor.yaml index dcf0ae6b9..f3ac15d1e 100644 --- a/api/doc/thor.yaml +++ b/api/doc/thor.yaml @@ -2104,11 +2104,12 @@ components: The unified path of the target to be traced. Currently, only the clause is supported. Format: - `blockID/(txIndex|txId)/clauseIndex` + `blockID/(txIndex|txId)/clauseIndex` or `txID/clauseIndex` + example: '0x010709463c1f0c9aa66a31182fb36d1977d99bfb6526bae0564a0eac4006c31a/0/0' nullable: false - pattern: '^0x[0-9a-fA-F]{64}\/(0x[0-9a-fA-F]{64}|\d+)\/[0-9]+$' + pattern: '^0x[0-9a-fA-F]{64}(\/(0x[0-9a-fA-F]{64}|\d+))?\/[0-9]+$' example: target: '0x010709463c1f0c9aa66a31182fb36d1977d99bfb6526bae0564a0eac4006c31a/0/0' @@ -2174,9 +2175,9 @@ components: The unified path of the transaction clause. Format: - `blockID/(txIndex|txId)/clauseIndex` + `blockID/(txIndex|txId)/clauseIndex` or `txID/clauseIndex` nullable: false - pattern: '^0x[0-9a-fA-F]{64}\/(0x[0-9a-fA-F]{64}|\d+)\/[0-9]+$' + pattern: '^0x[0-9a-fA-F]{64}(\/(0x[0-9a-fA-F]{64}|\d+))?\/[0-9]+$' StorageRange: type: object From aecf01df4b886f3a7625898fe198c936ed54ea9c Mon Sep 17 00:00:00 2001 From: tony Date: Fri, 6 Dec 2024 09:29:35 +0000 Subject: [PATCH 5/5] fix tests Signed-off-by: jsvisa --- api/debug/debug_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/debug/debug_test.go b/api/debug/debug_test.go index 37a7841d6..d56718143 100644 --- a/api/debug/debug_test.go +++ b/api/debug/debug_test.go @@ -61,6 +61,7 @@ func TestDebug(t *testing.T) { "testTraceClauseWithClauseIndexOutOfBound": testTraceClauseWithClauseIndexOutOfBound, "testTraceClauseWithCustomTracer": testTraceClauseWithCustomTracer, "testTraceClause": testTraceClause, + "testTraceClauseWithoutBlockID": testTraceClauseWithoutBlockID, } { t.Run(name, tt) } @@ -266,7 +267,7 @@ func testTraceClause(t *testing.T) { assert.Equal(t, expectedExecutionResult, parsedExecutionRes) } -func testTraceClauseWithoutBlockId(t *testing.T) { +func testTraceClauseWithoutBlockID(t *testing.T) { traceClauseOption := &TraceClauseOption{ Name: "structLogger", Target: fmt.Sprintf("%s/1", transaction.ID()),