Skip to content

Commit 28341eb

Browse files
authored
feat: eth_sendPrivateTransaction (#182)
## 📝 Summary This adds support for `eth_sendPrivateTransaction` instead of letting it fall through to the provider. ## ⛱ Motivation and Context Upstream provider is deprecating their support for this method, and adds an extra hop. ## 📚 References <!-- Any interesting external links to documentation, articles, tweets which add value to the PR --> --- ## ✅ I have run these commands * [x] `make lint` * [x] `make test` * [x] `go mod tidy`
1 parent 618e29a commit 28341eb

File tree

6 files changed

+140
-5
lines changed

6 files changed

+140
-5
lines changed

go.mod

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ require (
1616
github.com/pkg/errors v0.9.1
1717
github.com/stretchr/testify v1.9.0
1818
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
19+
gopkg.in/yaml.v3 v3.0.1
1920
)
2021

2122
require (
@@ -44,8 +45,6 @@ require (
4445
github.com/valyala/fastrand v1.1.0 // indirect
4546
github.com/valyala/histogram v1.2.0 // indirect
4647
golang.org/x/sync v0.10.0 // indirect
47-
gopkg.in/yaml.v2 v2.4.0 // indirect
48-
gopkg.in/yaml.v3 v3.0.1 // indirect
4948
rsc.io/tmplfunc v0.0.3 // indirect
5049
)
5150

openrpc.json

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
{
2+
"openrpc": "1.3.2",
3+
"info": {
4+
"title": "Protect API",
5+
"description": "APIs that Protect API Serves without proxying upstream to a provider",
6+
"version": "1.0.0"
7+
},
8+
"methods": [
9+
{
10+
"name": "eth_sendPrivateTransaction",
11+
"description": "Send a single private transaction. Private transactions are sent directly to validators and not included in the public mempool.",
12+
"params": [
13+
{
14+
"name": "TransactionString",
15+
"description": "The raw signed transaction",
16+
"required": true,
17+
"schema": {
18+
"type": "string"
19+
}
20+
},
21+
{
22+
"name": "MaxBlockNumber",
23+
"description": "Highest block number that the block can be included in",
24+
"schema": {
25+
"type": "string",
26+
"format": "^0x(0|[1-9a-f][0-9a-f]*)$"
27+
}
28+
},
29+
{
30+
"name": "Preferences",
31+
"description": "Transaction preferences",
32+
"schema": {
33+
"type": "object",
34+
"properties": {
35+
"fast": {
36+
"type": "boolean"
37+
}
38+
}
39+
}
40+
}
41+
],
42+
"result": {
43+
"name": "TransactionHash",
44+
"schema": {
45+
"title": "32 byte hex value",
46+
"type": "string",
47+
"pattern": "^0x[0-9a-f]{64}$"
48+
}
49+
},
50+
"examples": [
51+
{
52+
"name": "sendPrivateTransactionExample",
53+
"params": [
54+
{
55+
"name": "TransactionString",
56+
"value": "0x02f87205158459682f00850c51e9727a82520894477db63b8e73aea96f201c3c4f5e8fbfcdd18b5c87038d7ea4c6800080c080a0312d6375e578ab953a41456d4be583a46dced97a9e38f349bb8e7b63d14cfc1ea001daea40332180670568a1864e6d3f910b194903128d4a0a5c499597f3f6ff40"
57+
},
58+
{
59+
"name": "MaxBlockNumber",
60+
"value": "0x2540be400"
61+
},
62+
{
63+
"name": "Preferences",
64+
"value": {
65+
"fast": true
66+
}
67+
}
68+
],
69+
"result": {
70+
"name": "ResultExample",
71+
"value": "0xb1770efb14906e509893b6190359658208ae64d0c56e22f748a1b0869885559e"
72+
}
73+
}
74+
]
75+
}
76+
]
77+
}

server/request_handler.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,10 @@ func (r *RpcRequestHandler) process() {
158158
// processRequest handles single request
159159
func (r *RpcRequestHandler) processRequest(client RPCProxyClient, jsonReq *types.JsonRpcRequest, origin, referer string, isWhitehatBundleCollection bool, whitehatBundleId string, urlParams URLParameters, reqURL string, body []byte) {
160160
var entry *database.EthSendRawTxEntry
161-
if jsonReq.Method == "eth_sendRawTransaction" {
161+
if jsonReq.Method == "eth_sendRawTransaction" || jsonReq.Method == "eth_sendPrivateTransaction" {
162162
entry = r.requestRecord.AddEthSendRawTxEntry(uuid.New())
163163
// log the full url for debugging
164-
r.logger.Info("[processRequest] eth_sendRawTransaction request URL", "url", reqURL)
164+
r.logger.Info("[processRequest] ", jsonReq.Method, " request URL", "url", reqURL)
165165
}
166166
// Handle single request
167167
rpcReq := NewRpcRequest(r.logger, client, jsonReq, r.relaySigningKey, r.relayUrl, origin, referer, isWhitehatBundleCollection, whitehatBundleId, entry, urlParams, r.chainID, r.rpcCache, r.defaultEthClient)

server/request_processor.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ type RpcRequest struct {
4848
chainID []byte
4949
rpcCache *application.RpcCache
5050
flashbotsSigningAddress string
51+
maxBlockNumberOverride uint64
5152
}
5253

5354
func NewRpcRequest(
@@ -110,6 +111,9 @@ func (r *RpcRequest) ProcessRequest() *types.JsonRpcResponse {
110111
case r.jsonReq.Method == "eth_sendRawTransaction":
111112
r.ethSendRawTxEntry.WhiteHatBundleId = r.whitehatBundleId
112113
r.handle_sendRawTransaction()
114+
case r.jsonReq.Method == "eth_sendPrivateTransaction":
115+
r.ethSendRawTxEntry.WhiteHatBundleId = r.whitehatBundleId
116+
r.handle_sendPrivateTransaction()
113117
case r.jsonReq.Method == "eth_getTransactionCount" && r.intercept_signed_eth_getTransactionCount():
114118
case r.jsonReq.Method == "eth_getTransactionCount" && r.intercept_mm_eth_getTransactionCount(): // intercept if MM needs to show an error to user
115119
case r.jsonReq.Method == "eth_call" && r.intercept_eth_call_to_FlashRPC_Contract(): // intercept if Flashbots isRPC contract
@@ -320,7 +324,9 @@ func (r *RpcRequest) sendTxToRelay() {
320324
sendPrivateTxArgs.Preferences.Privacy.AuctionTimeout = r.urlParams.auctionTimeout
321325
}
322326

323-
if r.urlParams.blockRange > 0 {
327+
if r.maxBlockNumberOverride > 0 {
328+
sendPrivateTxArgs.MaxBlockNumber = r.maxBlockNumberOverride
329+
} else if r.urlParams.blockRange > 0 {
324330
bn, err := r.defaultEthClient.BlockNumber(context.Background())
325331
if err != nil {
326332
r.logger.Error("[sendTxToRelay] BlockNumber failed", "error", err)

server/request_sendprivatetx.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package server
2+
3+
import (
4+
"github.com/ethereum/go-ethereum/common/hexutil"
5+
"github.com/flashbots/rpc-endpoint/types"
6+
)
7+
8+
func (r *RpcRequest) handle_sendPrivateTransaction() {
9+
if len(r.jsonReq.Params) > 1 {
10+
m, ok := r.jsonReq.Params[1].(string)
11+
if !ok {
12+
r.writeRpcError("MaxBlockNumber must be a string", types.JsonRpcParseError)
13+
return
14+
}
15+
max, err := hexutil.DecodeUint64(m)
16+
if err != nil {
17+
r.writeRpcError("MaxBlockNumber must be a valid hexadecimal string", types.JsonRpcParseError)
18+
return
19+
}
20+
r.maxBlockNumberOverride = max
21+
}
22+
if len(r.jsonReq.Params) > 2 {
23+
f, ok := r.jsonReq.Params[2].(map[string]interface{})
24+
if !ok {
25+
r.writeRpcError("Preferences must be an object", types.JsonRpcParseError)
26+
return
27+
}
28+
fast, ok := f["fast"].(bool)
29+
if !ok {
30+
r.writeRpcError("Preferences fast must be a boolean", types.JsonRpcParseError)
31+
return
32+
}
33+
r.urlParams.pref.Fast = fast
34+
}
35+
36+
r.handle_sendRawTransaction()
37+
}

tests/e2e_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,22 @@ func TestWhitehatBundleCollectionGetBalance(t *testing.T) {
617617
require.Equal(t, "0x56bc75e2d63100000", val)
618618
}
619619

620+
func TestSendPrivateTransaction(t *testing.T) {
621+
testServerSetupWithMockStore()
622+
623+
req := types.NewJsonRpcRequest(1, "eth_sendPrivateTransaction", []interface{}{
624+
testutils.TestTx_BundleFailedTooManyTimes_RawTx,
625+
})
626+
r1 := testutils.SendRpcAndParseResponseOrFailNowAllowRpcError(t, req)
627+
require.Nil(t, r1.Error)
628+
629+
require.Equal(t, "eth_sendPrivateTransaction", testutils.MockBackendLastJsonRpcRequest.Method)
630+
631+
var res string
632+
json.Unmarshal(r1.Result, &res)
633+
require.Equal(t, testutils.TestTx_BundleFailedTooManyTimes_Hash, res)
634+
}
635+
620636
func Test_StoreRequests(t *testing.T) {
621637
// Store setup
622638
memStore := database.NewMemStore()

0 commit comments

Comments
 (0)