Skip to content
Merged
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
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.9.0
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
gopkg.in/yaml.v3 v3.0.1
)

require (
Expand Down Expand Up @@ -44,8 +45,6 @@ require (
github.com/valyala/fastrand v1.1.0 // indirect
github.com/valyala/histogram v1.2.0 // indirect
golang.org/x/sync v0.10.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
rsc.io/tmplfunc v0.0.3 // indirect
)

Expand Down
77 changes: 77 additions & 0 deletions openrpc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{
"openrpc": "1.3.2",
"info": {
"title": "Protect API",
"description": "APIs that Protect API Serves without proxying upstream to a provider",
"version": "1.0.0"
},
"methods": [
{
"name": "eth_sendPrivateTransaction",
"description": "Send a single private transaction. Private transactions are sent directly to validators and not included in the public mempool.",
"params": [
{
"name": "TransactionString",
"description": "The raw signed transaction",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "MaxBlockNumber",
"description": "Highest block number that the block can be included in",
"schema": {
"type": "string",
"format": "^0x(0|[1-9a-f][0-9a-f]*)$"
}
},
{
"name": "Preferences",
"description": "Transaction preferences",
"schema": {
"type": "object",
"properties": {
"fast": {
"type": "boolean"
}
}
}
}
],
"result": {
"name": "TransactionHash",
"schema": {
"title": "32 byte hex value",
"type": "string",
"pattern": "^0x[0-9a-f]{64}$"
}
},
"examples": [
{
"name": "sendPrivateTransactionExample",
"params": [
{
"name": "TransactionString",
"value": "0x02f87205158459682f00850c51e9727a82520894477db63b8e73aea96f201c3c4f5e8fbfcdd18b5c87038d7ea4c6800080c080a0312d6375e578ab953a41456d4be583a46dced97a9e38f349bb8e7b63d14cfc1ea001daea40332180670568a1864e6d3f910b194903128d4a0a5c499597f3f6ff40"
},
{
"name": "MaxBlockNumber",
"value": "0x2540be400"
},
{
"name": "Preferences",
"value": {
"fast": true
}
}
],
"result": {
"name": "ResultExample",
"value": "0xb1770efb14906e509893b6190359658208ae64d0c56e22f748a1b0869885559e"
}
}
]
}
]
}
4 changes: 2 additions & 2 deletions server/request_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,10 @@ func (r *RpcRequestHandler) process() {
// processRequest handles single request
func (r *RpcRequestHandler) processRequest(client RPCProxyClient, jsonReq *types.JsonRpcRequest, origin, referer string, isWhitehatBundleCollection bool, whitehatBundleId string, urlParams URLParameters, reqURL string, body []byte) {
var entry *database.EthSendRawTxEntry
if jsonReq.Method == "eth_sendRawTransaction" {
if jsonReq.Method == "eth_sendRawTransaction" || jsonReq.Method == "eth_sendPrivateTransaction" {
entry = r.requestRecord.AddEthSendRawTxEntry(uuid.New())
// log the full url for debugging
r.logger.Info("[processRequest] eth_sendRawTransaction request URL", "url", reqURL)
r.logger.Info("[processRequest] ", jsonReq.Method, " request URL", "url", reqURL)
}
// Handle single request
rpcReq := NewRpcRequest(r.logger, client, jsonReq, r.relaySigningKey, r.relayUrl, origin, referer, isWhitehatBundleCollection, whitehatBundleId, entry, urlParams, r.chainID, r.rpcCache, r.defaultEthClient)
Expand Down
8 changes: 7 additions & 1 deletion server/request_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type RpcRequest struct {
chainID []byte
rpcCache *application.RpcCache
flashbotsSigningAddress string
maxBlockNumberOverride uint64
}

func NewRpcRequest(
Expand Down Expand Up @@ -110,6 +111,9 @@ func (r *RpcRequest) ProcessRequest() *types.JsonRpcResponse {
case r.jsonReq.Method == "eth_sendRawTransaction":
r.ethSendRawTxEntry.WhiteHatBundleId = r.whitehatBundleId
r.handle_sendRawTransaction()
case r.jsonReq.Method == "eth_sendPrivateTransaction":
r.ethSendRawTxEntry.WhiteHatBundleId = r.whitehatBundleId
r.handle_sendPrivateTransaction()
case r.jsonReq.Method == "eth_getTransactionCount" && r.intercept_signed_eth_getTransactionCount():
case r.jsonReq.Method == "eth_getTransactionCount" && r.intercept_mm_eth_getTransactionCount(): // intercept if MM needs to show an error to user
case r.jsonReq.Method == "eth_call" && r.intercept_eth_call_to_FlashRPC_Contract(): // intercept if Flashbots isRPC contract
Expand Down Expand Up @@ -320,7 +324,9 @@ func (r *RpcRequest) sendTxToRelay() {
sendPrivateTxArgs.Preferences.Privacy.AuctionTimeout = r.urlParams.auctionTimeout
}

if r.urlParams.blockRange > 0 {
if r.maxBlockNumberOverride > 0 {
sendPrivateTxArgs.MaxBlockNumber = r.maxBlockNumberOverride
} else if r.urlParams.blockRange > 0 {
bn, err := r.defaultEthClient.BlockNumber(context.Background())
if err != nil {
r.logger.Error("[sendTxToRelay] BlockNumber failed", "error", err)
Expand Down
37 changes: 37 additions & 0 deletions server/request_sendprivatetx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package server

import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/flashbots/rpc-endpoint/types"
)

func (r *RpcRequest) handle_sendPrivateTransaction() {
if len(r.jsonReq.Params) > 1 {
m, ok := r.jsonReq.Params[1].(string)
if !ok {
r.writeRpcError("MaxBlockNumber must be a string", types.JsonRpcParseError)
return
}
max, err := hexutil.DecodeUint64(m)
if err != nil {
r.writeRpcError("MaxBlockNumber must be a valid hexadecimal string", types.JsonRpcParseError)
return
}
r.maxBlockNumberOverride = max
}
if len(r.jsonReq.Params) > 2 {
f, ok := r.jsonReq.Params[2].(map[string]interface{})
if !ok {
r.writeRpcError("Preferences must be an object", types.JsonRpcParseError)
return
}
fast, ok := f["fast"].(bool)
if !ok {
r.writeRpcError("Preferences fast must be a boolean", types.JsonRpcParseError)
return
}
r.urlParams.pref.Fast = fast
}

r.handle_sendRawTransaction()
}
16 changes: 16 additions & 0 deletions tests/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,22 @@ func TestWhitehatBundleCollectionGetBalance(t *testing.T) {
require.Equal(t, "0x56bc75e2d63100000", val)
}

func TestSendPrivateTransaction(t *testing.T) {
testServerSetupWithMockStore()

req := types.NewJsonRpcRequest(1, "eth_sendPrivateTransaction", []interface{}{
testutils.TestTx_BundleFailedTooManyTimes_RawTx,
})
r1 := testutils.SendRpcAndParseResponseOrFailNowAllowRpcError(t, req)
require.Nil(t, r1.Error)

require.Equal(t, "eth_sendPrivateTransaction", testutils.MockBackendLastJsonRpcRequest.Method)

var res string
json.Unmarshal(r1.Result, &res)
require.Equal(t, testutils.TestTx_BundleFailedTooManyTimes_Hash, res)
}

func Test_StoreRequests(t *testing.T) {
// Store setup
memStore := database.NewMemStore()
Expand Down