Skip to content

Commit

Permalink
Merge pull request #1251 from bloxapp/e2e-bls-verification
Browse files Browse the repository at this point in the history
E2E BLS Verification
  • Loading branch information
y0sher authored Jan 3, 2024
2 parents b74e603 + a63211d commit 2eb8f06
Show file tree
Hide file tree
Showing 12 changed files with 1,100 additions and 86 deletions.
15 changes: 10 additions & 5 deletions e2e/cmd/ssv-e2e/beacon_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/attestantio/go-eth2-client/http"
"os"
"time"

"github.com/attestantio/go-eth2-client/http"

eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/auto"
"github.com/attestantio/go-eth2-client/spec/phase0"
Expand All @@ -30,6 +31,10 @@ type BeaconProxyCmd struct {
BasePort int ` env:"BASE_PORT" help:"Base port for the gateways." default:"6631"`
}

type BeaconProxyJSON struct {
Validators map[phase0.ValidatorIndex]string `json:"beacon_proxy"`
}

func GetValidators(ctx context.Context, beaconURL string, idxs []phase0.ValidatorIndex) (map[phase0.ValidatorIndex]*v1.Validator, error) {
// todo: maybe create the client on top and pass down to all components
client, err := auto.New(
Expand Down Expand Up @@ -79,17 +84,17 @@ func (cmd *BeaconProxyCmd) Run(logger *zap.Logger, globals Globals) error {
}
logger.Info("Beacon client status OK")

var validators map[phase0.ValidatorIndex]string // dx => tests
contents, err := os.ReadFile(globals.ValidatorsFile)
if err != nil {
return fmt.Errorf("failed to read file contents: %s, %w", globals.ValidatorsFile, err)
}
err = json.Unmarshal(contents, &validators)
if err != nil {

var beaconProxyJSON BeaconProxyJSON // dx => tests
if err = json.Unmarshal(contents, &beaconProxyJSON); err != nil {
return fmt.Errorf("error parsing json file: %s, %w", globals.ValidatorsFile, err)
}

validatorsData, err := GetValidators(ctx, cmd.BeaconNodeUrl, maps.Keys(validators))
validatorsData, err := GetValidators(ctx, cmd.BeaconNodeUrl, maps.Keys(beaconProxyJSON.Validators))
if err != nil {
return fmt.Errorf("failed to get validators data from beacon node err:%v", err)
}
Expand Down
64 changes: 57 additions & 7 deletions e2e/cmd/ssv-e2e/logs_catcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,27 @@ package main

import (
"context"
"encoding/json"
"fmt"
"os"

"go.uber.org/zap"

"github.com/bloxapp/ssv/e2e/logs_catcher"
"github.com/bloxapp/ssv/e2e/logs_catcher/docker"
"go.uber.org/zap"
)

type LogsCatcherCmd struct {
Mode string `required:"" env:"Mode" help:"Mode of the logs catcher. Can be Slashing or BlsVerification"`
}

const (
SlashingMode = "Slashing"
BlsVerificationMode = "BlsVerification"
)

type BlsVerificationJSON struct {
CorruptedShares []*logs_catcher.CorruptedShare `json:"bls_verification"`
}

func (cmd *LogsCatcherCmd) Run(logger *zap.Logger, globals Globals) error {
Expand All @@ -23,15 +37,51 @@ func (cmd *LogsCatcherCmd) Run(logger *zap.Logger, globals Globals) error {

//TODO: run fataler and matcher in parallel?

err = logs_catcher.FatalListener(ctx, logger, cli)
if err != nil {
return err
// Execute different logic based on the value of the Mode flag
switch cmd.Mode {
case SlashingMode:
logger.Info("Running slashing mode")
err = logs_catcher.FatalListener(ctx, logger, cli)
if err != nil {
return err
}
err = logs_catcher.Match(ctx, logger, cli)
if err != nil {
return err
}

case BlsVerificationMode:
logger.Info("Running BlsVerification mode")

corruptedShares, err := UnmarshalBlsVerificationJSON(globals.ValidatorsFile)
if err != nil {
return fmt.Errorf("failed to unmarshal bls verification json: %w", err)
}

for _, corruptedShare := range corruptedShares {
if err = logs_catcher.VerifyBLSSignature(ctx, logger, cli, corruptedShare); err != nil {
return fmt.Errorf("failed to verify BLS signature for validator index %d: %w", corruptedShare.ValidatorIndex, err)
}
}

default:
return fmt.Errorf("invalid mode: %s", cmd.Mode)
}

err = logs_catcher.Match(ctx, logger, cli)
return nil
}

// UnmarshalBlsVerificationJSON reads the JSON file and unmarshals it into []*CorruptedShare.
func UnmarshalBlsVerificationJSON(filePath string) ([]*logs_catcher.CorruptedShare, error) {
contents, err := os.ReadFile(filePath)
if err != nil {
return err
return nil, fmt.Errorf("error reading json file for BLS verification: %s, %w", filePath, err)
}

return nil
var blsVerificationJSON BlsVerificationJSON
if err = json.Unmarshal(contents, &blsVerificationJSON); err != nil {
return nil, fmt.Errorf("error parsing json file for BLS verification: %s, %w", filePath, err)
}

return blsVerificationJSON.CorruptedShares, nil
}
2 changes: 2 additions & 0 deletions e2e/cmd/ssv-e2e/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
)

type Globals struct {
NetworkName string `env:"NETWORK" default:"holesky-e2e" help:"Network config name"`
LogLevel string `env:"LOG_LEVEL" enum:"debug,info,warn,error" default:"debug" help:"Log level."`
LogFormat string `env:"LOG_FORMAT" enum:"console,json" default:"console" help:"Log format."`
ValidatorsFile string `env:"VALIDATORS_FILE" default:"./validators.json" help:"Path to the validators.json file." type:"path"`
Expand All @@ -21,6 +22,7 @@ type CLI struct {
Globals
BeaconProxy BeaconProxyCmd `cmd:""`
LogsCatcher LogsCatcherCmd `cmd:""`
ShareUpdate ShareUpdateCmd `cmd:""`
}

func main() {
Expand Down
188 changes: 188 additions & 0 deletions e2e/cmd/ssv-e2e/share_update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package main

import (
"crypto/x509"
"encoding/base64"
"encoding/hex"
"fmt"
"os"

"github.com/bloxapp/ssv-spec/types"
"github.com/bloxapp/ssv/ekm"
"github.com/bloxapp/ssv/networkconfig"
operatorstorage "github.com/bloxapp/ssv/operator/storage"
"github.com/bloxapp/ssv/storage/basedb"
"github.com/bloxapp/ssv/storage/kv"
"github.com/bloxapp/ssv/utils/blskeygen"
"github.com/bloxapp/ssv/utils/rsaencryption"
"go.uber.org/zap"
"gopkg.in/yaml.v3"

"github.com/bloxapp/ssv/e2e/logs_catcher"
)

type ShareUpdateCmd struct {
OperatorPrivateKey string `yaml:"OperatorPrivateKey"`
}

const (
dbPathFormat = "/ssv-node-%d-data/db"
shareYAMLPath = "/tconfig/share%d.yaml"
)

func (cmd *ShareUpdateCmd) Run(logger *zap.Logger, globals Globals) error {
networkConfig, err := networkconfig.GetNetworkConfigByName(globals.NetworkName)
if err != nil {
return fmt.Errorf("failed to get network config: %w", err)
}

corruptedShares, err := UnmarshalBlsVerificationJSON(globals.ValidatorsFile)
if err != nil {
return fmt.Errorf("failed to unmarshal bls verification json: %w", err)
}

operatorSharesMap := buildOperatorCorruptedSharesMap(corruptedShares)

for operatorID, operatorCorruptedShares := range operatorSharesMap {
// Read OperatorPrivateKey from the YAML file
operatorPrivateKey, err := readOperatorPrivateKeyFromFile(fmt.Sprintf(shareYAMLPath, operatorID))
if err != nil {
return err
}

if err := Process(logger, networkConfig, operatorPrivateKey, operatorID, operatorCorruptedShares); err != nil {
return fmt.Errorf("failed to process operator %d: %w", operatorID, err)
}
}

return nil
}

func Process(logger *zap.Logger, networkConfig networkconfig.NetworkConfig, operatorPrivateKey string, operatorID types.OperatorID, operatorCorruptedShares []*logs_catcher.CorruptedShare) error {
dbPath := fmt.Sprintf(dbPathFormat, operatorID)
db, err := openDB(logger, dbPath)
if err != nil {
return err
}
defer db.Close()

nodeStorage, err := operatorstorage.NewNodeStorage(logger, db)
if err != nil {
return fmt.Errorf("failed to create node storage: %w", err)
}

opSK, err := base64.StdEncoding.DecodeString(operatorPrivateKey)
if err != nil {
return err
}
rsaPriv, err := rsaencryption.ConvertPemToPrivateKey(string(opSK))
if err != nil {
return fmt.Errorf("failed to convert PEM to private key: %w", err)
}

rsaPub, err := rsaencryption.ExtractPublicKey(rsaPriv)
if err != nil {
return fmt.Errorf("failed to extract public key: %w", err)
}

operatorData, found, err := nodeStorage.GetOperatorDataByPubKey(nil, []byte(rsaPub))
if err != nil {
return fmt.Errorf("failed to get operator data: %w", err)
}
if !found {
return fmt.Errorf("operator data not found")
}
if operatorData.ID != operatorID {
return fmt.Errorf("operator ID mismatch")
}

logger.Info("operator data found", zap.Any("operator ID", operatorData.ID))

keyBytes := x509.MarshalPKCS1PrivateKey(rsaPriv)
hashedKey, _ := rsaencryption.HashRsaKey(keyBytes)

keyManager, err := ekm.NewETHKeyManagerSigner(logger, db, networkConfig, false, hashedKey)
if err != nil {
return fmt.Errorf("failed to create key manager: %w", err)
}

for _, corruptedShare := range operatorCorruptedShares {
pkBytes, err := hex.DecodeString(corruptedShare.ValidatorPubKey)
if err != nil {
return fmt.Errorf("failed to decode validator public key: %w", err)
}

validatorShare := nodeStorage.Shares().Get(nil, pkBytes)
if validatorShare == nil {
return fmt.Errorf(fmt.Sprintf("validator share not found for %s", corruptedShare.ValidatorPubKey))
}
if validatorShare.Metadata.BeaconMetadata.Index != corruptedShare.ValidatorIndex {
return fmt.Errorf("validator index mismatch for validator %s", corruptedShare.ValidatorPubKey)
}

var operatorFound bool
for i, op := range validatorShare.Committee {
if op.OperatorID == operatorData.ID {
operatorFound = true

blsSK, blsPK := blskeygen.GenBLSKeyPair()
if err = keyManager.AddShare(blsSK); err != nil {
return fmt.Errorf("failed to add share: %w", err)
}

preChangePK := validatorShare.SharePubKey
validatorShare.SharePubKey = blsPK.Serialize()
validatorShare.Share.Committee[i].PubKey = validatorShare.SharePubKey
if err = nodeStorage.Shares().Save(nil, validatorShare); err != nil {
return fmt.Errorf("failed to save share: %w", err)
}

logger.Info("validator share was updated successfully",
zap.String("validator pub key", hex.EncodeToString(validatorShare.ValidatorPubKey)),
zap.String("BEFORE: share pub key", hex.EncodeToString(preChangePK)),
zap.String("AFTER: share pub key", hex.EncodeToString(validatorShare.SharePubKey)),
)
}
}
if !operatorFound {
return fmt.Errorf("operator %d not found in corrupted share", operatorData.ID)
}
}

return nil
}

func openDB(logger *zap.Logger, dbPath string) (*kv.BadgerDB, error) {
db, err := kv.New(logger, basedb.Options{Path: dbPath})
if err != nil {
return nil, fmt.Errorf("failed to open db: %w", err)
}
return db, nil
}

func readOperatorPrivateKeyFromFile(filePath string) (string, error) {
var config ShareUpdateCmd

data, err := os.ReadFile(filePath)
if err != nil {
return "", fmt.Errorf("failed to read file: %s, error: %w", filePath, err)
}

if err = yaml.Unmarshal(data, &config); err != nil {
return "", fmt.Errorf("failed to unmarshal YAML: %w", err)
}

return config.OperatorPrivateKey, nil
}

// buildOperatorCorruptedSharesMap takes a slice of CorruptedShare and returns a map
// where each key is an OperatorID and the value is a slice of CorruptedShares associated with that OperatorID.
func buildOperatorCorruptedSharesMap(corruptedShares []*logs_catcher.CorruptedShare) map[types.OperatorID][]*logs_catcher.CorruptedShare {
operatorSharesMap := make(map[types.OperatorID][]*logs_catcher.CorruptedShare)

for _, share := range corruptedShares {
operatorSharesMap[share.OperatorID] = append(operatorSharesMap[share.OperatorID], share)
}

return operatorSharesMap
}
20 changes: 20 additions & 0 deletions e2e/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,28 @@ services:
networks:
- blox-docker
volumes:
- ${PWD}/validators.json:/app/validators.json
- /var/run/docker.sock:/var/run/docker.sock

share_update:
build:
context: .
dockerfile: Dockerfile
image: share_update:latest
command: share-update
volumes:
- ${PWD}/validators.json:/app/validators.json
- ssv-node-1-data:/ssv-node-1-data
- ssv-node-2-data:/ssv-node-2-data
- ssv-node-3-data:/ssv-node-3-data
- ssv-node-4-data:/ssv-node-4-data
- ${PWD}/config/share1.yaml:/tconfig/share1.yaml
- ${PWD}/config/share2.yaml:/tconfig/share2.yaml
- ${PWD}/config/share3.yaml:/tconfig/share3.yaml
- ${PWD}/config/share4.yaml:/tconfig/share4.yaml
networks:
- blox-docker

x-base: &default-base
depends_on:
beacon_proxy:
Expand Down
Loading

0 comments on commit 2eb8f06

Please sign in to comment.