Commit ab8f1a4a authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into refactor/invariants-encoding

parents 606541f2 ebae25b4
......@@ -40,7 +40,7 @@ var (
"Should be provided in following format: <threshold>:<label>;<threshold>:<label>;..." +
"For example: -40:graylist;-20:restricted;0:nopx;20:friend;",
Required: false,
Value: "-40:graylist;-20:restricted;0:nopx;20:friend;",
Value: "-40:<=-40;-10:<=-10;-5:<=-05;-0.05:<=-00.05;0:<=0;0.05:<=00.05;5:<=05;10:<=10;20:<=20;100:>20;",
EnvVar: p2pEnv("SCORE_BANDS"),
}
......
......@@ -143,9 +143,9 @@ func (s *scorer) SnapshotHook() pubsub.ExtendedPeerScoreInspectFn {
}
if topSnap, ok := snap.Topics[blocksTopicName]; ok {
diff.Blocks.TimeInMesh = float64(topSnap.TimeInMesh) / float64(time.Second)
diff.Blocks.MeshMessageDeliveries = uint64(topSnap.MeshMessageDeliveries)
diff.Blocks.FirstMessageDeliveries = uint64(topSnap.FirstMessageDeliveries)
diff.Blocks.InvalidMessageDeliveries = uint64(topSnap.InvalidMessageDeliveries)
diff.Blocks.MeshMessageDeliveries = topSnap.MeshMessageDeliveries
diff.Blocks.FirstMessageDeliveries = topSnap.FirstMessageDeliveries
diff.Blocks.InvalidMessageDeliveries = topSnap.InvalidMessageDeliveries
}
if err := s.peerStore.SetScore(id, &diff); err != nil {
s.log.Warn("Unable to update peer gossip score", "err", err)
......
......@@ -11,9 +11,9 @@ import (
type TopicScores struct {
TimeInMesh float64 `json:"timeInMesh"` // in seconds
FirstMessageDeliveries uint64 `json:"firstMessageDeliveries"`
MeshMessageDeliveries uint64 `json:"meshMessageDeliveries"`
InvalidMessageDeliveries uint64 `json:"invalidMessageDeliveries"`
FirstMessageDeliveries float64 `json:"firstMessageDeliveries"`
MeshMessageDeliveries float64 `json:"meshMessageDeliveries"`
InvalidMessageDeliveries float64 `json:"invalidMessageDeliveries"`
}
type GossipScores struct {
......
......@@ -14,6 +14,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
......@@ -379,6 +380,13 @@ func SetBalance(addr common.Address, amount *big.Int) HeadFn {
}
}
func SetCode(addr common.Address, code hexutil.Bytes) HeadFn {
return func(headState *state.StateDB) error {
headState.SetCode(addr, code)
return nil
}
}
func SetNonce(addr common.Address, nonce uint64) HeadFn {
return func(headState *state.StateDB) error {
headState.SetNonce(addr, nonce)
......
......@@ -12,6 +12,7 @@ import (
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rpc"
......@@ -179,6 +180,10 @@ func addrFlag(name string, usage string) cli.GenericFlag {
return textFlag[*common.Address](name, usage, new(common.Address))
}
func bytesFlag(name string, usage string) cli.GenericFlag {
return textFlag[*hexutil.Bytes](name, usage, new(hexutil.Bytes))
}
func hashFlag(name string, usage string) cli.GenericFlag {
return textFlag[*common.Hash](name, usage, new(common.Hash))
}
......@@ -191,6 +196,10 @@ func addrFlagValue(name string, ctx *cli.Context) common.Address {
return *ctx.Generic(name).(*TextFlag[*common.Address]).Value
}
func bytesFlagValue(name string, ctx *cli.Context) hexutil.Bytes {
return *ctx.Generic(name).(*TextFlag[*hexutil.Bytes]).Value
}
func hashFlagValue(name string, ctx *cli.Context) common.Hash {
return *ctx.Generic(name).(*TextFlag[*common.Hash]).Value
}
......@@ -271,6 +280,17 @@ var (
return ch.RunAndClose(cheat.SetBalance(addrFlagValue("address", ctx), bigFlagValue("balance", ctx)))
}),
}
CheatSetCodeCmd = cli.Command{
Name: "code",
Flags: []cli.Flag{
DataDirFlag,
addrFlag("address", "Address to change code of"),
bytesFlag("code", "New code of the account"),
},
Action: CheatAction(false, func(ctx *cli.Context, ch *cheat.Cheater) error {
return ch.RunAndClose(cheat.SetCode(addrFlagValue("address", ctx), bytesFlagValue("code", ctx)))
}),
}
CheatSetNonceCmd = cli.Command{
Name: "nonce",
Flags: []cli.Flag{
......@@ -440,6 +460,7 @@ var CheatCmd = cli.Command{
Subcommands: []cli.Command{
CheatStorageCmd,
CheatSetBalanceCmd,
CheatSetCodeCmd,
CheatSetNonceCmd,
CheatOvmOwnersCmd,
CheatPrintHeadBlock,
......
......@@ -97,3 +97,11 @@ RUN echo "downloading and verifying Codecov uploader" && \
cp codecov /usr/local/bin/codecov && \
chmod +x /usr/local/bin/codecov && \
rm codecov
RUN echo "downloading mockery tool" && \
mkdir -p mockery-tmp-dir && \
curl -o mockery-tmp-dir/mockery.tar.gz -sL https://github.com/vektra/mockery/releases/download/v2.28.1/mockery_2.28.1_Linux_x86_64.tar.gz && \
tar -xzvf mockery-tmp-dir/mockery.tar.gz -C mockery-tmp-dir && \
cp mockery-tmp-dir/mockery /usr/local/bin/mockery && \
chmod +x /usr/local/bin/mockery && \
rm -rf mockery-tmp-dir
......@@ -71,6 +71,7 @@ The following request methods are rewritten:
* `eth_getBlockByNumber`
* `eth_getTransactionByBlockNumberAndIndex`
* `eth_getUncleByBlockNumberAndIndex`
* `debug_getRawReceipts`
And `eth_blockNumber` response is overridden with current block consensus.
......@@ -86,7 +87,7 @@ Cache use Redis and can be enabled for the following immutable methods:
* `eth_getBlockByHash`
* `eth_getTransactionByBlockHashAndIndex`
* `eth_getUncleByBlockHashAndIndex`
* `debug_getRawReceipts` (block hash only)
## Metrics
......
......@@ -2,9 +2,12 @@ package proxyd
import (
"context"
"encoding/json"
"strings"
"time"
"github.com/ethereum/go-ethereum/rpc"
"github.com/go-redis/redis/v8"
"github.com/golang/snappy"
lru "github.com/hashicorp/golang-lru"
......@@ -124,6 +127,21 @@ type rpcCache struct {
func newRPCCache(cache Cache) RPCCache {
staticHandler := &StaticMethodHandler{cache: cache}
debugGetRawReceiptsHandler := &StaticMethodHandler{cache: cache,
filter: func(req *RPCReq) bool {
// cache only if the request is for a block hash
var p []rpc.BlockNumberOrHash
err := json.Unmarshal(req.Params, &p)
if err != nil {
return false
}
if len(p) != 1 {
return false
}
return p[0].BlockHash != nil
},
}
handlers := map[string]RPCMethodHandler{
"eth_chainId": staticHandler,
"net_version": staticHandler,
......@@ -132,6 +150,7 @@ func newRPCCache(cache Cache) RPCCache {
"eth_getBlockByHash": staticHandler,
"eth_getTransactionByBlockHashAndIndex": staticHandler,
"eth_getUncleByBlockHashAndIndex": staticHandler,
"debug_getRawReceipts": debugGetRawReceiptsHandler,
}
return &rpcCache{
cache: cache,
......
......@@ -101,6 +101,20 @@ func TestRPCCacheImmutableRPCs(t *testing.T) {
},
name: "eth_getUncleByBlockHashAndIndex",
},
{
req: &RPCReq{
JSONRPC: "2.0",
Method: "debug_getRawReceipts",
Params: mustMarshalJSON([]string{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"}),
ID: ID,
},
res: &RPCRes{
JSONRPC: "2.0",
Result: `{"debug_getRawReceipts":"!"}`,
ID: ID,
},
name: "debug_getRawReceipts",
},
}
for _, rpc := range rpcs {
......@@ -173,6 +187,15 @@ func TestRPCCacheUnsupportedMethod(t *testing.T) {
ID: ID,
},
},
{
req: &RPCReq{
JSONRPC: "2.0",
Method: "debug_getRawReceipts",
Params: mustMarshalJSON([]string{"0x100"}),
ID: ID,
},
name: "debug_getRawReceipts",
},
}
for _, rpc := range rpcs {
......
......@@ -8,6 +8,7 @@ import (
"os"
"path"
"testing"
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
......@@ -630,6 +631,55 @@ func TestConsensus(t *testing.T) {
require.Equal(t, len(nodes["node2"].mockBackend.Requests()), 0, msg)
})
t.Run("load balancing should not hit if node is degraded", func(t *testing.T) {
reset()
useOnlyNode1()
// replace node1 handler with one that adds a 500ms delay
oldHandler := nodes["node1"].mockBackend.handler
defer func() { nodes["node1"].mockBackend.handler = oldHandler }()
nodes["node1"].mockBackend.SetHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(500 * time.Millisecond)
oldHandler.ServeHTTP(w, r)
}))
update()
// send 10 requests to make node1 degraded
numberReqs := 10
for numberReqs > 0 {
_, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x101", false})
require.NoError(t, err)
require.Equal(t, 200, statusCode)
numberReqs--
}
// bring back node2
nodes["node2"].handler.ResetOverrides()
update()
// reset request counts
nodes["node1"].mockBackend.Reset()
nodes["node2"].mockBackend.Reset()
require.Equal(t, 0, len(nodes["node1"].mockBackend.Requests()))
require.Equal(t, 0, len(nodes["node2"].mockBackend.Requests()))
numberReqs = 10
for numberReqs > 0 {
_, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x101", false})
require.NoError(t, err)
require.Equal(t, 200, statusCode)
numberReqs--
}
msg := fmt.Sprintf("n1 %d, n2 %d",
len(nodes["node1"].mockBackend.Requests()), len(nodes["node2"].mockBackend.Requests()))
require.Equal(t, 0, len(nodes["node1"].mockBackend.Requests()), msg)
require.Equal(t, 10, len(nodes["node2"].mockBackend.Requests()), msg)
})
t.Run("rewrite response of eth_blockNumber", func(t *testing.T) {
reset()
update()
......
......@@ -3,6 +3,7 @@ rpc_port = 8545
[backend]
response_timeout_seconds = 1
max_degraded_latency_threshold = "30ms"
[backends]
[backends.node1]
......
......@@ -17,8 +17,9 @@ type RPCMethodHandler interface {
}
type StaticMethodHandler struct {
cache Cache
m sync.RWMutex
cache Cache
m sync.RWMutex
filter func(*RPCReq) bool
}
func (e *StaticMethodHandler) key(req *RPCReq) string {
......@@ -33,6 +34,10 @@ func (e *StaticMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*R
if e.cache == nil {
return nil, nil
}
if e.filter != nil && !e.filter(req) {
return nil, nil
}
e.m.RLock()
defer e.m.RUnlock()
......@@ -62,6 +67,9 @@ func (e *StaticMethodHandler) PutRPCMethod(ctx context.Context, req *RPCReq, res
if e.cache == nil {
return nil
}
if e.filter != nil && !e.filter(req) {
return nil
}
e.m.Lock()
defer e.m.Unlock()
......
......@@ -358,6 +358,14 @@ var (
"backend_name",
})
degradedBackends = promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: MetricsNamespace,
Name: "backend_degraded",
Help: "Bool gauge for degraded backends",
}, []string{
"backend_name",
})
networkErrorRateBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: MetricsNamespace,
Name: "backend_error_rate",
......@@ -493,6 +501,7 @@ func RecordConsensusBackendUpdateDelay(b *Backend, lastUpdate time.Time) {
func RecordBackendNetworkLatencyAverageSlidingWindow(b *Backend, avgLatency time.Duration) {
avgLatencyBackend.WithLabelValues(b.Name).Set(float64(avgLatency.Milliseconds()))
degradedBackends.WithLabelValues(b.Name).Set(boolToFloat64(b.IsDegraded()))
}
func RecordBackendNetworkErrorRateSlidingWindow(b *Backend, rate float64) {
......
......@@ -63,24 +63,26 @@ func RewriteRequest(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResul
case "eth_getLogs",
"eth_newFilter":
return rewriteRange(rctx, req, res, 0)
case "debug_getRawReceipts":
return rewriteParam(rctx, req, res, 0, true)
case "eth_getBalance",
"eth_getCode",
"eth_getTransactionCount",
"eth_call":
return rewriteParam(rctx, req, res, 1)
return rewriteParam(rctx, req, res, 1, false)
case "eth_getStorageAt":
return rewriteParam(rctx, req, res, 2)
return rewriteParam(rctx, req, res, 2, false)
case "eth_getBlockTransactionCountByNumber",
"eth_getUncleCountByBlockNumber",
"eth_getBlockByNumber",
"eth_getTransactionByBlockNumberAndIndex",
"eth_getUncleByBlockNumberAndIndex":
return rewriteParam(rctx, req, res, 0)
return rewriteParam(rctx, req, res, 0, false)
}
return RewriteNone, nil
}
func rewriteParam(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int) (RewriteResult, error) {
func rewriteParam(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int, required bool) (RewriteResult, error) {
var p []interface{}
err := json.Unmarshal(req.Params, &p)
if err != nil {
......@@ -89,9 +91,9 @@ func rewriteParam(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int) (Rewri
// we assume latest if the param is missing,
// and we don't rewrite if there is not enough params
if len(p) == pos {
if len(p) == pos && !required {
p = append(p, "latest")
} else if len(p) < pos {
} else if len(p) <= pos {
return RewriteNone, nil
}
......
......@@ -148,6 +148,74 @@ func TestRewriteRequest(t *testing.T) {
expected: RewriteOverrideError,
expectedErr: ErrRewriteBlockOutOfRange,
},
/* required parameter at pos 0 */
{
name: "debug_getRawReceipts latest",
args: args{
rctx: RewriteContext{latest: hexutil.Uint64(100)},
req: &RPCReq{Method: "debug_getRawReceipts", Params: mustMarshalJSON([]string{"latest"})},
res: nil,
},
expected: RewriteOverrideRequest,
check: func(t *testing.T, args args) {
var p []string
err := json.Unmarshal(args.req.Params, &p)
require.Nil(t, err)
require.Equal(t, 1, len(p))
require.Equal(t, hexutil.Uint64(100).String(), p[0])
},
},
{
name: "debug_getRawReceipts within range",
args: args{
rctx: RewriteContext{latest: hexutil.Uint64(100)},
req: &RPCReq{Method: "debug_getRawReceipts", Params: mustMarshalJSON([]string{hexutil.Uint64(55).String()})},
res: nil,
},
expected: RewriteNone,
check: func(t *testing.T, args args) {
var p []string
err := json.Unmarshal(args.req.Params, &p)
require.Nil(t, err)
require.Equal(t, 1, len(p))
require.Equal(t, hexutil.Uint64(55).String(), p[0])
},
},
{
name: "debug_getRawReceipts out of range",
args: args{
rctx: RewriteContext{latest: hexutil.Uint64(100)},
req: &RPCReq{Method: "debug_getRawReceipts", Params: mustMarshalJSON([]string{hexutil.Uint64(111).String()})},
res: nil,
},
expected: RewriteOverrideError,
expectedErr: ErrRewriteBlockOutOfRange,
},
{
name: "debug_getRawReceipts missing parameter",
args: args{
rctx: RewriteContext{latest: hexutil.Uint64(100)},
req: &RPCReq{Method: "debug_getRawReceipts", Params: mustMarshalJSON([]string{})},
res: nil,
},
expected: RewriteNone,
},
{
name: "debug_getRawReceipts with block hash",
args: args{
rctx: RewriteContext{latest: hexutil.Uint64(100)},
req: &RPCReq{Method: "debug_getRawReceipts", Params: mustMarshalJSON([]string{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"})},
res: nil,
},
expected: RewriteNone,
check: func(t *testing.T, args args) {
var p []string
err := json.Unmarshal(args.req.Params, &p)
require.Nil(t, err)
require.Equal(t, 1, len(p))
require.Equal(t, "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", p[0])
},
},
/* default block parameter */
{
name: "eth_getCode omit block, should add",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment