Skip to content

Commit

Permalink
Merge pull request #187 from chainbound/nico/chore/rm-builder-auth
Browse files Browse the repository at this point in the history
chore(builder,relay): remove auth when subscribing for constraints
  • Loading branch information
merklefruit authored Aug 6, 2024
2 parents b0d0786 + 1b2902c commit 466b575
Show file tree
Hide file tree
Showing 5 changed files with 6 additions and 115 deletions.
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
21 changes: 2 additions & 19 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,6 +3047,7 @@ func (api *RelayAPI) handleSubscribeConstraints(w http.ResponseWriter, req *http
return
case <-ticker.C:
// Send a keepalive to the client
// NOTE: the length of the message is intentional, do not make it shorter
fmt.Fprint(w, ": keepaliveeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\n\n")
flusher.Flush()
case constraint := <-constraintsCh:
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

0 comments on commit 466b575

Please sign in to comment.