Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding transaction_call endpoint #832

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func New(
}
blocks.New(repo, bft).
Mount(router, "/blocks")
transactions.New(repo, txPool).
transactions.New(repo, stater, txPool, bft, forkConfig).
Mount(router, "/transactions")
debug.New(repo, stater, forkConfig, config.CallGasLimit, config.AllowCustomTracer, bft, config.AllowedTracers, config.SoloMode).
Mount(router, "/debug")
Expand Down
171 changes: 171 additions & 0 deletions api/doc/thor.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,177 @@ paths:
type: string
example: 'Invalid transaction ID'

/transactions/call:
post:
parameters:
- $ref: '#/components/parameters/HeadInQuery'
tags:
- Transactions
summary: Execute a transaction locally
description: |
This endpoint allows you to execute a transaction locally. It simulates the transaction execution without submitting it to the blockchain.

requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
id:
type: string
description: Transaction ID placeholder.
example: '0x0000000000000000000000000000000000000000000000000000000000000000'
chainTag:
type: integer
description: Chain tag identifier.
example: 246
blockRef:
type: string
description: Block reference.
example: '0x0000000000000000'
expiration:
type: integer
description: Expiration time for the transaction.
example: 10
clauses:
type: array
description: An array of actions the transaction will perform.
items:
type: object
properties:
to:
type: string
description: Recipient address.
example: '0xf077b491b355e64048ce21e3a6fc4751eeea77fa'
value:
type: string
description: Amount to transfer, in hexadecimal.
example: '0x4d2'
data:
type: string
description: Input data for the clause.
example: '0x'
gasPriceCoef:
type: integer
description: Coefficient for gas price.
example: 0
gas:
type: integer
description: Gas limit.
example: 21000
origin:
type: string
description: Origin address.
example: '0xf077b491b355e64048ce21e3a6fc4751eeea77fa'
delegator:
type: string
description: Sponsor or delegator address, if any.
nullable: true
example: null
nonce:
type: string
description: Transaction nonce.
example: '0x0'
dependsOn:
type: string
description: Dependent transaction ID, if any.
nullable: true
example: null
size:
type: integer
description: Size of the transaction.
example: 40
meta:
type: string
description: Metadata for the transaction, if any.
nullable: true
example: null
example:
id: '0x0000000000000000000000000000000000000000000000000000000000000000'
chainTag: 246
blockRef: '0x0000000000000000'
expiration: 10
clauses:
- to: '0xf077b491b355e64048ce21e3a6fc4751eeea77fa'
value: '0x4d2'
data: '0x'
gasPriceCoef: 0
gas: 21000
origin: '0xf077b491b355e64048ce21e3a6fc4751eeea77fa'
delegator: null
nonce: '0x0'
dependsOn: null
size: 40
meta: null

responses:
'200':
description: Execution result
content:
application/json:
schema:
type: object
properties:
gasUsed:
type: integer
description: Total gas used.
example: 21000
gasPayer:
type: string
description: Address that paid the gas fee.
example: '0xf077b491b355e64048ce21e3a6fc4751eeea77fa'
paid:
type: string
description: Total amount paid for gas, in hexadecimal.
example: '0x2ea11e32ad50000'
reward:
type: string
description: Gas reward, in hexadecimal.
example: '0xdfd22a8cd98000'
reverted:
type: boolean
description: Indicates if the transaction was reverted.
example: false
txID:
type: string
description: Transaction ID.
example: '0x0000000000000000000000000000000000000000000000000000000000000000'
txOrigin:
type: string
description: Origin address.
example: '0xf077b491b355e64048ce21e3a6fc4751eeea77fa'
outputs:
type: array
description: Details of outputs produced by each clause.
items:
type: object
properties:
contractAddress:
type: string
description: Contract address, if applicable.
nullable: true
example: null
events:
type: array
items:
$ref: '#/components/schemas/Event'
transfers:
type: array
items:
$ref: '#/components/schemas/Transfer'
vmError:
type: string
description: Virtual machine error message, if any.
example: ''
'400':
description: Bad Request
content:
text/plain:
schema:
type: string
example: 'Invalid transaction request'

/transactions/{id}/receipt:
get:
parameters:
Expand Down
43 changes: 42 additions & 1 deletion api/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import (
"bufio"
"encoding/json"
"errors"
"net"
"net/http"
Expand All @@ -25,6 +26,8 @@
}
metricHTTPReqCounter = metrics.LazyLoadCounterVec("api_request_count", []string{"name", "code", "method"})
metricHTTPReqDuration = metrics.LazyLoadHistogramVec("api_duration_ms", []string{"name", "code", "method"}, metrics.BucketHTTPReqs)
metricActiveWebsocketCount = metrics.LazyLoadGaugeVec("api_active_websocket_count", []string{"subject"})
metricTxCallVMErrors = metrics.LazyLoadCounterVec("api_tx_call_vm_errors", []string{"error"})
metricWebsocketDuration = metrics.LazyLoadHistogramVec("api_websocket_duration", []string{"name", "code"}, websocketDurations)
metricActiveWebsocketGauge = metrics.LazyLoadGaugeVec("api_active_websocket_gauge", []string{"name"})
metricWebsocketCounter = metrics.LazyLoadCounterVec("api_websocket_counter", []string{"name"})
Expand All @@ -40,11 +43,35 @@
return &metricsResponseWriter{w, http.StatusOK}
}

type callTxResponseWriter struct {
http.ResponseWriter
statusCode int
vmError string
}

func newCallTxResponseWriter(w http.ResponseWriter) *callTxResponseWriter {
return &callTxResponseWriter{w, http.StatusOK, ""}
}

func (m *metricsResponseWriter) WriteHeader(code int) {
m.statusCode = code
m.ResponseWriter.WriteHeader(code)
}

func (c *callTxResponseWriter) Write(b []byte) (int, error) {
var resp struct {
VmError string `json:"vmError"`
}

if err := json.Unmarshal(b, &resp); err == nil {
if resp.VmError != "" {
c.vmError = resp.VmError
}
}

return c.ResponseWriter.Write(b)

Check warning

Code scanning / CodeQL

Reflected cross-site scripting Medium

Cross-site scripting vulnerability due to
user-provided value
.
Cross-site scripting vulnerability due to
user-provided value
.

Copilot Autofix AI 7 days ago

To fix the reflected cross-site scripting vulnerability, we need to ensure that any user-controlled data is properly sanitized or escaped before being written to the HTTP response. In this case, we can use the html.EscapeString function to escape any potentially dangerous characters in the vmError field before writing it to the response.

Suggested changeset 1
api/metrics.go

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/api/metrics.go b/api/metrics.go
--- a/api/metrics.go
+++ b/api/metrics.go
@@ -15,2 +15,3 @@
 	"strings"
+	"html"
 	"time"
@@ -67,3 +68,3 @@
 		if resp.VmError != "" {
-			c.vmError = resp.VmError
+			c.vmError = html.EscapeString(resp.VmError)
 		}
EOF
@@ -15,2 +15,3 @@
"strings"
"html"
"time"
@@ -67,3 +68,3 @@
if resp.VmError != "" {
c.vmError = resp.VmError
c.vmError = html.EscapeString(resp.VmError)
}
Copilot is powered by AI and may make mistakes. Always verify output.
Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
}

// Hijack complies the writer with WS subscriptions interface
// Hijack lets the caller take over the connection.
// After a call to Hijack the HTTP server library
Expand All @@ -71,10 +98,24 @@
subscription = false
)

// all named route will be recorded
if rt != nil && rt.GetName() != "" {
enabled = true
name = rt.GetName()

if name == "transactions_call_tx" {
ctxWriter := newCallTxResponseWriter(w)
next.ServeHTTP(ctxWriter, r)

// Record VM error if present
if ctxWriter.vmError != "" {
metricTxCallVMErrors().AddWithLabel(1, map[string]string{
"error": ctxWriter.vmError,
})
}
return
}

// Handle subscriptions
if strings.HasPrefix(name, "subscriptions") {
subscription = true
name = "WS " + r.URL.Path
Expand Down
Loading
Loading