[Chaos Unicorn] Expose ChaosModeUpdate (#1422)

This commit is contained in:
Adam Babik 2019-03-21 13:02:16 +01:00 committed by Andrea Maria Piana
parent 8fe14e8f23
commit 4c1b6c12e4
6 changed files with 166 additions and 1 deletions

View File

@ -641,3 +641,15 @@ func ExportNodeLogs() *C.char {
}
return C.CString(string(data))
}
// ChaosModeUpdate changes the URL of the upstream RPC client.
//export ChaosModeUpdate
func ChaosModeUpdate(on C.int) *C.char {
node := statusBackend.StatusNode()
if node == nil {
return makeJSONResponse(errors.New("node is not running"))
}
err := node.ChaosModeCheckRPCClientsUpstreamURL(on == 1)
return makeJSONResponse(err)
}

View File

@ -632,3 +632,14 @@ func ExportNodeLogs() string {
}
return string(data)
}
// ChaosModeUpdate sets the Chaos Mode on or off.
func ChaosModeUpdate(on bool) string {
node := statusBackend.StatusNode()
if node == nil {
return makeJSONResponse(errors.New("node is not running"))
}
err := node.ChaosModeCheckRPCClientsUpstreamURL(on)
return makeJSONResponse(err)
}

View File

@ -8,6 +8,7 @@ import (
"os"
"path/filepath"
"reflect"
"strings"
"sync"
"time"
@ -639,6 +640,35 @@ func (n *StatusNode) RPCPrivateClient() *rpc.Client {
return n.rpcPrivateClient
}
// ChaosModeCheckRPCClientsUpstreamURL updates RPCClient and RPCPrivateClient upstream URLs,
// if defined, without restarting the node. This is required for the Chaos Unicorn Day.
// Additionally, if the passed URL is Infura, it changes it to httpstat.us/500.
func (n *StatusNode) ChaosModeCheckRPCClientsUpstreamURL(on bool) error {
url := n.config.UpstreamConfig.URL
if on {
if strings.Contains(url, "infura.io") {
url = "https://httpstat.us/500"
}
}
publicClient := n.RPCClient()
if publicClient != nil {
if err := publicClient.UpdateUpstreamURL(url); err != nil {
return err
}
}
privateClient := n.RPCPrivateClient()
if privateClient != nil {
if err := privateClient.UpdateUpstreamURL(url); err != nil {
return err
}
}
return nil
}
// EnsureSync waits until blockchain synchronization
// is complete and returns.
func (n *StatusNode) EnsureSync(ctx context.Context) error {

View File

@ -1,9 +1,12 @@
package node
import (
"fmt"
"io/ioutil"
"math"
"net"
"net/http"
"net/http/httptest"
"os"
"path"
"reflect"
@ -333,3 +336,50 @@ func TestStatusNodeDiscoverNode(t *testing.T) {
require.NoError(t, err)
require.Equal(t, net.ParseIP("127.0.0.2").To4(), node.IP())
}
func TestChaosModeCheckRPCClientsUpstreamURL(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, `{
"id": 1,
"jsonrpc": "2.0",
"result": 1
}`)
}))
defer ts.Close()
config := params.NodeConfig{
NoDiscovery: true,
ListenAddr: "127.0.0.1:0",
UpstreamConfig: params.UpstreamRPCConfig{
Enabled: true,
// put "infura.io" substring to simulate blocking an actual infura.io URLs
URL: ts.URL + "?actualURL=infura.io",
},
}
n := New()
require.NoError(t, n.Start(&config))
defer func() { require.NoError(t, n.Stop()) }()
require.NotNil(t, n.RPCClient())
client := n.RPCClient()
require.NotNil(t, client)
err := client.Call(nil, "net_version")
require.NoError(t, err)
// act
err = n.ChaosModeCheckRPCClientsUpstreamURL(true)
require.NoError(t, err)
// assert
err = client.Call(nil, "net_version")
require.EqualError(t, err, `500 Internal Server Error "500 Internal Server Error"`)
// act
err = n.ChaosModeCheckRPCClientsUpstreamURL(false)
require.NoError(t, err)
// assert
err = client.Call(nil, "net_version")
require.NoError(t, err)
}

View File

@ -31,6 +31,8 @@ type Handler func(context.Context, ...interface{}) (interface{}, error)
// scheme. It automatically decides where RPC call
// goes - Upstream or Local node.
type Client struct {
sync.RWMutex
upstreamEnabled bool
upstreamURL string
@ -72,6 +74,25 @@ func NewClient(client *gethrpc.Client, upstream params.UpstreamRPCConfig) (*Clie
return &c, nil
}
// UpdateUpstreamURL changes the upstream RPC client URL, if the upstream is enabled.
func (c *Client) UpdateUpstreamURL(url string) error {
if c.upstream == nil {
return nil
}
rpcClient, err := gethrpc.Dial(url)
if err != nil {
return err
}
c.Lock()
c.upstream = rpcClient
c.upstreamURL = url
c.Unlock()
return nil
}
// Call performs a JSON-RPC call with the given arguments and unmarshals into
// result if no error occurred.
//
@ -118,7 +139,10 @@ func (c *Client) CallContextIgnoringLocalHandlers(ctx context.Context, result in
}
if c.router.routeRemote(method) {
return c.upstream.CallContext(ctx, result, method, args...)
c.RLock()
client := c.upstream
c.RUnlock()
return client.CallContext(ctx, result, method, args...)
}
return c.local.CallContext(ctx, result, method, args...)

View File

@ -76,3 +76,41 @@ func TestBlockedRoutesRawCall(t *testing.T) {
require.Contains(t, rawResult, fmt.Sprintf(`{"code":-32700,"message":"%s"}`, ErrMethodNotFound))
}
}
func TestUpdateUpstreamURL(t *testing.T) {
ts := createTestServer("")
defer ts.Close()
updatedUpstreamTs := createTestServer("")
defer updatedUpstreamTs.Close()
gethRPCClient, err := gethrpc.Dial(ts.URL)
require.NoError(t, err)
c, err := NewClient(gethRPCClient, params.UpstreamRPCConfig{Enabled: true, URL: ts.URL})
require.NoError(t, err)
require.Equal(t, ts.URL, c.upstreamURL)
// cache the original upstream client
originalUpstreamClient := c.upstream
err = c.UpdateUpstreamURL(updatedUpstreamTs.URL)
require.NoError(t, err)
// the upstream cleint instance should change
require.NotEqual(t, originalUpstreamClient, c.upstream)
require.Equal(t, updatedUpstreamTs.URL, c.upstreamURL)
}
func createTestServer(resp string) *httptest.Server {
if resp == "" {
resp = `{
"id": 1,
"jsonrpc": "2.0",
"result": "0x234234e22b9ffc2387e18636e0534534a3d0c56b0243567432453264c16e78a2adc"
}`
}
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, resp)
}))
}