Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(builder,relay): remove auth when subscribing for constraints #187

Merged
merged 2 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 3 additions & 27 deletions builder/builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,46 +256,23 @@ func (b *Builder) Start() error {
return b.SubscribeProposerConstraints()
}

// GenerateAuthenticationHeader generates an authentication string for the builder
// to subscribe to SSE constraint events emitted by relays
func (b *Builder) GenerateAuthenticationHeader() (string, error) {
// NOTE: the `slot` acts similarly to a nonce for the message to sign, to avoid replay attacks.
slot := b.slotAttrs.Slot
message, err := json.Marshal(common.ConstraintSubscriptionAuth{PublicKey: b.builderPublicKey, Slot: slot})
if err != nil {
log.Error(fmt.Sprintf("Failed to marshal auth message: %v", err))
return "", err
}
signatureEC := bls.Sign(b.builderSecretKey, message)
subscriptionSignatureJSON := `"` + phase0.BLSSignature(bls.SignatureToBytes(signatureEC)[:]).String() + `"`
authHeader := "BOLT " + subscriptionSignatureJSON + "," + string(message)
return authHeader, nil
}

// SubscribeProposerConstraints subscribes to the constraints made by Bolt proposers
// which the builder pulls from relay(s) using SSE.
func (b *Builder) SubscribeProposerConstraints() error {
// Create authentication signed message
authHeader, err := b.GenerateAuthenticationHeader()
if err != nil {
log.Error(fmt.Sprintf("Failed to generate authentication header: %v", err))
return err
}

// Check if `b.relay` is a RemoteRelayAggregator, if so we need to subscribe to
// the constraints made available by all the relays
relayAggregator, ok := b.relay.(*RemoteRelayAggregator)
if ok {
for _, relay := range relayAggregator.relays {
go b.subscribeToRelayForConstraints(relay.Config().Endpoint, authHeader)
go b.subscribeToRelayForConstraints(relay.Config().Endpoint)
}
} else {
go b.subscribeToRelayForConstraints(b.relay.Config().Endpoint, authHeader)
go b.subscribeToRelayForConstraints(b.relay.Config().Endpoint)
}
return nil
}

func (b *Builder) subscribeToRelayForConstraints(relayBaseEndpoint, authHeader string) error {
func (b *Builder) subscribeToRelayForConstraints(relayBaseEndpoint string) error {
attempts := 0
maxAttempts := 60 // Max 10 minutes of retries
retryInterval := 10 * time.Second
Expand All @@ -315,7 +292,6 @@ func (b *Builder) subscribeToRelayForConstraints(relayBaseEndpoint, authHeader s
log.Error(fmt.Sprintf("Failed to create new http request: %v", err))
return err
}
req.Header.Set("Authorization", authHeader)

client := http.Client{}

Expand Down
5 changes: 1 addition & 4 deletions builder/builder/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,10 +443,7 @@ func TestSubscribeProposerConstraints(t *testing.T) {
_, ok := builder.constraintsCache.Get(0)
require.Equal(t, false, ok)

// Create authentication signed message
authHeader, err := builder.GenerateAuthenticationHeader()
require.NoError(t, err)
builder.subscribeToRelayForConstraints(builder.relay.Config().Endpoint, authHeader)
builder.subscribeToRelayForConstraints(builder.relay.Config().Endpoint)
// Wait 2 seconds to save all constraints in cache
time.Sleep(2 * time.Second)

Expand Down
22 changes: 2 additions & 20 deletions mev-boost-relay/services/api/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -3014,25 +3014,7 @@ func (api *RelayAPI) handleSubscribeConstraints(w http.ResponseWriter, req *http
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")

// TODO: Docs regarding this autorization schema
// NOTE: This authorization schema is not final, but works for now.
auth := req.Header.Get("Authorization")

builderPublicKey, err := validateConstraintSubscriptionAuth(auth, api.headSlot.Load())
if err != nil {
api.log.Infof("Failed to validate constraint subscription auth: %s. err: %s", auth, err)
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}

_, ok := api.checkBuilderEntry(w, api.log, builderPublicKey)
if !ok {
api.log.Infof("Builder rejected: %s", builderPublicKey)
http.Error(w, "Builder rejected", http.StatusUnauthorized)
return
}

api.log.Infof("New constraints consumer with builder pubkey: %s", builderPublicKey)
api.log.Infof("New constraints consumer connected")

// Add the new consumer
constraintsCh := make(chan *SignedConstraints, 256)
Expand Down Expand Up @@ -3065,7 +3047,7 @@ func (api *RelayAPI) handleSubscribeConstraints(w http.ResponseWriter, req *http
return
case <-ticker.C:
// Send a keepalive to the client
fmt.Fprint(w, ": keepaliveeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\n\n")
mempirate marked this conversation as resolved.
Show resolved Hide resolved
fmt.Fprint(w, ": keepalive\n\n")
flusher.Flush()
case constraint := <-constraintsCh:
constraintJSON, err := json.Marshal([]*SignedConstraints{constraint})
Expand Down
15 changes: 0 additions & 15 deletions mev-boost-relay/services/api/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,17 +398,6 @@ func TestSubscribeToConstraints(t *testing.T) {
// Wait for the server to start
time.Sleep(500 * time.Millisecond)

// Setup information of the builder making the request
builderPrivateKey, builderPublicKey, err := bls.GenerateNewKeypair()
require.NoError(t, err)
var phase0BuilderPublicKey phase0.BLSPubKey = builderPublicKey.Bytes()

headSlot := backend.relay.headSlot.Load()
message, err := json.Marshal(ConstraintSubscriptionAuth{PublicKey: phase0BuilderPublicKey, Slot: headSlot})
require.NoError(t, err)
signatureEC := bls.Sign(builderPrivateKey, message)
subscriptionSignatureJSON := `"` + phase0.BLSSignature(bls.SignatureToBytes(signatureEC)[:]).String() + `"`

// Run the request in a goroutine so that it doesn't block the test,
// but it finishes as soon as the message is sent over the channel
go func() {
Expand All @@ -418,10 +407,6 @@ func TestSubscribeToConstraints(t *testing.T) {
log.Fatalf("Failed to create request: %v", err)
}

// Add authentication
authHeader := "BOLT " + subscriptionSignatureJSON + "," + string(message)
req.Header.Set("Authorization", authHeader)

// Send the request
client := &http.Client{}
// NOTE: this response arrives after the first data is flushed
Expand Down
50 changes: 0 additions & 50 deletions mev-boost-relay/services/api/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,62 +150,12 @@ func verifyBlockSignature(block *common.VersionedSignedBlindedBeaconBlock, domai
return bls.VerifySignatureBytes(msg[:], sig[:], pubKey[:])
}

func getPayloadAttributesKey(parentHash string, slot uint64) string {
return fmt.Sprintf("%s-%d", parentHash, slot)
}

func broadcastToChannels[T any](constraintsConsumers []chan *T, constraint *T) {
for _, consumer := range constraintsConsumers {
consumer <- constraint
}
}

// validateConstraintSubscriptionAuth checks the authentication string data from the Builder,
// and returns its BLS public key if the authentication is valid.
func validateConstraintSubscriptionAuth(auth string, headSlot uint64) (phase0.BLSPubKey, error) {
zeroKey := phase0.BLSPubKey{}
if auth == "" {
return zeroKey, errors.Errorf("Authorization header missing")
}
// Authorization: <auth-scheme> <authorization-parameters>
parts := strings.Split(auth, " ")
if len(parts) != 2 {
return zeroKey, errors.Errorf("Ill-formed authorization header")
}
if parts[0] != "BOLT" {
return zeroKey, errors.Errorf("Not BOLT authentication scheme")
}
// <signatureJSON>,<authDataJSON>
parts = strings.SplitN(parts[1], ",", 2)
if len(parts) != 2 {
return zeroKey, errors.Errorf("Ill-formed authorization header")
}

signature := new(phase0.BLSSignature)
if err := signature.UnmarshalJSON([]byte(parts[0])); err != nil {
fmt.Println("Failed to unmarshal authData: ", err)
return zeroKey, errors.Errorf("Ill-formed authorization header")
}

authDataRaw := []byte(parts[1])
authData := new(ConstraintSubscriptionAuth)
if err := json.Unmarshal(authDataRaw, authData); err != nil {
fmt.Println("Failed to unmarshal authData: ", err)
return zeroKey, errors.Errorf("Ill-formed authorization header")
}

// FIXME: this is broken on the devnet, let's skip it for now
// if headSlot != authData.Slot {
// return zeroKey, errors.Errorf("Invalid head slot. Expected %d, got %d", headSlot, authData.Slot)
// }

ok, err := bls.VerifySignatureBytes(authDataRaw, signature[:], authData.PublicKey[:])
if err != nil || !ok {
return zeroKey, errors.Errorf("Invalid signature")
}
return authData.PublicKey, nil
}

func JSONStringify[T any](obj T) string {
out, err := json.Marshal(obj)
if err != nil {
Expand Down
Loading