Skip to content

Commit

Permalink
Add precompile to make remote http calls (#110)
Browse files Browse the repository at this point in the history
* Add precompile to make remote http calls

* Fix error

* Handle error and integrate other external calls

* Last fixes

* Fix

* Show more diffs

* Fix test
  • Loading branch information
ferranbt authored and dmarzzz committed Dec 21, 2023
1 parent 40806c9 commit e6f1d87
Show file tree
Hide file tree
Showing 19 changed files with 419 additions and 67 deletions.
1 change: 1 addition & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ var (
utils.SuaveConfidentialStorePebbleDbPathFlag,
utils.SuaveEthBundleSigningKeyFlag,
utils.SuaveEthBlockSigningKeyFlag,
utils.SuaveExternalWhitelistFlag,
utils.SuaveDevModeFlag,
}
)
Expand Down
19 changes: 19 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,13 @@ var (
Category: flags.SuaveCategory,
}

SuaveExternalWhitelistFlag = &cli.StringSliceFlag{
Name: "suave.eth.external-whitelist",
EnvVars: []string{"SUAVE_EXTERNAL_WHITELIST"},
Usage: "List of external whitelisted addresses",
Category: flags.SuaveCategory,
}

SuaveDevModeFlag = &cli.BoolFlag{
Name: "suave.dev",
Usage: "Dev mode for suave",
Expand Down Expand Up @@ -1736,6 +1743,18 @@ func SetSuaveConfig(ctx *cli.Context, stack *node.Node, cfg *suave.Config) {
if ctx.IsSet(SuaveEthBlockSigningKeyFlag.Name) {
cfg.EthBlockSigningKeyHex = ctx.String(SuaveEthBlockSigningKeyFlag.Name)
}

if ctx.IsSet(SuaveEthBundleSigningKeyFlag.Name) {
cfg.EthBundleSigningKeyHex = ctx.String(SuaveEthBundleSigningKeyFlag.Name)
}

if ctx.IsSet(SuaveExternalWhitelistFlag.Name) {
cfg.ExternalWhitelist = ctx.StringSlice(SuaveEthBundleSigningKeyFlag.Name)
if len(cfg.ExternalWhitelist) == 0 {
// As of now, default to wildcard
cfg.ExternalWhitelist = []string{"*"}
}
}
}

// SetEthConfig applies eth-related command line flags to the config.
Expand Down
9 changes: 8 additions & 1 deletion core/types/suave_structs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

73 changes: 68 additions & 5 deletions core/vm/contracts_suave.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package vm

import (
"bytes"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -138,13 +143,71 @@ func mustParseMethodAbi(data string, method string) abi.Method {
return inoutAbi.Methods[method]
}

func formatPeekerError(format string, args ...any) ([]byte, error) {
err := fmt.Errorf(format, args...)
return []byte(err.Error()), err
}

type suaveRuntime struct {
suaveContext *SuaveContext
}

var _ SuaveRuntime = &suaveRuntime{}

func (s *suaveRuntime) doHTTPRequest(request types.HttpRequest) ([]byte, error) {
if request.Method != "GET" && request.Method != "POST" {
return nil, fmt.Errorf("only GET and POST methods are supported")
}
if request.Url == "" {
return nil, fmt.Errorf("url is empty")
}

var body io.Reader
if request.Body != nil {
body = bytes.NewReader(request.Body)
}

// decode the url and check if the domain is allowed
parsedURL, err := url.Parse(request.Url)
if err != nil {
panic(err)
}

var allowed bool
for _, allowedDomain := range s.suaveContext.Backend.ExternalWhitelist {
if allowedDomain == "*" || allowedDomain == parsedURL.Hostname() {
allowed = true
break
}
}
if !allowed {
return nil, fmt.Errorf("domain %s is not allowed", parsedURL.Hostname())
}

req, err := http.NewRequest(request.Method, request.Url, body)
if err != nil {
return nil, err
}

for _, header := range request.Headers {
indx := strings.Index(header, ":")
if indx == -1 {
return nil, fmt.Errorf("incorrect header format '%s', no ':' present", header)
}
req.Header.Add(header[:indx], header[indx+1:])
}

client := &http.Client{
Timeout: 5 * time.Second, // TODO: test
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

if resp.StatusCode > 299 {
return nil, fmt.Errorf("http error: %s: %v", resp.Status, data)
}
return data, nil
}
76 changes: 22 additions & 54 deletions core/vm/contracts_suave_eth.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package vm

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"math/big"
"net/http"
"time"
Expand Down Expand Up @@ -280,35 +278,19 @@ func (b *suaveRuntime) buildEthBlock(blockArgs types.BuildBlockArgs, bidId types
}

func (b *suaveRuntime) submitEthBlockBidToRelay(relayUrl string, builderBidJson []byte) ([]byte, error) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(3*time.Second))
defer cancel()

endpoint := relayUrl + "/relay/v1/builder/blocks"
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(builderBidJson))
if err != nil {
return formatPeekerError("could not prepare request to relay: %w", err)
}

req.Header.Add("Content-Type", "application/json")

// Execute request
resp, err := http.DefaultClient.Do(req)
if err != nil {
return formatPeekerError("could not send request to relay: %w", err)
httpReq := types.HttpRequest{
Method: http.MethodPost,
Url: endpoint,
Body: builderBidJson,
Headers: []string{
"Content-Type:application/json",
"Accept:application/json",
},
}
defer resp.Body.Close()

if resp.StatusCode == http.StatusNoContent {
return nil, nil
}

if resp.StatusCode > 299 {
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return formatPeekerError("could not read error response body for status code %d: %w", resp.StatusCode, err)
}

return formatPeekerError("relay request failed with code %d: %s", resp.StatusCode, string(bodyBytes))
if _, err := b.doHTTPRequest(httpReq); err != nil {
return nil, err
}

return nil, nil
Expand Down Expand Up @@ -356,9 +338,6 @@ func executableDataToCapellaExecutionPayload(data *engine.ExecutableData) (*spec
}

func (c *suaveRuntime) submitBundleJsonRPC(url string, method string, params []byte) ([]byte, error) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(3*time.Second))
defer cancel()

request := map[string]interface{}{
"id": json.RawMessage([]byte("1")),
"jsonrpc": "2.0",
Expand All @@ -379,29 +358,18 @@ func (c *suaveRuntime) submitBundleJsonRPC(url string, method string, params []b

signature := crypto.PubkeyToAddress(c.suaveContext.Backend.EthBundleSigningKey.PublicKey).Hex() + ":" + hexutil.Encode(sig)

req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(body))
if err != nil {
return formatPeekerError("could not prepare request to relay: %w", err)
}

req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "application/json")
req.Header.Add("X-Flashbots-Signature", signature)

// Execute request
resp, err := http.DefaultClient.Do(req)
if err != nil {
return formatPeekerError("could not send request to relay: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode > 299 {
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return formatPeekerError("request failed with code %d", resp.StatusCode)
}

return formatPeekerError("request failed with code %d: %s", resp.StatusCode, string(bodyBytes))
httpReq := types.HttpRequest{
Method: http.MethodPost,
Url: url,
Body: body,
Headers: []string{
"Content-Type:application/json",
"Accept:application/json",
"X-Flashbots-Signature:" + signature,
},
}
if _, err := c.doHTTPRequest(httpReq); err != nil {
return nil, err
}

return nil, nil
Expand Down
50 changes: 48 additions & 2 deletions core/vm/contracts_suave_runtime_adapter.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions core/vm/contracts_suave_runtime_adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ func (m *mockRuntime) submitEthBlockBidToRelay(relayUrl string, builderBid []byt
return []byte{0x1}, nil
}

func (m *mockRuntime) doHTTPRequest(request types.HttpRequest) ([]byte, error) {
return []byte{0x1}, nil
}

func TestRuntimeAdapter(t *testing.T) {
adapter := &SuaveRuntimeAdapter{
impl: &mockRuntime{},
Expand Down
Loading

0 comments on commit e6f1d87

Please sign in to comment.