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

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,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:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't tried but when this yml gets generated, does it contain a working example ? (like the other endpoints)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so, but do you mean examples for values? like here
or code/usage example?

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
41 changes: 40 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,7 @@
}
metricHTTPReqCounter = metrics.LazyLoadCounterVec("api_request_count", []string{"name", "code", "method"})
metricHTTPReqDuration = metrics.LazyLoadHistogramVec("api_duration_ms", []string{"name", "code", "method"}, metrics.BucketHTTPReqs)
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 +42,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
.
}

// 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 +97,23 @@
subscription = false
)

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the goal of this particular case ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess to separate errors that happened specifically on that endpoint, the name for it is set here. Not sure if that metric is needed, since it's the errors that happened on transactions, that are not mined

ctxWriter := newCallTxResponseWriter(w)
next.ServeHTTP(ctxWriter, r)

// Record VM error if present
if ctxWriter.VMError != "" {
metricTxCallVMErrors().AddWithLabel(1, map[string]string{
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if there are a high number of different errors here ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the code I read, there is always at most only 1 possible vmerror per transaction, that doesn't depend on the amount of clauses in a transaction. Was that the question?

"error": ctxWriter.VMError,
})
}
return
}

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