From 6ac8c78a0085c7707840b896e3414f523146f878 Mon Sep 17 00:00:00 2001 From: Jacob Shufro Date: Thu, 12 Jun 2025 18:51:19 -0400 Subject: [PATCH 1/6] Support ssz-formatted register_validator calls --- go.mod | 4 +- go.sum | 8 +- guarded-beacon-proxy.go | 4 + http.go | 29 ++++- http_test.go | 95 +++++++++++++++- types.go => jsontypes/types.go | 10 +- ssz/types.go | 52 +++++++++ ssz/types_encoding.go | 195 +++++++++++++++++++++++++++++++++ sszgen.sh | 7 ++ 9 files changed, 394 insertions(+), 10 deletions(-) rename types.go => jsontypes/types.go (87%) create mode 100644 ssz/types.go create mode 100644 ssz/types_encoding.go create mode 100755 sszgen.sh diff --git a/go.mod b/go.mod index 2f35475..7556f5b 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.19 require ( github.com/ethereum/go-ethereum v1.12.0 + github.com/ferranbt/fastssz v0.1.4 github.com/gorilla/mux v1.8.0 github.com/mwitkow/grpc-proxy v0.0.0-20230212185441-f345521cb9c9 github.com/prysmaticlabs/prysm/v4 v4.0.6 @@ -18,6 +19,7 @@ require ( github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect + github.com/emicklei/dot v1.6.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.0.1 // indirect @@ -33,7 +35,7 @@ require ( github.com/prometheus/procfs v0.9.0 // indirect github.com/prysmaticlabs/fastssz v0.0.0-20220628121656-93dfe28febab // indirect github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 // indirect - github.com/prysmaticlabs/gohashtree v0.0.3-alpha // indirect + github.com/prysmaticlabs/gohashtree v0.0.4-beta // indirect github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e // indirect golang.org/x/crypto v0.7.0 // indirect golang.org/x/sys v0.7.0 // indirect diff --git a/go.sum b/go.sum index 4f27aed..c7f298f 100644 --- a/go.sum +++ b/go.sum @@ -60,6 +60,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= +github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= +github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -67,6 +69,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ethereum/go-ethereum v1.12.0 h1:bdnhLPtqETd4m3mS8BGMNvBTf36bO5bx/hxE2zljOa0= github.com/ethereum/go-ethereum v1.12.0/go.mod h1:/oo2X/dZLJjf2mJ6YT9wcWxa4nNJDBKDBU6sFIpx1Gs= +github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY= +github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg= github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -184,8 +188,8 @@ github.com/prysmaticlabs/fastssz v0.0.0-20220628121656-93dfe28febab h1:Y3PcvUrnn github.com/prysmaticlabs/fastssz v0.0.0-20220628121656-93dfe28febab/go.mod h1:MA5zShstUwCQaE9faGHgCGvEWUbG87p4SAXINhmCkvg= 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.3-alpha h1:1EVinCWdb3Lorq7xn8DYQHf48nCcdAM3Vb18KsFlRWY= -github.com/prysmaticlabs/gohashtree v0.0.3-alpha/go.mod h1:4pWaT30XoEx1j8KNJf3TV+E3mQkaufn7mf+jRNb/Fuk= +github.com/prysmaticlabs/gohashtree v0.0.4-beta h1:H/EbCuXPeTV3lpKeXGPpEV9gsUpkqOOVnWapUyeWro4= +github.com/prysmaticlabs/gohashtree v0.0.4-beta/go.mod h1:BFdtALS+Ffhg3lGQIHv9HDWuHS8cTvHZzrHWxwOtGOs= github.com/prysmaticlabs/prysm/v4 v4.0.6 h1:e3EyQUPonzvQF2PxxEF+GCjaOhr42eZs48PAvB66gyo= github.com/prysmaticlabs/prysm/v4 v4.0.6/go.mod h1:LjWP50kVb6UzhvkVxdV8KC0eo289XOfumomkKRMw8Sk= github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= diff --git a/guarded-beacon-proxy.go b/guarded-beacon-proxy.go index 5f40c8c..56f9c86 100644 --- a/guarded-beacon-proxy.go +++ b/guarded-beacon-proxy.go @@ -10,11 +10,15 @@ import ( "net/url" "time" + "github.com/Rocket-Rescue-Node/guarded-beacon-proxy/jsontypes" "github.com/gorilla/mux" "github.com/mwitkow/grpc-proxy/proxy" "google.golang.org/grpc" ) +type RegisterValidatorRequest = jsontypes.RegisterValidatorRequest +type PrepareBeaconProposerRequest = jsontypes.PrepareBeaconProposerRequest + // PrepareBeaconProposerGuard is a function that validates whether or not a PrepareBeaconProposer call // should be proxied. The provided Context is whatever was returned by the authenticator. type PrepareBeaconProposerGuard func(PrepareBeaconProposerRequest, context.Context) (AuthenticationStatus, error) diff --git a/http.go b/http.go index b8b0afc..35246f5 100644 --- a/http.go +++ b/http.go @@ -7,6 +7,9 @@ import ( "fmt" "io" "net/http" + "strings" + + "github.com/Rocket-Rescue-Node/guarded-beacon-proxy/ssz" ) // HTTPAuthenticator is a function type which can authenticate HTTP requests. @@ -89,9 +92,31 @@ func (gbp *GuardedBeaconProxy) registerValidator(w http.ResponseWriter, r *http. return } + // Check the content-type header + contentType := r.Header.Get("Content-Type") + contentType = strings.ToLower(contentType) + var validators RegisterValidatorRequest - if err := json.NewDecoder(buf).Decode(&validators); err != nil { - gbp.httpError(w, http.StatusBadRequest, nil) + switch contentType { + case "application/json": + if err := json.NewDecoder(buf).Decode(&validators); err != nil { + gbp.httpError(w, http.StatusBadRequest, err) + return + } + case "application/octet-stream": + // slurp the body into a buffer + data, err := io.ReadAll(buf) + if err != nil { + gbp.httpError(w, http.StatusInternalServerError, err) + return + } + + if err := ssz.ToRegisterValidatorRequest(&validators, data); err != nil { + gbp.httpError(w, http.StatusBadRequest, err) + return + } + default: + gbp.httpError(w, http.StatusBadRequest, fmt.Errorf("unsupported content type: %s", contentType)) return } diff --git a/http_test.go b/http_test.go index 9dee846..a043009 100644 --- a/http_test.go +++ b/http_test.go @@ -1,7 +1,9 @@ package guardedbeaconproxy import ( + "bytes" "context" + "encoding/hex" "fmt" "io" "net/http" @@ -11,6 +13,9 @@ import ( "testing" "time" + "github.com/Rocket-Rescue-Node/guarded-beacon-proxy/ssz" + + "github.com/ethereum/go-ethereum/common" "golang.org/x/net/nettest" "gotest.tools/assert" ) @@ -315,5 +320,93 @@ func TestGuardedUnauthedWithContext(t *testing.T) { t.Error(err) } - assertResp(t, res, ``, http.StatusBadRequest) + assertResp(t, res, `{"error":"invalid character '}' looking for beginning of value"}`, http.StatusBadRequest) +} + +func TestSSZ(t *testing.T) { + addr := common.HexToAddress("0xa111111111111111111111111111111111111111") + pubkey := "0xa55555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555" + pubkeyBytes, err := hex.DecodeString(pubkey[2:]) + if err != nil { + t.Error(err) + } + + signature := "0xa99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" + signatureBytes, err := hex.DecodeString(signature[2:]) + if err != nil { + t.Error(err) + } + + ts := testServer(handlerOK(), handlerOK(), handlerOK()) + defer ts.Close() + t.Logf("upstream listening on %s\n", ts.Listener.Addr()) + + // Set up a gbp with passthrough auth and guards + gbp, start, stop := newGbp(t, ts) + gbp.HTTPAuthenticator = func(r *http.Request) (AuthenticationStatus, context.Context, error) { + ctx := r.Context() + return Allowed, context.WithValue(ctx, testkey, "testvalue"), nil + } + gbp.RegisterValidatorGuard = func(r RegisterValidatorRequest, ctx context.Context) (AuthenticationStatus, error) { + if ctx.Value(testkey) != "testvalue" { + t.Error("context passthrough failed") + } + + assert.Equal(t, len(r), 2) + + for _, m := range r { + if m.Message.FeeRecipient != addr.Hex() { + return Conflict, fmt.Errorf("incorrect fee recipient") + } + + if m.Signature != signature { + return Conflict, fmt.Errorf("incorrect signature") + } + } + + return Allowed, nil + + } + go start(t) + defer stop() + t.Logf("proxy listening on %s\n", gbp.Addr) + + // Create a valid SSZ payload + + payload := ssz.RegisterValidatorRequest{ + ssz.SignedValidatorRegistration{ + Message: ssz.ValidatorRegistration{ + FeeRecipient: addr.Bytes(), + GasLimit: 100, + Timestamp: 1932, + Pubkey: pubkeyBytes, + }, + Signature: signatureBytes, + }, + ssz.SignedValidatorRegistration{ + Message: ssz.ValidatorRegistration{ + FeeRecipient: addr.Bytes(), + GasLimit: 100, + Timestamp: 1932, + Pubkey: pubkeyBytes, + }, + Signature: signatureBytes, + }, + } + + var buf []byte + // serialize the elements + for _, element := range payload { + sszBytes, err := element.MarshalSSZ() + if err != nil { + t.Error(err) + } + buf = append(buf, sszBytes...) + } + + res, err := http.Post("http://"+gbp.Addr+rvPath, "application/octet-stream", bytes.NewReader(buf)) + if err != nil { + t.Error(err) + } + fmt.Println(res.StatusCode) } diff --git a/types.go b/jsontypes/types.go similarity index 87% rename from types.go rename to jsontypes/types.go index e01fb4c..6a70678 100644 --- a/types.go +++ b/jsontypes/types.go @@ -1,4 +1,4 @@ -package guardedbeaconproxy +package jsontypes // PrepareBeaconProposerRequest is the in-memory representation of a // prepare_beacon_proposer API call, be it gRPC or HTTP. @@ -16,9 +16,11 @@ type RegisterValidatorMessage struct { Pubkey string `json:"pubkey"` } -// RegisterValidatorRequest is the in-memory representation of a -// register_validator API call, be it gRPC or HTTP. -type RegisterValidatorRequest []struct { +type SignedValidatorRegistration struct { Message RegisterValidatorMessage `json:"message"` Signature string `json:"signature"` } + +// RegisterValidatorRequest is the in-memory representation of a +// register_validator API call, be it gRPC or HTTP. +type RegisterValidatorRequest []SignedValidatorRegistration diff --git a/ssz/types.go b/ssz/types.go new file mode 100644 index 0000000..0c93180 --- /dev/null +++ b/ssz/types.go @@ -0,0 +1,52 @@ +package ssz + +import ( + "encoding/hex" + "fmt" + "strconv" + + "github.com/Rocket-Rescue-Node/guarded-beacon-proxy/jsontypes" +) + +type ValidatorRegistration struct { + FeeRecipient []byte `ssz-size:"20"` + GasLimit uint64 + Timestamp uint64 + Pubkey []byte `ssz-size:"48"` +} + +type SignedValidatorRegistration struct { + Message ValidatorRegistration + Signature []byte `ssz-size:"96"` +} + +type RegisterValidatorRequest []SignedValidatorRegistration + +func ToRegisterValidatorRequest(dst *jsontypes.RegisterValidatorRequest, buf []byte) error { + + // Ensure the buffer is a multiple of SignedValidatorRegistration.SizeSSZ() + size := (&SignedValidatorRegistration{}).SizeSSZ() + if len(buf)%size != 0 { + return fmt.Errorf("buffer is not a multiple of SignedValidatorRegistration length: %d", size) + } + + // Unmarshal the buffer SSZ objects + for i := 0; i < len(buf); i += size { + section := buf[i : i+size] + var signedValidatorRegistration SignedValidatorRegistration + if err := signedValidatorRegistration.UnmarshalSSZ(section); err != nil { + return err + } + *dst = append(*dst, jsontypes.SignedValidatorRegistration{ + Message: jsontypes.RegisterValidatorMessage{ + FeeRecipient: "0x" + hex.EncodeToString(signedValidatorRegistration.Message.FeeRecipient), + GasLimit: strconv.FormatUint(signedValidatorRegistration.Message.GasLimit, 10), + Timestamp: strconv.FormatUint(signedValidatorRegistration.Message.Timestamp, 10), + Pubkey: "0x" + hex.EncodeToString(signedValidatorRegistration.Message.Pubkey), + }, + Signature: hex.EncodeToString(signedValidatorRegistration.Signature), + }) + } + + return nil +} diff --git a/ssz/types_encoding.go b/ssz/types_encoding.go new file mode 100644 index 0000000..5009902 --- /dev/null +++ b/ssz/types_encoding.go @@ -0,0 +1,195 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: 7801d93bbd737deaa7776807d0e9d1a404e312faaf97989e4a46e729698692df +// Version: 0.1.3 +package ssz + +import ( + ssz "github.com/ferranbt/fastssz" +) + +// MarshalSSZ ssz marshals the ValidatorRegistration object +func (v *ValidatorRegistration) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(v) +} + +// MarshalSSZTo ssz marshals the ValidatorRegistration object to a target array +func (v *ValidatorRegistration) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + + // Field (0) 'FeeRecipient' + if size := len(v.FeeRecipient); size != 20 { + err = ssz.ErrBytesLengthFn("ValidatorRegistration.FeeRecipient", size, 20) + return + } + dst = append(dst, v.FeeRecipient...) + + // Field (1) 'GasLimit' + dst = ssz.MarshalUint64(dst, v.GasLimit) + + // Field (2) 'Timestamp' + dst = ssz.MarshalUint64(dst, v.Timestamp) + + // Field (3) 'Pubkey' + if size := len(v.Pubkey); size != 48 { + err = ssz.ErrBytesLengthFn("ValidatorRegistration.Pubkey", size, 48) + return + } + dst = append(dst, v.Pubkey...) + + return +} + +// UnmarshalSSZ ssz unmarshals the ValidatorRegistration object +func (v *ValidatorRegistration) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size != 84 { + return ssz.ErrSize + } + + // Field (0) 'FeeRecipient' + if cap(v.FeeRecipient) == 0 { + v.FeeRecipient = make([]byte, 0, len(buf[0:20])) + } + v.FeeRecipient = append(v.FeeRecipient, buf[0:20]...) + + // Field (1) 'GasLimit' + v.GasLimit = ssz.UnmarshallUint64(buf[20:28]) + + // Field (2) 'Timestamp' + v.Timestamp = ssz.UnmarshallUint64(buf[28:36]) + + // Field (3) 'Pubkey' + if cap(v.Pubkey) == 0 { + v.Pubkey = make([]byte, 0, len(buf[36:84])) + } + v.Pubkey = append(v.Pubkey, buf[36:84]...) + + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the ValidatorRegistration object +func (v *ValidatorRegistration) SizeSSZ() (size int) { + size = 84 + return +} + +// HashTreeRoot ssz hashes the ValidatorRegistration object +func (v *ValidatorRegistration) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(v) +} + +// HashTreeRootWith ssz hashes the ValidatorRegistration object with a hasher +func (v *ValidatorRegistration) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'FeeRecipient' + if size := len(v.FeeRecipient); size != 20 { + err = ssz.ErrBytesLengthFn("ValidatorRegistration.FeeRecipient", size, 20) + return + } + hh.PutBytes(v.FeeRecipient) + + // Field (1) 'GasLimit' + hh.PutUint64(v.GasLimit) + + // Field (2) 'Timestamp' + hh.PutUint64(v.Timestamp) + + // Field (3) 'Pubkey' + if size := len(v.Pubkey); size != 48 { + err = ssz.ErrBytesLengthFn("ValidatorRegistration.Pubkey", size, 48) + return + } + hh.PutBytes(v.Pubkey) + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the ValidatorRegistration object +func (v *ValidatorRegistration) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(v) +} + +// MarshalSSZ ssz marshals the SignedValidatorRegistration object +func (s *SignedValidatorRegistration) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(s) +} + +// MarshalSSZTo ssz marshals the SignedValidatorRegistration object to a target array +func (s *SignedValidatorRegistration) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + + // Field (0) 'Message' + if dst, err = s.Message.MarshalSSZTo(dst); err != nil { + return + } + + // Field (1) 'Signature' + if size := len(s.Signature); size != 96 { + err = ssz.ErrBytesLengthFn("SignedValidatorRegistration.Signature", size, 96) + return + } + dst = append(dst, s.Signature...) + + return +} + +// UnmarshalSSZ ssz unmarshals the SignedValidatorRegistration object +func (s *SignedValidatorRegistration) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size != 180 { + return ssz.ErrSize + } + + // Field (0) 'Message' + if err = s.Message.UnmarshalSSZ(buf[0:84]); err != nil { + return err + } + + // Field (1) 'Signature' + if cap(s.Signature) == 0 { + s.Signature = make([]byte, 0, len(buf[84:180])) + } + s.Signature = append(s.Signature, buf[84:180]...) + + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the SignedValidatorRegistration object +func (s *SignedValidatorRegistration) SizeSSZ() (size int) { + size = 180 + return +} + +// HashTreeRoot ssz hashes the SignedValidatorRegistration object +func (s *SignedValidatorRegistration) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(s) +} + +// HashTreeRootWith ssz hashes the SignedValidatorRegistration object with a hasher +func (s *SignedValidatorRegistration) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'Message' + if err = s.Message.HashTreeRootWith(hh); err != nil { + return + } + + // Field (1) 'Signature' + if size := len(s.Signature); size != 96 { + err = ssz.ErrBytesLengthFn("SignedValidatorRegistration.Signature", size, 96) + return + } + hh.PutBytes(s.Signature) + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the SignedValidatorRegistration object +func (s *SignedValidatorRegistration) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(s) +} diff --git a/sszgen.sh b/sszgen.sh new file mode 100755 index 0000000..403cac9 --- /dev/null +++ b/sszgen.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +# Generates the ssz encoding methods for eth2 types with fastssz +# Install sszgen with `go get github.com/ferranbt/fastssz/sszgen` +SSZGEN_CMD="go run github.com/ferranbt/fastssz/sszgen@v0.1.4" +find ./ssz -name "*_encoding.go" -exec sh -c 'head -1 {} | grep -q "Code generated by fastssz"' \; -exec rm {} \; +$SSZGEN_CMD --path ./ssz From ca3798af635c37edfdffbd3c87cc922781836020 Mon Sep 17 00:00:00 2001 From: Jacob Shufro Date: Fri, 13 Jun 2025 11:55:42 -0400 Subject: [PATCH 2/6] Use http 415 instead of 400 on bad content-type. add tests. --- http.go | 2 +- http_test.go | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/http.go b/http.go index 35246f5..bd418ec 100644 --- a/http.go +++ b/http.go @@ -116,7 +116,7 @@ func (gbp *GuardedBeaconProxy) registerValidator(w http.ResponseWriter, r *http. return } default: - gbp.httpError(w, http.StatusBadRequest, fmt.Errorf("unsupported content type: %s", contentType)) + gbp.httpError(w, http.StatusUnsupportedMediaType, fmt.Errorf("unsupported content type: %s", contentType)) return } diff --git a/http_test.go b/http_test.go index a043009..da4fa90 100644 --- a/http_test.go +++ b/http_test.go @@ -410,3 +410,69 @@ func TestSSZ(t *testing.T) { } fmt.Println(res.StatusCode) } + +func TestInvalidContentType(t *testing.T) { + ts := testServer(handlerOK(), handlerOK(), handlerOK()) + defer ts.Close() + t.Logf("upstream listening on %s\n", ts.Listener.Addr()) + + gbp, start, stop := newGbp(t, ts) + gbp.RegisterValidatorGuard = func(r RegisterValidatorRequest, ctx context.Context) (AuthenticationStatus, error) { + + return Allowed, nil + } + go start(t) + defer stop() + + res, err := http.Post("http://"+gbp.Addr+rvPath, "application/text", strings.NewReader("test")) + if err != nil { + t.Error(err) + } + + assertResp(t, res, `{"error":"unsupported content type: application/text"}`, http.StatusUnsupportedMediaType) + +} + +func TestValidContentType(t *testing.T) { + ts := testServer(handlerOK(), handlerOK(), handlerOK()) + defer ts.Close() + t.Logf("upstream listening on %s\n", ts.Listener.Addr()) + + gbp, start, stop := newGbp(t, ts) + gbp.RegisterValidatorGuard = func(r RegisterValidatorRequest, ctx context.Context) (AuthenticationStatus, error) { + + return Allowed, nil + } + go start(t) + defer stop() + + res, err := http.Post("http://"+gbp.Addr+rvPath, "application/json", strings.NewReader("[]")) + if err != nil { + t.Error(err) + } + + assertRespOK(t, res) +} + +func TestInvalidSSZ(t *testing.T) { + ts := testServer(handlerOK(), handlerOK(), handlerOK()) + defer ts.Close() + t.Logf("upstream listening on %s\n", ts.Listener.Addr()) + + gbp, start, stop := newGbp(t, ts) + gbp.RegisterValidatorGuard = func(r RegisterValidatorRequest, ctx context.Context) (AuthenticationStatus, error) { + + return Allowed, nil + } + go start(t) + defer stop() + + res, err := http.Post("http://"+gbp.Addr+rvPath, "application/octet-stream", strings.NewReader("{}")) + if err != nil { + t.Error(err) + } + + sszSize := (&ssz.SignedValidatorRegistration{}).SizeSSZ() + + assertResp(t, res, fmt.Sprintf(`{"error":"buffer is not a multiple of SignedValidatorRegistration length: %d"}`, sszSize), http.StatusBadRequest) +} From 08e5d1c05317b98b3d6df7025f1b1159348b5e42 Mon Sep 17 00:00:00 2001 From: Jacob Shufro Date: Mon, 16 Jun 2025 10:40:24 -0400 Subject: [PATCH 3/6] Fix content-type parsing to support kv pairs --- http.go | 9 ++++++--- http_test.go | 22 +++++++++++++++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/http.go b/http.go index bd418ec..eca8bf4 100644 --- a/http.go +++ b/http.go @@ -6,8 +6,8 @@ import ( "encoding/json" "fmt" "io" + "mime" "net/http" - "strings" "github.com/Rocket-Rescue-Node/guarded-beacon-proxy/ssz" ) @@ -93,8 +93,11 @@ func (gbp *GuardedBeaconProxy) registerValidator(w http.ResponseWriter, r *http. } // Check the content-type header - contentType := r.Header.Get("Content-Type") - contentType = strings.ToLower(contentType) + contentType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + if err != nil { + gbp.httpError(w, http.StatusUnsupportedMediaType, err) + return + } var validators RegisterValidatorRequest switch contentType { diff --git a/http_test.go b/http_test.go index da4fa90..4796bda 100644 --- a/http_test.go +++ b/http_test.go @@ -446,7 +446,7 @@ func TestValidContentType(t *testing.T) { go start(t) defer stop() - res, err := http.Post("http://"+gbp.Addr+rvPath, "application/json", strings.NewReader("[]")) + res, err := http.Post("http://"+gbp.Addr+rvPath, "application/json; charset=utf-8", strings.NewReader("[]")) if err != nil { t.Error(err) } @@ -476,3 +476,23 @@ func TestInvalidSSZ(t *testing.T) { assertResp(t, res, fmt.Sprintf(`{"error":"buffer is not a multiple of SignedValidatorRegistration length: %d"}`, sszSize), http.StatusBadRequest) } + +func TestInvalidMIMEContentType(t *testing.T) { + ts := testServer(handlerOK(), handlerOK(), handlerOK()) + defer ts.Close() + t.Logf("upstream listening on %s\n", ts.Listener.Addr()) + + gbp, start, stop := newGbp(t, ts) + gbp.RegisterValidatorGuard = func(r RegisterValidatorRequest, ctx context.Context) (AuthenticationStatus, error) { + return Allowed, nil + } + go start(t) + defer stop() + + res, err := http.Post("http://"+gbp.Addr+rvPath, "application/json; charset", strings.NewReader("[]")) + if err != nil { + t.Error(err) + } + + assertResp(t, res, `{"error":"mime: invalid media parameter"}`, http.StatusUnsupportedMediaType) +} From ab42b8965113e6f858039bae2ec36f347cb0e864 Mon Sep 17 00:00:00 2001 From: Jacob Shufro Date: Tue, 17 Jun 2025 19:57:24 -0400 Subject: [PATCH 4/6] Bump go version to 1.21 --- go.mod | 2 +- go.sum | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 7556f5b..27ef803 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/Rocket-Rescue-Node/guarded-beacon-proxy -go 1.19 +go 1.21 require ( github.com/ethereum/go-ethereum v1.12.0 diff --git a/go.sum b/go.sum index c7f298f..f44ec06 100644 --- a/go.sum +++ b/go.sum @@ -34,13 +34,16 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= +github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/VictoriaMetrics/fastcache v1.12.0 h1:vnVi/y9yKDcD9akmc4NqAoqgQhJrOwUF+j9LTgn4QDE= +github.com/VictoriaMetrics/fastcache v1.12.0/go.mod h1:tjiYeEfYXCqacuvYw/7UoDIeJaNxq6132xHICNP77w8= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -51,13 +54,20 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= +github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZOZLBCor4mBk= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 h1:ytcWPaNPhNoGMWEhDvS3zToKcDpRsLuRolQJBVGdozk= +github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811/go.mod h1:Nb5lgvnQ2+oGlE/EyZy4+2/CxRh9KfvCXnag1vtpxVM= github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= +github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U= +github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= @@ -72,16 +82,22 @@ github.com/ethereum/go-ethereum v1.12.0/go.mod h1:/oo2X/dZLJjf2mJ6YT9wcWxa4nNJDB github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY= github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg= github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= +github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= +github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -112,6 +128,7 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu 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/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -151,16 +168,21 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.16.4 h1:91KN02FnsOYhuunwU4ssRe8lc2JosWmizWa91B5v1PU= +github.com/klauspost/compress v1.16.4/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= @@ -168,9 +190,11 @@ github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mwitkow/grpc-proxy v0.0.0-20230212185441-f345521cb9c9 h1:62uLwA3l2JMH84liO4ZhnjTH5PjFyCYxbHLgXPaJMtI= github.com/mwitkow/grpc-proxy v0.0.0-20230212185441-f345521cb9c9/go.mod h1:MvMXoufZAtqExNexqi4cjrNYE9MefKddKylxjS+//n0= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -193,26 +217,35 @@ github.com/prysmaticlabs/gohashtree v0.0.4-beta/go.mod h1:BFdtALS+Ffhg3lGQIHv9HD github.com/prysmaticlabs/prysm/v4 v4.0.6 h1:e3EyQUPonzvQF2PxxEF+GCjaOhr42eZs48PAvB66gyo= github.com/prysmaticlabs/prysm/v4 v4.0.6/go.mod h1:LjWP50kVb6UzhvkVxdV8KC0eo289XOfumomkKRMw8Sk= github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= +github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= +github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs= +github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e h1:cR8/SYRgyQCt5cNCMniB/ZScMkhI9nk8U5C7SbISXjo= github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e/go.mod h1:Tu4lItkATkonrYuvtVjG0/rhy15qrNGNTjPdaphtZ/8= github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= +github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= +github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= +github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -236,6 +269,7 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -305,6 +339,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -487,6 +522,7 @@ google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -494,6 +530,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 4ad9434932f9df7aeeb72b5cb3da23e8a2618cae Mon Sep 17 00:00:00 2001 From: Jacob Shufro Date: Tue, 17 Jun 2025 20:53:24 -0400 Subject: [PATCH 5/6] limited request body reading --- guarded-beacon-proxy.go | 58 +++++++++++++++++++++++- http.go | 63 ++++++++++++-------------- http_test.go | 97 +++++++++++++++++++++++++++++++++++++++-- ssz/types.go | 45 +++++++++++++------ 4 files changed, 209 insertions(+), 54 deletions(-) diff --git a/guarded-beacon-proxy.go b/guarded-beacon-proxy.go index 56f9c86..ee7e1c0 100644 --- a/guarded-beacon-proxy.go +++ b/guarded-beacon-proxy.go @@ -3,6 +3,7 @@ package guardedbeaconproxy import ( "context" "fmt" + "io" "log" "net" "net/http" @@ -57,6 +58,9 @@ type GuardedBeaconProxy struct { Addr string // Optional GRPC address to listen on GRPCAddr string + // Maximum request body size in bytes + // If 0, no limit is applied + MaxRequestBodySize int64 // Pass-through HTTP server settings ReadTimeout time.Duration ReadHeaderTimeout time.Duration @@ -117,6 +121,43 @@ func (gbp *GuardedBeaconProxy) init() { gbp.server.ErrorLog = gbp.ErrorLog } +func (gbp *GuardedBeaconProxy) limitRequestBodyHandlerFunc(next httpGuard) httpGuard { + return func(w http.ResponseWriter, r *http.Request) bool { + if gbp.MaxRequestBodySize == 0 { + return next(w, r) + } + + // Allow 1 extra byte. If it actually gets read, we will return an error. + // This lets us detect if the request body is exactly the size of the limit. + sizeLimit := gbp.MaxRequestBodySize + 1 + + if r.ContentLength >= sizeLimit { + gbp.httpError(w, http.StatusRequestEntityTooLarge, fmt.Errorf("request body too large")) + return false + } + + limited := &io.LimitedReader{ + R: r.Body, + N: sizeLimit, + } + // According to the docs, http servers don't need to close the body ReadCloser, only Clients do. + r.Body = io.NopCloser(limited) + shouldProxy := next(w, r) + if !shouldProxy { + return false + } + if limited.N == 0 { + // The next handler didn't return an error, but we exceeded the limit. + // Do not proxy the request, and return StatusRequestEntityTooLarge. + gbp.httpError(w, http.StatusRequestEntityTooLarge, fmt.Errorf("request body too large")) + return false + } + // The next handler didn't return an error, and we didn't exceed the limit. + // Proxy the request. + return true + } +} + // Serve attaches the proxy to the provided listener(s) // // Serve blocks until Stop is called or an error is encountered. @@ -128,12 +169,25 @@ func (gbp *GuardedBeaconProxy) Serve(httpListener net.Listener, grpcListener *ne router := mux.NewRouter() if gbp.PrepareBeaconProposerGuard != nil { - router.Path("/eth/v1/validator/prepare_beacon_proposer").HandlerFunc(gbp.prepareBeaconProposer) + router.Path("/eth/v1/validator/prepare_beacon_proposer").HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + if gbp.limitRequestBodyHandlerFunc(gbp.prepareBeaconProposer)(w, r) { + gbp.proxy.ServeHTTP(w, r) + } + }, + ) } if gbp.RegisterValidatorGuard != nil { - router.Path("/eth/v1/validator/register_validator").HandlerFunc(gbp.registerValidator) + router.Path("/eth/v1/validator/register_validator").HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + if gbp.limitRequestBodyHandlerFunc(gbp.registerValidator)(w, r) { + gbp.proxy.ServeHTTP(w, r) + } + }, + ) } + router.PathPrefix("/").Handler(gbp.proxy) if gbp.HTTPAuthenticator != nil { diff --git a/http.go b/http.go index eca8bf4..cae1bdb 100644 --- a/http.go +++ b/http.go @@ -25,6 +25,9 @@ import ( // information. type HTTPAuthenticator func(*http.Request) (AuthenticationStatus, context.Context, error) +// If true is returned, the upstream will proxy the request. +type httpGuard func(w http.ResponseWriter, r *http.Request) bool + func (gbp *GuardedBeaconProxy) authenticationMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { status, context, err := gbp.HTTPAuthenticator(r) @@ -43,16 +46,13 @@ func (gbp *GuardedBeaconProxy) authenticationMiddleware(next http.Handler) http. } func cloneRequestBody(r *http.Request) (io.ReadCloser, error) { - // Read the body - buf, err := io.ReadAll(r.Body) - if err != nil { - return nil, err - } + // Use an io.TeeReader to return a reader that re-writes the body to the original request body. + buf := bytes.NewBuffer(nil) + tee := io.TeeReader(r.Body, buf) + out := io.NopCloser(tee) + r.Body = io.NopCloser(buf) - original := io.NopCloser(bytes.NewBuffer(buf)) - clone := io.NopCloser(bytes.NewBuffer(buf)) - r.Body = original - return clone, nil + return out, nil } func (gbp *GuardedBeaconProxy) httpError(w http.ResponseWriter, code int, err error) { @@ -63,71 +63,64 @@ func (gbp *GuardedBeaconProxy) httpError(w http.ResponseWriter, code int, err er } } -func (gbp *GuardedBeaconProxy) prepareBeaconProposer(w http.ResponseWriter, r *http.Request) { - buf, err := cloneRequestBody(r) +func (gbp *GuardedBeaconProxy) prepareBeaconProposer(w http.ResponseWriter, r *http.Request) bool { + reader, err := cloneRequestBody(r) if err != nil { gbp.httpError(w, http.StatusInternalServerError, nil) - return + return false } var proposers PrepareBeaconProposerRequest - if err := json.NewDecoder(buf).Decode(&proposers); err != nil { + if err := json.NewDecoder(reader).Decode(&proposers); err != nil { gbp.httpError(w, http.StatusBadRequest, nil) - return + return false } status, err := gbp.PrepareBeaconProposerGuard(proposers, r.Context()) if status != Allowed { gbp.httpError(w, status.httpStatus(), err) - return + return false } - gbp.proxy.ServeHTTP(w, r) + return true } -func (gbp *GuardedBeaconProxy) registerValidator(w http.ResponseWriter, r *http.Request) { - buf, err := cloneRequestBody(r) +func (gbp *GuardedBeaconProxy) registerValidator(w http.ResponseWriter, r *http.Request) bool { + reader, err := cloneRequestBody(r) if err != nil { gbp.httpError(w, http.StatusInternalServerError, nil) - return + return false } // Check the content-type header contentType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) if err != nil { gbp.httpError(w, http.StatusUnsupportedMediaType, err) - return + return false } var validators RegisterValidatorRequest switch contentType { case "application/json": - if err := json.NewDecoder(buf).Decode(&validators); err != nil { + if err := json.NewDecoder(reader).Decode(&validators); err != nil { gbp.httpError(w, http.StatusBadRequest, err) - return + return false } case "application/octet-stream": - // slurp the body into a buffer - data, err := io.ReadAll(buf) - if err != nil { - gbp.httpError(w, http.StatusInternalServerError, err) - return - } - - if err := ssz.ToRegisterValidatorRequest(&validators, data); err != nil { - gbp.httpError(w, http.StatusBadRequest, err) - return + if err, status := ssz.ToRegisterValidatorRequest(&validators, reader, gbp.MaxRequestBodySize); err != nil { + gbp.httpError(w, status, err) + return false } default: gbp.httpError(w, http.StatusUnsupportedMediaType, fmt.Errorf("unsupported content type: %s", contentType)) - return + return false } status, err := gbp.RegisterValidatorGuard(validators, r.Context()) if status != Allowed { gbp.httpError(w, status.httpStatus(), err) - return + return false } - gbp.proxy.ServeHTTP(w, r) + return true } diff --git a/http_test.go b/http_test.go index 4796bda..0762175 100644 --- a/http_test.go +++ b/http_test.go @@ -26,6 +26,7 @@ var okBody string = "TEST OK" func handlerOK() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + _, _ = io.ReadAll(r.Body) fmt.Fprint(w, okBody) } } @@ -356,11 +357,12 @@ func TestSSZ(t *testing.T) { for _, m := range r { if m.Message.FeeRecipient != addr.Hex() { - return Conflict, fmt.Errorf("incorrect fee recipient") + return Conflict, fmt.Errorf("incorrect fee recipient, wanted: %s, got: %s", addr.Hex(), m.Message.FeeRecipient) } if m.Signature != signature { - return Conflict, fmt.Errorf("incorrect signature") + fmt.Println(m.Signature) + return Conflict, fmt.Errorf("incorrect signature, wanted: %s, got: %s", signature, m.Signature) } } @@ -408,7 +410,8 @@ func TestSSZ(t *testing.T) { if err != nil { t.Error(err) } - fmt.Println(res.StatusCode) + + assertRespOK(t, res) } func TestInvalidContentType(t *testing.T) { @@ -474,7 +477,7 @@ func TestInvalidSSZ(t *testing.T) { sszSize := (&ssz.SignedValidatorRegistration{}).SizeSSZ() - assertResp(t, res, fmt.Sprintf(`{"error":"buffer is not a multiple of SignedValidatorRegistration length: %d"}`, sszSize), http.StatusBadRequest) + assertResp(t, res, fmt.Sprintf(`{"error":"buffer is not a positive multiple of SignedValidatorRegistration length: %d"}`, sszSize), http.StatusBadRequest) } func TestInvalidMIMEContentType(t *testing.T) { @@ -496,3 +499,89 @@ func TestInvalidMIMEContentType(t *testing.T) { assertResp(t, res, `{"error":"mime: invalid media parameter"}`, http.StatusUnsupportedMediaType) } + +func TestUnguardedUnauthedRequestTooLargeByOne(t *testing.T) { + ts := testServer(handlerOK(), handlerOK(), handlerOK()) + defer ts.Close() + t.Logf("upstream listening on %s\n", ts.Listener.Addr()) + + sszSize := (&ssz.SignedValidatorRegistration{}).SizeSSZ() + + // Set up a gbp with no auth and no guards + gbp, start, stop := newGbp(t, ts) + gbp.MaxRequestBodySize = int64(sszSize*4) - 1 + gbp.RegisterValidatorGuard = func(r RegisterValidatorRequest, ctx context.Context) (AuthenticationStatus, error) { + return Allowed, nil + } + go start(t) + defer stop() + t.Logf("proxy listening on %s\n", gbp.Addr) + + // Check any old route + res, err := http.Get("http://" + gbp.Addr + "/status") + if err != nil { + t.Error(err) + } + + assertRespOK(t, res) + + body := make([]byte, 4*sszSize) + + // Check a guarded route + postReq, err := http.NewRequest("POST", "http://"+gbp.Addr+rvPath, bytes.NewReader(body)) + if err != nil { + t.Error(err) + } + postReq.Header.Set("Content-Type", "application/octet-stream") + postReq.ContentLength = 0 + + res, err = http.DefaultClient.Do(postReq) + if err != nil { + t.Error(err) + } + + assertResp(t, res, `{"error":"request body too large"}`, http.StatusRequestEntityTooLarge) +} + +func TestUnguardedUnauthedRequestTooLargeByAWholeChunk(t *testing.T) { + ts := testServer(handlerOK(), handlerOK(), handlerOK()) + defer ts.Close() + t.Logf("upstream listening on %s\n", ts.Listener.Addr()) + + sszSize := (&ssz.SignedValidatorRegistration{}).SizeSSZ() + + // Set up a gbp with no auth and no guards + gbp, start, stop := newGbp(t, ts) + gbp.MaxRequestBodySize = int64(sszSize * 4) + gbp.RegisterValidatorGuard = func(r RegisterValidatorRequest, ctx context.Context) (AuthenticationStatus, error) { + return Allowed, nil + } + go start(t) + defer stop() + t.Logf("proxy listening on %s\n", gbp.Addr) + + // Check any old route + res, err := http.Get("http://" + gbp.Addr + "/status") + if err != nil { + t.Error(err) + } + + assertRespOK(t, res) + + body := make([]byte, 5*sszSize) + + // Check a guarded route + postReq, err := http.NewRequest("POST", "http://"+gbp.Addr+rvPath, bytes.NewReader(body)) + if err != nil { + t.Error(err) + } + postReq.Header.Set("Content-Type", "application/octet-stream") + postReq.ContentLength = 0 + + res, err = http.DefaultClient.Do(postReq) + if err != nil { + t.Error(err) + } + + assertResp(t, res, `{"error":"request body too large"}`, http.StatusRequestEntityTooLarge) +} diff --git a/ssz/types.go b/ssz/types.go index 0c93180..6a3ea16 100644 --- a/ssz/types.go +++ b/ssz/types.go @@ -1,8 +1,11 @@ package ssz import ( + "bytes" "encoding/hex" "fmt" + "io" + "net/http" "strconv" "github.com/Rocket-Rescue-Node/guarded-beacon-proxy/jsontypes" @@ -22,20 +25,35 @@ type SignedValidatorRegistration struct { type RegisterValidatorRequest []SignedValidatorRegistration -func ToRegisterValidatorRequest(dst *jsontypes.RegisterValidatorRequest, buf []byte) error { +func ToRegisterValidatorRequest(dst *jsontypes.RegisterValidatorRequest, reader io.Reader, maxBytes int64) (error, int) { - // Ensure the buffer is a multiple of SignedValidatorRegistration.SizeSSZ() - size := (&SignedValidatorRegistration{}).SizeSSZ() - if len(buf)%size != 0 { - return fmt.Errorf("buffer is not a multiple of SignedValidatorRegistration length: %d", size) - } + // Get the expected size of the SSZ objects + size := int64((&SignedValidatorRegistration{}).SizeSSZ()) + + totalBytes := int64(0) - // Unmarshal the buffer SSZ objects - for i := 0; i < len(buf); i += size { - section := buf[i : i+size] + // Read `size` bytes at a time, and ensure the buffer is a multiple of `size` + buf := bytes.NewBuffer(make([]byte, 0, size)) + for { + if maxBytes > 0 && totalBytes+size > maxBytes { + return fmt.Errorf("request body too large"), http.StatusRequestEntityTooLarge + } + w, err := io.CopyN(buf, reader, size) + totalBytes += w + fmt.Println("w", w) + if err == io.EOF { + if w != 0 { + return fmt.Errorf("buffer is not a positive multiple of SignedValidatorRegistration length: %d", size), + http.StatusBadRequest + } + break + } + if err != nil { + return err, http.StatusInternalServerError + } var signedValidatorRegistration SignedValidatorRegistration - if err := signedValidatorRegistration.UnmarshalSSZ(section); err != nil { - return err + if err := signedValidatorRegistration.UnmarshalSSZ(buf.Bytes()); err != nil { + return err, http.StatusBadRequest } *dst = append(*dst, jsontypes.SignedValidatorRegistration{ Message: jsontypes.RegisterValidatorMessage{ @@ -44,9 +62,10 @@ func ToRegisterValidatorRequest(dst *jsontypes.RegisterValidatorRequest, buf []b Timestamp: strconv.FormatUint(signedValidatorRegistration.Message.Timestamp, 10), Pubkey: "0x" + hex.EncodeToString(signedValidatorRegistration.Message.Pubkey), }, - Signature: hex.EncodeToString(signedValidatorRegistration.Signature), + Signature: "0x" + hex.EncodeToString(signedValidatorRegistration.Signature), }) + buf.Reset() } - return nil + return nil, http.StatusOK } From d4d53decddd20d980e7b6eef3f8b33e8999a6cca Mon Sep 17 00:00:00 2001 From: Jacob Shufro Date: Tue, 17 Jun 2025 20:55:00 -0400 Subject: [PATCH 6/6] Update the ci to golang 1.21 --- .github/workflows/golangci-lint.yml | 2 +- .github/workflows/test.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 9b3f011..f97f1ce 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -19,7 +19,7 @@ jobs: steps: - uses: actions/setup-go@v3 with: - go-version: 1.19 + go-version: 1.21 - uses: actions/checkout@v3 - name: golangci-lint uses: golangci/golangci-lint-action@v3 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 981310a..c57848a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -12,7 +12,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.19' + go-version: '1.21' - name: Test run: go test -v ./...