From 5eea713f5727fd9b5b95772d362ea6df6c26c25c Mon Sep 17 00:00:00 2001 From: raina Date: Wed, 20 Mar 2024 11:45:31 +0800 Subject: [PATCH 1/2] cicd: clean dockerfile --- Dockerfile | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index a53a327..0b7cb58 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,10 +11,6 @@ RUN apk add --no-cache build-base git bash linux-headers eudev-dev curl ca-certi WORKDIR /build COPY . . -ARG GH_TOKEN="" -RUN go env -w GOPRIVATE="github.com/bnb-chain/*" -RUN git config --global url."https://${GH_TOKEN}@github.com".insteadOf "https://github.com" - RUN go mod tidy RUN go build -o .build/sentry ./cmd From 44e383819235ee9e76a6966785d3b5ce83af7837 Mon Sep 17 00:00:00 2001 From: irrun Date: Mon, 1 Apr 2024 14:03:47 +0800 Subject: [PATCH 2/2] feat: feat: take pay bid tx as block identifier (#6) fix: NPE and chainID (#7) --- README.md | 62 +++++++++++++- account/account.go | 94 +++++++++++---------- cmd/main.go | 18 ++--- config/config.go | 3 - configs/config-example.toml | 38 ++++----- gin/panic_recovery.go | 7 +- go.mod | 6 +- go.sum | 19 +++-- metrics/metrics.go | 35 ++++++++ node/builder.go | 4 +- node/node.go | 73 ----------------- node/validator.go | 157 ++++++++++++++++++++++++++++++++---- service/error.go | 25 ++++++ service/sentry.go | 124 ++++++++++++++++++---------- 14 files changed, 432 insertions(+), 233 deletions(-) create mode 100644 metrics/metrics.go delete mode 100644 node/node.go create mode 100644 service/error.go diff --git a/README.md b/README.md index a936723..7976cb3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,62 @@ # BSC-MEV-Sentry -BSC-MEV-Sentry is a proxy service within the BSC MEV architecture designed to shield the validator network. -It functions to forward requests and pay to builders. +BSC-MEV-Sentry serves as the proxy service for BSC MEV architecture, It has the following features: + +1. Forward RPC requests: mev_sendBid, mev_params, mev_running, mev_bestBidGasFee to validators. +2. Forward RPC request: mev_reportIssue to builders. +3. Pay builders on behalf of validators for their bids. +4. Monitor validators' status and health. + +See also: https://github.com/bnb-chain/BEPs/pull/322 + +For the details of mev_params, here are some notices: + +1. The builder can call mev_params to obtain the delayLeftOver and bidSimulationLeftOver time settings, and then call + the [BidBetterBefore](https://github.com/bnb-chain/bsc/blob/master/common/bidutil/bidutil.go) to calculate the + deadline for sending the bid. +2. The builder can call mev_params to obtain the gasCeil of the validator, to generate a valid header in the block + building settlement. +3. The builder can call mev_params to obtain the builderFeeCeil of the validator, to help to decide the builder fee. + +# Usage + +1. `make build` +2. `.build/sentry -config ./configs/config.toml` + +Sentry settings are configured in the `config.toml` file. The following is an example of a `config.toml` file: + +``` +[Service] +HTTPListenAddr = "localhost:8555" # The address to listen on for HTTP requests. +RPCConcurrency = 100 # The maximum number of concurrent requests. +RPCTimeout = "10s" # The timeout for RPC requests. + +[[Validators]] # A list of validators to forward requests to. +PrivateURL = "https://bsc-fuji" # The private rpc url of the validator, it can only been accessed in the local network. +PublicHostName = "bsc-fuji" # The domain name of the validator, if a request's HOST info is same with this, it will be forwarded to the validator. +PayAccountMode = "privateKey" # The unlock mode of the pay bid account. +PrivateKey = "59ba8068eb256d520...2bd306e1bd603fdb8c8da10e8" # The private key of the pay bid account. + +[[Validators]] +PrivateURL = "https://bsc-mathwallet" +PublicHostName = "bsc-mathwallet" +PayAccountMode = "keystore" +KeystorePath = "./keystore" # The keystore file path of the pay bid account. +PasswordFilePath = "./password.txt" # The path of the pay bid account's password file. +PayAccountAddress = "0x12c86Bf9...845B98F23" # The address of the pay bid account. + +[[Validators]] +PrivateURL = "https://bsc-trustwallet" +PublicHostName = "bsc-trustwallet" +PayAccountMode = "privateKey" # The unlock mode of the pay account. +PrivateKey = "59ba8068eb...d306e1bd603fdb8c8da10e8" # The private key of the pay account. + +[[Builders]] +Address = "0x45EbEBe8...664D59c12" # The address of the builder. +URL = "http://bsc-builder-1" # The public URL of the builder. + +[[Builders]] +Address = "0x980A75eC...fc9b863D5" +URL = "http://bsc-builder-2" + +``` diff --git a/account/account.go b/account/account.go index f52d273..5c9b755 100644 --- a/account/account.go +++ b/account/account.go @@ -1,10 +1,11 @@ package account import ( - "context" "crypto/ecdsa" "errors" "math/big" + "os" + "strings" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/keystore" @@ -13,7 +14,6 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/bnb-chain/bsc-mev-sentry/log" - "github.com/bnb-chain/bsc-mev-sentry/node" ) type Mode string @@ -24,7 +24,8 @@ const ( ) type Account interface { - PayBidTx(context.Context, node.FullNode, common.Address, *big.Int) ([]byte, error) + Address() common.Address + SignTx(tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) } func New(config *Config) (Account, error) { @@ -32,9 +33,9 @@ func New(config *Config) (Account, error) { case privateKeyMode: return newPrivateKeyAccount(config.PrivateKey) case keystoreMode: - return newKeystoreAccount(config.KeystorePath, config.Password, config.Address) + return newKeystoreAccount(config.KeystorePath, config.PasswordFilePath, config.Address) default: - return nil, errors.New("invalid account mode") + return nil, errors.New("invalid pay account mode") } } @@ -44,60 +45,64 @@ type Config struct { PrivateKey string // KeystorePath path of keystore KeystorePath string - // Password keystore password - Password string + // PasswordFilePath stores keystore password + PasswordFilePath string // Address public address of sentry wallet Address string } +type baseAccount struct { + address common.Address +} + +func (a *baseAccount) Address() common.Address { + return a.address +} + type keystoreAccount struct { keystore *keystore.KeyStore account accounts.Account + *baseAccount } -func newKeystoreAccount(keystorePath, password, opAccount string) (*keystoreAccount, error) { +func newKeystoreAccount(keystorePath, passwordFilePath, opAccount string) (*keystoreAccount, error) { + address := common.HexToAddress(opAccount) ks := keystore.NewKeyStore(keystorePath, keystore.StandardScryptN, keystore.StandardScryptP) - account, err := ks.Find(accounts.Account{Address: common.HexToAddress(opAccount)}) + account, err := ks.Find(accounts.Account{Address: address}) if err != nil { log.Errorw("failed to create key store account", "err", err) return nil, err } + password := MakePasswordFromPath(passwordFilePath) + err = ks.Unlock(account, password) if err != nil { log.Errorw("failed to unlock account", "err", err) return nil, err } - return &keystoreAccount{ks, account}, nil -} - -func (k *keystoreAccount) PayBidTx(ctx context.Context, fullNode node.FullNode, receiver common.Address, amount *big.Int) ([]byte, error) { - nonce, err := fetchNonce(ctx, fullNode, k.account.Address) + err = os.Remove(passwordFilePath) if err != nil { - return nil, err + log.Errorw("failed to remove password file", "err", err) } - tx := types.NewTx(&types.LegacyTx{ - Nonce: nonce, - GasPrice: big.NewInt(0), - Gas: 25000, - To: &receiver, - Value: amount, - }) + return &keystoreAccount{ks, account, &baseAccount{address: address}}, nil +} - signedTx, err := k.keystore.SignTx(k.account, tx, fullNode.ChainID()) +func (k *keystoreAccount) SignTx(tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + signedTx, err := k.keystore.SignTx(k.account, tx, chainID) if err != nil { log.Errorw("failed to sign tx", "err", err) return nil, err } - return signedTx.MarshalBinary() + return signedTx, nil } type privateKeyAccount struct { - key *ecdsa.PrivateKey - address common.Address + key *ecdsa.PrivateKey + *baseAccount } func newPrivateKeyAccount(privateKey string) (*privateKeyAccount, error) { @@ -116,38 +121,31 @@ func newPrivateKeyAccount(privateKey string) (*privateKeyAccount, error) { addr := crypto.PubkeyToAddress(*pubKeyECDSA) - return &privateKeyAccount{key, addr}, nil + return &privateKeyAccount{key, &baseAccount{address: addr}}, nil } -func (p *privateKeyAccount) PayBidTx(ctx context.Context, fullNode node.FullNode, receiver common.Address, amount *big.Int) ([]byte, error) { - nonce, err := fetchNonce(ctx, fullNode, p.address) - if err != nil { - return nil, err - } - - tx := types.NewTx(&types.LegacyTx{ - Nonce: nonce, - GasPrice: big.NewInt(0), - Gas: 25000, - To: &receiver, - Value: amount, - }) - - signedTx, err := types.SignTx(tx, types.LatestSignerForChainID(fullNode.ChainID()), p.key) +func (p *privateKeyAccount) SignTx(tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + signedTx, err := types.SignTx(tx, types.LatestSignerForChainID(chainID), p.key) if err != nil { log.Errorw("failed to sign tx", "err", err) return nil, err } - return signedTx.MarshalBinary() + return signedTx, nil } -func fetchNonce(ctx context.Context, fullNode node.FullNode, address common.Address) (uint64, error) { - nonce, err := fullNode.PendingNonceAt(ctx, address) +func MakePasswordFromPath(path string) string { + if path == "" { + return "" + } + text, err := os.ReadFile(path) if err != nil { - log.Errorw("failed to get nonce", "err", err) - return 0, err + log.Panicw("failed to read password file: %v", err) + } + lines := strings.Split(string(text), "\n") + if len(lines) == 0 { + return "" } - return nonce, err + return strings.TrimRight(lines[0], "\r") } diff --git a/cmd/main.go b/cmd/main.go index b5ae975..f74dfbc 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -12,7 +12,6 @@ import ( "github.com/gin-gonic/gin" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/bnb-chain/bsc-mev-sentry/account" "github.com/bnb-chain/bsc-mev-sentry/config" ginutils "github.com/bnb-chain/bsc-mev-sentry/gin" "github.com/bnb-chain/bsc-mev-sentry/log" @@ -40,14 +39,9 @@ func main() { log.Infow("bsc mev-sentry start", "configPath", *configPath, "config", cfg) - acc, err := account.New(&cfg.Account) - if err != nil { - log.Panicw("failed to create account", "err", err) - } - validators := make(map[string]node.Validator) for _, v := range cfg.Validators { - validator := node.NewValidator(&v) + validator := node.NewValidator(v) if validator != nil { validators[v.PublicHostName] = validator } @@ -55,17 +49,15 @@ func main() { builders := make(map[common.Address]node.Builder) for _, b := range cfg.Builders { - builder := node.NewBuilder(&b) + builder := node.NewBuilder(b) if builder != nil { builders[b.Address] = builder } } - fullNode := node.NewFullNode(&cfg.FullNode) - rpcServer := rpc.NewServer() - sentryService := service.NewMevSentry(&cfg.Service, acc, validators, builders, fullNode) - if err = rpcServer.RegisterName("mev", sentryService); err != nil { + sentryService := service.NewMevSentry(&cfg.Service, validators, builders) + if err := rpcServer.RegisterName("mev", sentryService); err != nil { panic(err) } @@ -78,7 +70,7 @@ func main() { app.POST("/", gin.WrapH(rpcServer)) - if err = app.Run(cfg.Service.HTTPListenAddr); err != nil { + if err := app.Run(cfg.Service.HTTPListenAddr); err != nil { log.Errorf("fail to run rpc server, err:%v", err) } } diff --git a/config/config.go b/config/config.go index 1837bbb..4dd7806 100644 --- a/config/config.go +++ b/config/config.go @@ -9,17 +9,14 @@ import ( "github.com/naoina/toml" - "github.com/bnb-chain/bsc-mev-sentry/account" "github.com/bnb-chain/bsc-mev-sentry/node" "github.com/bnb-chain/bsc-mev-sentry/service" ) type Config struct { Service service.Config - Account account.Config Validators []node.ValidatorConfig Builders []node.BuilderConfig - FullNode node.FullNodeConfig Debug DebugConfig Log LogConfig diff --git a/configs/config-example.toml b/configs/config-example.toml index a3fdc64..387407b 100644 --- a/configs/config-example.toml +++ b/configs/config-example.toml @@ -1,28 +1,24 @@ [Service] -HTTPListenAddr = "localhost:3000" -RPCConcurrency = 10 -RPCTimeout = "10s" +HTTPListenAddr = "localhost:8555" # The address to listen on for HTTP requests. +RPCConcurrency = 100 # The maximum number of concurrent requests. +RPCTimeout = "10s" # The timeout for RPC requests. -[Account] -Mode = "keystore" -KeystorePath = "./keystore" -Password = "sentry" -Address = "0x837060bd423eFcDd5B7b6B92aB3CFc74B9CD0df4" +[[Validators]] +PrivateURL = "http://10.200.31.36:8545" +PublicHostName = "bsc-testnet-elbrus.bnbchain.org" +PayAccountMode = "privateKey" +PrivateKey = "b1fed931ad50...34796ddbee68a53cf" [[Validators]] -PrivateURL = "http://127.0.0.1:8546" -PublicHostName = "127.0.0.1" +PrivateURL = "http://10.200.33.92:8545" +PublicHostName = "bsc-testnet-ararat.bnbchain.org" +PayAccountMode = "privateKey" +PrivateKey = "ce3f1b757384...755f66f647503" [[Builders]] -Address = "0x837060bd423eFcDd5B7b6B92aB3CFc74B9CD0df4" -URL = "http://localhost:8555" - -[FullNode] -URL = "http://localhost:8545" +Address = "0x980A75eCd1309eA12fa2ED87A8744fBfc9b863D5" # The address of the builder. +URL = "http://bsc-builder-1" # The public URL of the builder. -[Debug] -ListenAddr = "localhost:8090" - -[Log] -RootDir = "./logs" -Level = "debug" \ No newline at end of file +[[Builders]] +Address = "0x980A75eCd1309eA12fa2ED87A8744fBfc9b863D5" +URL = "http://bsc-builder-2" \ No newline at end of file diff --git a/gin/panic_recovery.go b/gin/panic_recovery.go index 0edecd3..53f3222 100644 --- a/gin/panic_recovery.go +++ b/gin/panic_recovery.go @@ -3,7 +3,6 @@ package middlewares import ( "bytes" "fmt" - "log" "net" "net/http" "net/http/httputil" @@ -12,6 +11,8 @@ import ( "strings" "github.com/gin-gonic/gin" + + "github.com/bnb-chain/bsc-mev-sentry/log" ) var ( @@ -49,9 +50,9 @@ func PanicRecovery() gin.HandlerFunc { } headersToStr := strings.Join(headers, "\r\n") if brokenPipe { - log.Println("broken pipe", "err", err, "headers", headersToStr) + log.Errorw("broken pipe", "err", err, "headers", headersToStr) } else { - log.Println("panic recovered", "err", err, "headers", headersToStr, "stack", stack) + log.Errorw("panic recovered", "err", err, "headers", headersToStr, "stack", stack) } if brokenPipe { diff --git a/go.mod b/go.mod index 6384d85..7760610 100644 --- a/go.mod +++ b/go.mod @@ -64,7 +64,7 @@ require ( github.com/goccy/go-json v0.10.2 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/uuid v1.4.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect @@ -144,7 +144,7 @@ require ( golang.org/x/tools v0.16.0 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/grpc v1.56.3 // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/protobuf v1.33.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 @@ -153,7 +153,7 @@ require ( replace ( github.com/btcsuite/btcd => github.com/btcsuite/btcd v0.23.0 github.com/cometbft/cometbft => github.com/bnb-chain/greenfield-tendermint v0.0.0-20230417032003-4cda1f296fb2 - github.com/ethereum/go-ethereum => github.com/irrun/bsc v0.0.0-20240307063536-491ddfc5f814 + github.com/ethereum/go-ethereum => github.com/bnb-chain/bsc v1.4.1-alpha.0.20240401053830-3b7ee60e140b github.com/grpc-ecosystem/grpc-gateway/v2 => github.com/prysmaticlabs/grpc-gateway/v2 v2.3.1-0.20210702154020-550e1cd83ec1 github.com/syndtr/goleveldb v1.0.1 => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 github.com/tendermint/tendermint => github.com/bnb-chain/tendermint v0.31.16 diff --git a/go.sum b/go.sum index ef7a605..5bf34c7 100644 --- a/go.sum +++ b/go.sum @@ -938,6 +938,9 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bnb-chain/bsc v1.4.1-alpha.0.20240401053830-3b7ee60e140b h1:RuqsAETd6txMpI0YS5HSIhkT04pXfonyYlL4WncXJZY= +github.com/bnb-chain/bsc v1.4.1-alpha.0.20240401053830-3b7ee60e140b/go.mod h1:r9/LuIlntgKKPG0ztTHdX2Env53Vci323MQivBOSR3Q= +github.com/bnb-chain/fastssz v0.1.2/go.mod h1:KcabV+OEw2QwgyY8Fc88ZG79CKYkFdu0kKWyfA3dI6o= github.com/bnb-chain/greenfield-tendermint v0.0.0-20230417032003-4cda1f296fb2 h1:jubavYCs/mCFj/g6Utl+l4SfpykdBdWJFPsvb9FcEXU= github.com/bnb-chain/greenfield-tendermint v0.0.0-20230417032003-4cda1f296fb2/go.mod h1:9q11eHNRY9FDwFH+4pompzPNGv//Z3VcfvkELaHJPMs= github.com/bnb-chain/ics23 v0.1.0 h1:DvjGOts2FBfbxB48384CYD1LbcrfjThFz8kowY/7KxU= @@ -1430,8 +1433,9 @@ github.com/ferranbt/fastssz v0.0.0-20210905181407-59cf6761a7d5 h1:6dVcS0LktRSyEE github.com/ferranbt/fastssz v0.0.0-20210905181407-59cf6761a7d5/go.mod h1:S8yiDeAXy8f88W4Ul+0dBMPx49S05byYbmZD6Uv94K4= github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI= github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY= -github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA= +github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA= github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= @@ -1716,8 +1720,9 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -2013,8 +2018,9 @@ github.com/herumi/bls-eth-go-binary v0.0.0-20210130185500-57372fb27371/go.mod h1 github.com/herumi/bls-eth-go-binary v0.0.0-20210917013441-d37c07cfda4e h1:wCMygKUQhmcQAjlk2Gquzq6dLmyMv2kF+llRspoRgrk= github.com/herumi/bls-eth-go-binary v0.0.0-20210917013441-d37c07cfda4e/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= -github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7 h1:3JQNjnMRil1yD0IfZKHF9GxxWKDJGj8I0IqOUol//sw= github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= +github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 h1:X4egAf/gcS1zATw6wn4Ej8vjuVGxeHdan+bRb2ebyv4= +github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/goevmlab v0.0.0-20221201133036-b31966a5267d/go.mod h1:tQJ4EfAokPShVDyEwKslIWKyt0qA/z8u+iK3kAwO424= @@ -2107,8 +2113,6 @@ github.com/iris-contrib/jade v1.1.4/go.mod h1:EDqR+ur9piDl6DUgs6qRrlfzmlx/D5Uybo github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA= -github.com/irrun/bsc v0.0.0-20240307063536-491ddfc5f814 h1:SNtLl+uHpc1BH7cfIg3uMFtC8C2vUck/UX5iEEqHqxw= -github.com/irrun/bsc v0.0.0-20240307063536-491ddfc5f814/go.mod h1:GiSvQIQBNzED3cQQQxnAZSo5rfDmCWynGqSQWtPhBqc= github.com/ishidawataru/sctp v0.0.0-20191218070446-00ab2ac2db07/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= github.com/ishidawataru/sctp v0.0.0-20210226210310-f2269e66cdee/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= @@ -3100,6 +3104,7 @@ github.com/prysmaticlabs/go-bitfield v0.0.0-20210108222456-8e92c3709aa0/go.mod h github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 h1:0tVE4tdWQK9ZpYygoV7+vS6QkDvQVySboMVEIxBJmXw= github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7/go.mod h1:wmuf/mdK4VMD+jA9ThwcUKjg3a2XWM9cVfFYjDyY4j4= github.com/prysmaticlabs/gohashtree v0.0.0-20220517220438-192ee5ae6982/go.mod h1:4pWaT30XoEx1j8KNJf3TV+E3mQkaufn7mf+jRNb/Fuk= +github.com/prysmaticlabs/gohashtree v0.0.1-alpha.0.20220714111606-acbb2962fb48/go.mod h1:4pWaT30XoEx1j8KNJf3TV+E3mQkaufn7mf+jRNb/Fuk= github.com/prysmaticlabs/gohashtree v0.0.3-alpha h1:1EVinCWdb3Lorq7xn8DYQHf48nCcdAM3Vb18KsFlRWY= github.com/prysmaticlabs/gohashtree v0.0.3-alpha/go.mod h1:4pWaT30XoEx1j8KNJf3TV+E3mQkaufn7mf+jRNb/Fuk= github.com/prysmaticlabs/grpc-gateway/v2 v2.3.1-0.20210702154020-550e1cd83ec1 h1:xcu59yYL6AWWTl6jtejBfE0y8uF35fArCBeZjRlvJss= @@ -4876,8 +4881,8 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/bsm/ratelimit.v1 v1.0.0-20160220154919-db14e161995a/go.mod h1:KF9sEfUPAXdG8Oev9e99iLGnl2uJMjc5B+4y3O7x610= diff --git a/metrics/metrics.go b/metrics/metrics.go new file mode 100644 index 0000000..15105b7 --- /dev/null +++ b/metrics/metrics.go @@ -0,0 +1,35 @@ +package metrics + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +var ( + namespace = "bsc_mev_sentry" + + ApiLatencyHist = promauto.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: namespace, + Subsystem: "api", + Name: "latency", + Buckets: prometheus.ExponentialBuckets(0.01, 3, 15), + }, []string{"method"}) + + ApiErrorCounter = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Subsystem: "api", + Name: "error", + }, []string{"method", "code"}) + + AccountError = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Subsystem: "account", + Name: "error", + }, []string{"account", "message"}) + + ChainError = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: namespace, + Subsystem: "chainRPC", + Name: "error", + }) +) diff --git a/node/builder.go b/node/builder.go index aec839c..03f6061 100644 --- a/node/builder.go +++ b/node/builder.go @@ -20,7 +20,7 @@ type BuilderConfig struct { URL string } -func NewBuilder(config *BuilderConfig) Builder { +func NewBuilder(config BuilderConfig) Builder { cli, err := builderclient.DialOptions(context.Background(), config.URL, rpc.WithHTTPClient(client)) if err != nil { log.Errorw("failed to dial builder", "url", config.URL, "err", err) @@ -34,7 +34,7 @@ func NewBuilder(config *BuilderConfig) Builder { } type builder struct { - cfg *BuilderConfig + cfg BuilderConfig client *builderclient.Client } diff --git a/node/node.go b/node/node.go deleted file mode 100644 index f3fa07c..0000000 --- a/node/node.go +++ /dev/null @@ -1,73 +0,0 @@ -package node - -import ( - "context" - "math/big" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/rpc" - "github.com/go-co-op/gocron" - - "github.com/bnb-chain/bsc-mev-sentry/log" -) - -type FullNode interface { - ChainID() *big.Int - PendingNonceAt(context.Context, common.Address) (uint64, error) -} - -type FullNodeConfig struct { - URL string -} - -func NewFullNode(config *FullNodeConfig) FullNode { - cli, err := ethclient.DialOptions(context.Background(), config.URL, rpc.WithHTTPClient(client)) - if err != nil { - log.Errorw("failed to dial validator", "url", config.URL, "err", err) - return nil - } - - f := &fullNode{ - cfg: config, - client: cli, - scheduler: gocron.NewScheduler(time.UTC), - } - - if _, err := f.scheduler.Every(1).Hours().Do(func() { - f.refresh() - }); err != nil { - log.Debugw("error while setting up scheduler", "err", err) - } - - f.scheduler.StartAsync() - - return f -} - -type fullNode struct { - cfg *FullNodeConfig - client *ethclient.Client - - scheduler *gocron.Scheduler - chainID *big.Int -} - -func (f *fullNode) ChainID() *big.Int { - return f.chainID -} - -func (f *fullNode) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { - return f.client.PendingNonceAt(ctx, account) -} - -func (f *fullNode) refresh() { - chainID, err := f.client.ChainID(context.Background()) - if err != nil { - log.Errorw("failed to fetch chain id", "err", err) - return - } - - f.chainID = chainID -} diff --git a/node/validator.go b/node/validator.go index 368e42e..c396178 100644 --- a/node/validator.go +++ b/node/validator.go @@ -3,21 +3,28 @@ package node import ( "context" "crypto/tls" + "errors" "math/big" "net" "net/http" + "sync/atomic" "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/rpc" "github.com/go-co-op/gocron" + "github.com/bnb-chain/bsc-mev-sentry/account" "github.com/bnb-chain/bsc-mev-sentry/log" + "github.com/bnb-chain/bsc-mev-sentry/metrics" ) var ( + PayBidTxGasUsed = uint64(25000) + dialer = &net.Dialer{ Timeout: time.Second, KeepAlive: 60 * time.Second, @@ -42,27 +49,50 @@ type Validator interface { MevRunning() bool BestBidGasFee(ctx context.Context, parentHash common.Hash) (*big.Int, error) MevParams(ctx context.Context) (*types.MevParams, error) + BuilderFeeCeil() *big.Int + GeneratePayBidTx(ctx context.Context, builder common.Address, builderFee *big.Int) (hexutil.Bytes, error) } type ValidatorConfig struct { PrivateURL string PublicHostName string + + PayAccountMode account.Mode + // PrivateKey private key of sentry wallet + PrivateKey string + // KeystorePath path of keystore + KeystorePath string + // PasswordFilePath stores keystore password + PasswordFilePath string + // PayAccountAddress public address of sentry wallet + PayAccountAddress string } -func NewValidator(config *ValidatorConfig) Validator { +func NewValidator(config ValidatorConfig) Validator { cli, err := ethclient.DialOptions(context.Background(), config.PrivateURL, rpc.WithHTTPClient(client)) if err != nil { log.Errorw("failed to dial validator", "url", config.PrivateURL, "err", err) return nil } + acc, err := account.New(&account.Config{ + Mode: config.PayAccountMode, + PrivateKey: config.PrivateKey, + KeystorePath: config.KeystorePath, + PasswordFilePath: config.PasswordFilePath, + Address: config.PayAccountAddress}) + if err != nil { + log.Panicw("failed to create payAccount", "err", err) + } + v := &validator{ - cfg: config, - client: cli, - scheduler: gocron.NewScheduler(time.UTC), + cfg: config, + client: cli, + scheduler: gocron.NewScheduler(time.UTC), + payAccount: acc, } - if _, err := v.scheduler.Every(1).Hours().Do(func() { + if _, err := v.scheduler.Every(10).Second().Do(func() { v.refresh() }); err != nil { log.Debugw("error while setting up scheduler", "err", err) @@ -74,11 +104,16 @@ func NewValidator(config *ValidatorConfig) Validator { } type validator struct { - cfg *ValidatorConfig - client *ethclient.Client - - scheduler *gocron.Scheduler - mevRunning bool + cfg ValidatorConfig + client *ethclient.Client + payAccount account.Account + + scheduler *gocron.Scheduler + chainID atomic.Pointer[big.Int] + mevRunning uint32 + mevParams atomic.Pointer[types.MevParams] + payAccountBalance atomic.Pointer[big.Int] + payAccountNonce uint64 } func (n *validator) SendBid(ctx context.Context, args types.BidArgs) (common.Hash, error) { @@ -86,22 +121,114 @@ func (n *validator) SendBid(ctx context.Context, args types.BidArgs) (common.Has } func (n *validator) MevRunning() bool { - return n.mevRunning + return atomic.LoadUint32(&n.mevRunning) == 1 } func (n *validator) refresh() { + chainID, err := n.client.ChainID(context.Background()) + if err != nil { + metrics.ChainError.Inc() + log.Errorw("failed to fetch chainID", "url", n.cfg.PrivateURL, "err", err) + } + + if chainID != nil { + n.chainID.Store(chainID) + } + mevRunning, err := n.client.MevRunning(context.Background()) if err != nil { - log.Errorw("failed to fetch mev running status", "err", err) + metrics.ChainError.Inc() + log.Errorw("failed to fetch mev running status", "url", n.cfg.PrivateURL, "err", err) } - n.mevRunning = mevRunning + if mevRunning { + atomic.StoreUint32(&n.mevRunning, 1) + } else { + atomic.StoreUint32(&n.mevRunning, 0) + } + + balance, err := n.client.BalanceAt(context.Background(), n.payAccount.Address(), nil) + if err != nil { + metrics.ChainError.Inc() + log.Errorw("failed to fetch validator payAccount balance", "err", err) + } + + if balance != nil { + n.payAccountBalance.Store(balance) + } + + nonce, err := n.client.PendingNonceAt(context.Background(), n.payAccount.Address()) + if err != nil { + metrics.ChainError.Inc() + log.Errorw("failed to fetch validator payAccount nonce", "err", err) + } + + atomic.StoreUint64(&n.payAccountNonce, nonce) + + params, err := n.client.MevParams(context.Background()) + if err != nil { + metrics.ChainError.Inc() + log.Errorw("failed to fetch validator mev params", "err", err) + } + + if params != nil { + n.mevParams.Store(params) + } } func (n *validator) BestBidGasFee(ctx context.Context, parentHash common.Hash) (*big.Int, error) { return n.client.BestBidGasFee(ctx, parentHash) } -func (n *validator) MevParams(ctx context.Context) (*types.MevParams, error) { - return n.client.MevParams(ctx) +func (n *validator) MevParams(_ context.Context) (*types.MevParams, error) { + return n.mevParams.Load(), nil +} + +func (n *validator) BuilderFeeCeil() *big.Int { + params := n.mevParams.Load() + if params != nil { + return params.BuilderFeeCeil + } + + log.Errorw("mev params is nil, return 0 for BuilderFeeCeil", "validator", n.cfg.PublicHostName) + + return big.NewInt(0) +} + +func (n *validator) GeneratePayBidTx(_ context.Context, builder common.Address, builderFee *big.Int) (hexutil.Bytes, error) { + // take pay bid tx as block tag + var amount = big.NewInt(0) + + if builderFee != nil { + amount = builderFee + } + + if n.payAccountBalance.Load().Cmp(amount) < 0 { + metrics.AccountError.WithLabelValues(n.payAccount.Address().String(), "insufficient_balance").Inc() + log.Errorw("insufficient balance", "balance", n.payAccountBalance.Load().String(), + "builderFee", builderFee.String()) + return nil, errors.New("insufficient balance") + } + + tx := types.NewTx(&types.LegacyTx{ + Nonce: atomic.LoadUint64(&n.payAccountNonce), + GasPrice: big.NewInt(0), + Gas: PayBidTxGasUsed, + To: &builder, + Value: amount, + }) + + signedTx, err := n.payAccount.SignTx(tx, n.chainID.Load()) + if err != nil { + log.Errorw("failed to sign pay bid tx", "err", err) + return nil, err + } + + payBidTx, err := signedTx.MarshalBinary() + if err != nil { + log.Errorw("failed to marshal pay bid tx", "err", err) + return nil, err + } + + return payBidTx, nil } diff --git a/service/error.go b/service/error.go new file mode 100644 index 0000000..b578b5a --- /dev/null +++ b/service/error.go @@ -0,0 +1,25 @@ +package service + +import "errors" + +const sentryErrorCode = -38006 + +// sentryError is an API error that encompasses an invalid bid with JSON error +// code and a binary data blob. +type sentryError struct { + error + code int +} + +// ErrorCode returns the JSON error code for an invalid bid. +// See: https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal +func (e *sentryError) ErrorCode() int { + return e.code +} + +func newSentryError(message string) *sentryError { + return &sentryError{ + error: errors.New(message), + code: sentryErrorCode, + } +} diff --git a/service/sentry.go b/service/sentry.go index b2d04bb..e37880d 100644 --- a/service/sentry.go +++ b/service/sentry.go @@ -5,32 +5,20 @@ import ( "errors" "fmt" "math/big" + "strconv" "strings" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" "github.com/tredeske/u/ustrings" - "github.com/bnb-chain/bsc-mev-sentry/account" "github.com/bnb-chain/bsc-mev-sentry/log" + "github.com/bnb-chain/bsc-mev-sentry/metrics" "github.com/bnb-chain/bsc-mev-sentry/node" ) -var ( - namespace = "bsc_mev_sentry" - - apiLatencyHist = promauto.NewHistogramVec(prometheus.HistogramOpts{ - Namespace: namespace, - Subsystem: "api", - Name: "latency", - Buckets: prometheus.ExponentialBuckets(0.01, 3, 15), - }, []string{"method"}) -) - type Config struct { // HTTPListenAddr define the address sentry service listen on HTTPListenAddr string @@ -43,33 +31,35 @@ type Config struct { type MevSentry struct { timeout Duration - account account.Account validators map[string]node.Validator // hostname -> validator builders map[common.Address]node.Builder // address -> builder - fullNode node.FullNode } func NewMevSentry(cfg *Config, - account account.Account, validators map[string]node.Validator, builders map[common.Address]node.Builder, - fullNode node.FullNode) *MevSentry { +) *MevSentry { s := &MevSentry{ timeout: cfg.RPCTimeout, - account: account, validators: validators, builders: builders, - fullNode: fullNode, } return s } -func (s *MevSentry) SendBid(ctx context.Context, args types.BidArgs) (common.Hash, error) { +func (s *MevSentry) SendBid(ctx context.Context, args types.BidArgs) (bidHash common.Hash, err error) { method := "mev_sendBid" start := time.Now() defer recordLatency(method, start) defer timeoutCancel(&ctx, s.timeout)() + defer func() { + if err != nil { + if rpcErr, ok := err.(rpc.Error); ok { + metrics.ApiErrorCounter.WithLabelValues(method, strconv.Itoa(rpcErr.ErrorCode())).Inc() + } + } + }() hostname := rpc.PeerInfoFromContext(ctx).HTTP.Host if strings.Contains(hostname, ":") { @@ -79,33 +69,52 @@ func (s *MevSentry) SendBid(ctx context.Context, args types.BidArgs) (common.Has validator, ok := s.validators[hostname] if !ok { log.Errorw("validator not found", "hostname", hostname) - return common.Hash{}, errors.New("validator hostname not found") + err = types.NewInvalidBidError("validator hostname not found") + return + } + + bidFeeCeil := validator.BuilderFeeCeil() + + if args.RawBid.BuilderFee != nil && bidFeeCeil != nil { + if args.RawBid.BuilderFee.Cmp(bidFeeCeil) > 0 { + log.Errorw("bid fee exceeds the ceiling", "fee", args.RawBid.BuilderFee, "ceiling", bidFeeCeil.Uint64()) + err = types.NewInvalidBidError(fmt.Sprintf("bid fee exceeds the ceiling %v", bidFeeCeil)) + return + } } builder, err := args.EcrecoverSender() if err != nil { log.Errorw("failed to parse bid signature", "err", err) - return common.Hash{}, types.NewInvalidBidError(fmt.Sprintf("invalid signature:%v", err)) + err = types.NewInvalidBidError(fmt.Sprintf("invalid signature:%v", err)) + return } - if args.RawBid.BuilderFee != nil && args.RawBid.BuilderFee.Cmp(big.NewInt(0)) > 0 { - payBidTx, er := s.account.PayBidTx(ctx, s.fullNode, builder, args.RawBid.BuilderFee) - if er != nil { - log.Errorw("failed to create pay bid tx", "err", err) - return common.Hash{}, err - } - - args.PayBidTx = payBidTx + payBidTx, err := validator.GeneratePayBidTx(ctx, builder, args.RawBid.BuilderFee) + if err != nil { + log.Errorw("failed to create pay bid tx", "err", err) + err = newSentryError("failed to create pay bid tx") + return } + args.PayBidTx = payBidTx + args.PayBidTxGasUsed = node.PayBidTxGasUsed + return validator.SendBid(ctx, args) } -func (s *MevSentry) BestBidGasFee(ctx context.Context, parentHash common.Hash) (*big.Int, error) { +func (s *MevSentry) BestBidGasFee(ctx context.Context, parentHash common.Hash) (fee *big.Int, err error) { method := "mev_bestBidGasFee" start := time.Now() defer recordLatency(method, start) defer timeoutCancel(&ctx, s.timeout)() + defer func() { + if err != nil { + if rpcErr, ok := err.(rpc.Error); ok { + metrics.ApiErrorCounter.WithLabelValues(method, strconv.Itoa(rpcErr.ErrorCode())).Inc() + } + } + }() hostname := rpc.PeerInfoFromContext(ctx).HTTP.Host if strings.Contains(hostname, ":") { @@ -115,17 +124,26 @@ func (s *MevSentry) BestBidGasFee(ctx context.Context, parentHash common.Hash) ( validator, ok := s.validators[hostname] if !ok { log.Errorw("validator not found", "hostname", hostname) - return nil, errors.New("validator hostname not found") + err = types.NewInvalidBidError("validator hostname not found") + return } - return validator.BestBidGasFee(ctx, parentHash) + fee, err = validator.BestBidGasFee(ctx, parentHash) + return } -func (s *MevSentry) Params(ctx context.Context) (*types.MevParams, error) { +func (s *MevSentry) Params(ctx context.Context) (param *types.MevParams, err error) { method := "mev_params" start := time.Now() defer recordLatency(method, start) defer timeoutCancel(&ctx, s.timeout)() + defer func() { + if err != nil { + if rpcErr, ok := err.(rpc.Error); ok { + metrics.ApiErrorCounter.WithLabelValues(method, strconv.Itoa(rpcErr.ErrorCode())).Inc() + } + } + }() hostname := rpc.PeerInfoFromContext(ctx).HTTP.Host if strings.Contains(hostname, ":") { @@ -135,17 +153,27 @@ func (s *MevSentry) Params(ctx context.Context) (*types.MevParams, error) { validator, ok := s.validators[hostname] if !ok { log.Errorw("validator not found", "hostname", hostname) - return nil, errors.New("validator hostname not found") + err = types.NewInvalidBidError("validator hostname not found") + return } - return validator.MevParams(ctx) + param, err = validator.MevParams(ctx) + return } -func (s *MevSentry) Running(ctx context.Context) (bool, error) { +func (s *MevSentry) Running(ctx context.Context) (running bool, err error) { method := "mev_running" start := time.Now() defer recordLatency(method, start) defer timeoutCancel(&ctx, s.timeout)() + defer timeoutCancel(&ctx, s.timeout)() + defer func() { + if err != nil { + if rpcErr, ok := err.(rpc.Error); ok { + metrics.ApiErrorCounter.WithLabelValues(method, strconv.Itoa(rpcErr.ErrorCode())).Inc() + } + } + }() hostname := rpc.PeerInfoFromContext(ctx).HTTP.Host if strings.Contains(hostname, ":") { @@ -155,17 +183,25 @@ func (s *MevSentry) Running(ctx context.Context) (bool, error) { validator, ok := s.validators[hostname] if !ok { log.Errorw("validator not found", "hostname", hostname) - return false, errors.New("validator hostname not found") + err = types.NewInvalidBidError("validator hostname not found") + return } return validator.MevRunning(), nil } -func (s *MevSentry) ReportIssue(ctx context.Context, issue types.BidIssue) error { +func (s *MevSentry) ReportIssue(ctx context.Context, issue types.BidIssue) (err error) { method := "mev_reportIssue" start := time.Now() defer recordLatency(method, start) defer timeoutCancel(&ctx, s.timeout)() + defer func() { + if err != nil { + if rpcErr, ok := err.(rpc.Error); ok { + metrics.ApiErrorCounter.WithLabelValues(method, strconv.Itoa(rpcErr.ErrorCode())).Inc() + } + } + }() var builder node.Builder var ok bool @@ -173,16 +209,18 @@ func (s *MevSentry) ReportIssue(ctx context.Context, issue types.BidIssue) error builder, ok = s.builders[issue.Builder] if !ok { log.Errorw("builder not found", "address", issue.Builder) - return errors.New("builder not found") + err = errors.New("builder not found") + return } log.Debugw("report issue", "builder", builder, "issue", issue) - return builder.ReportIssue(ctx, issue) + err = builder.ReportIssue(ctx, issue) + return } func recordLatency(method string, start time.Time) { - apiLatencyHist.WithLabelValues(method).Observe(float64(time.Since(start).Milliseconds())) + metrics.ApiLatencyHist.WithLabelValues(method).Observe(float64(time.Since(start).Milliseconds())) } func nilCancel() {