Skip to content

Commit

Permalink
Merge pull request #28 from marioevz/remove-blob-sidecar-signing
Browse files Browse the repository at this point in the history
feat: Implement blob sidecar signatures removal
  • Loading branch information
marioevz authored Nov 23, 2023
2 parents 7fe4c76 + fc34e03 commit 9e2c13d
Show file tree
Hide file tree
Showing 34 changed files with 1,555 additions and 1,731 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

<img src="blobber_logo.png" width="200" height="200">

## !! WARNING !!

Running some of the actions included in this tool will get a validator **SLASHED**.

Please exercise caution when using this tool and never use it on Mainnet!

## Description

Testing tool that sits as a proxy between the beacon and validator clients in order to intercept proposals, and then modify, delay, conceal or corrupt the blobs included in the proposal, which are then relayed to all the beacon clients via the DevP2P network.

Expand Down
83 changes: 26 additions & 57 deletions blobber.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/gorilla/mux"
"github.com/marioevz/blobber/common"
"github.com/marioevz/blobber/config"
"github.com/marioevz/blobber/keys"
"github.com/marioevz/blobber/p2p"
"github.com/marioevz/blobber/slot_actions"
"github.com/marioevz/blobber/validator_proxy"
Expand All @@ -21,11 +22,9 @@ import (
"github.com/protolambda/eth2api/client/beaconapi"
"github.com/protolambda/zrnt/eth2/beacon"
beacon_common "github.com/protolambda/zrnt/eth2/beacon/common"
"github.com/protolambda/zrnt/eth2/beacon/deneb"
"github.com/protolambda/ztyp/tree"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared"
"github.com/sirupsen/logrus"

eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
)

const (
Expand Down Expand Up @@ -80,7 +79,7 @@ func NewBlobber(ctx context.Context, opts ...config.Option) (*Blobber, error) {

Config: &config.Config{
TestP2P: &p2p.TestP2P{
ChainStatus: common.NewStatus(),
ChainStatus: p2p.NewStatus(),
},
Host: DEFAULT_BLOBBER_HOST,
Port: DEFAULT_BLOBBER_PORT,
Expand Down Expand Up @@ -172,7 +171,7 @@ func (b *Blobber) AddBeaconClient(cl *beacon_client.BeaconClient, validatorProxy

// Update the validators map
if b.ValidatorKeys == nil {
b.ValidatorKeys = make(map[beacon_common.ValidatorIndex]*config.ValidatorKey)
b.ValidatorKeys = make(map[beacon_common.ValidatorIndex]*keys.ValidatorKey)
}
validatorResponses, err := b.loadStateValidators(b.ctx, cl, eth2api.StateHead, nil, nil)
if err != nil {
Expand Down Expand Up @@ -227,7 +226,7 @@ func (b *Blobber) loadStateValidators(
validatorIndex := validatorResponse.Index
validatorPubkey := validatorResponse.Validator.Pubkey
for _, key := range b.ValidatorKeysList {
if bytes.Equal(key.ValidatorPubkey[:], validatorPubkey[:]) {
if bytes.Equal(key.PubKeyToBytes(), validatorPubkey[:]) {
b.ValidatorKeys[validatorIndex] = key
break
}
Expand Down Expand Up @@ -292,22 +291,7 @@ func (b *Blobber) calcBeaconBlockDomain(slot beacon_common.Slot) beacon_common.B
)
}

func (b *Blobber) calcBlobSidecarDomain(slot beacon_common.Slot) beacon_common.BLSDomain {
b.Spec.ForkVersion(slot)
return beacon_common.ComputeDomain(
beacon_common.DOMAIN_BLOB_SIDECAR,
b.Spec.ForkVersion(slot),
b.GenesisValidatorsRoot,
)
}

func (b *Blobber) executeSlotActions(trigger_cl *beacon_client.BeaconClient, blResponse *eth.BeaconBlockAndBlobsDeneb, proposerKey *config.ValidatorKey) (bool, error) {
// Log current action info
blockRoot, err := blResponse.Block.HashTreeRoot()
if err != nil {
return false, errors.Wrap(err, "failed to get block hash tree root")
}

func (b *Blobber) executeSlotActions(trigger_cl *beacon_client.BeaconClient, blResponse *deneb.BlockContents, validatorKey *keys.ValidatorKey) (bool, error) {
slotAction, err := b.getSlotAction(uint64(blResponse.Block.Slot))
if err != nil {
return false, errors.Wrap(err, "failed to get slot action")
Expand All @@ -316,14 +300,6 @@ func (b *Blobber) executeSlotActions(trigger_cl *beacon_client.BeaconClient, blR
panic("slot action is nil")
}

logrus.WithFields(logrus.Fields{
"slot": blResponse.Block.Slot,
"block_root": fmt.Sprintf("%x", blockRoot),
"parent_block_root": fmt.Sprintf("%x", blResponse.Block.ParentRoot),
"blob_count": len(blResponse.Blobs),
"action_name": slotAction.Name(),
}).Info("Preparing action for block and blobs")

slotActionFields := logrus.Fields(slotAction.Fields())
if len(slotActionFields) > 0 {
logrus.WithFields(slotActionFields).Info("Action configuration")
Expand Down Expand Up @@ -353,21 +329,29 @@ func (b *Blobber) executeSlotActions(trigger_cl *beacon_client.BeaconClient, blR
}
}

calcBeaconBlockDomain := b.calcBeaconBlockDomain(beacon_common.Slot(blResponse.Block.Slot))
blobSidecarDomain := b.calcBlobSidecarDomain(beacon_common.Slot(blResponse.Block.Slot))
// Log current action info
blockRoot := blResponse.Block.HashTreeRoot(b.Spec, tree.GetHashFn())
logrus.WithFields(logrus.Fields{
"slot": blResponse.Block.Slot,
"block_root": blockRoot.String(),
"parent_block_root": blResponse.Block.ParentRoot.String(),
"blob_count": len(blResponse.Blobs),
"action_name": slotAction.Name(),
}).Info("Preparing action for block and blobs")

calcBeaconBlockDomain := b.calcBeaconBlockDomain(blResponse.Block.Slot)
executed, err := slotAction.Execute(
b.Spec,
testPeers,
blResponse.Block,
blResponse,
calcBeaconBlockDomain,
blResponse.Blobs,
blobSidecarDomain,
&proposerKey.ValidatorSecretKey,
validatorKey,
b.includeBlobRecord,
b.rejectBlobRecord,
)
if executed {
b.builtBlocksMap.Lock()
b.builtBlocksMap.BlockRoots[beacon_common.Slot(blResponse.Block.Slot)] = blockRoot
b.builtBlocksMap.BlockRoots[blResponse.Block.Slot] = blockRoot
b.builtBlocksMap.Unlock()
}
return executed, errors.Wrap(err, "failed to execute slot action")
Expand All @@ -392,9 +376,9 @@ func (b *Blobber) genValidatorBlockHandler(cl *beacon_client.BeaconClient, id in
if blockBlobResponse == nil {
return false, errors.Wrap(err, "response is nil")
}
var validatorKey *config.ValidatorKey
var validatorKey *keys.ValidatorKey
if b.ValidatorKeys != nil {
validatorKey = b.ValidatorKeys[beacon_common.ValidatorIndex(blockBlobResponse.Block.ProposerIndex)]
validatorKey = b.ValidatorKeys[blockBlobResponse.Block.ProposerIndex]
}
logrus.WithFields(logrus.Fields{
"proxy_id": id,
Expand Down Expand Up @@ -432,10 +416,9 @@ type BlockDataStruct struct {
Data json.RawMessage `json:"data"`
}

func ParseResponse(response []byte) (string, *eth.BeaconBlockAndBlobsDeneb, error) {
func ParseResponse(response []byte) (string, *deneb.BlockContents, error) {
var (
blockDataStruct BlockDataStruct
err error
)
if err := json.Unmarshal(response, &blockDataStruct); err != nil {
return blockDataStruct.Version, nil, errors.Wrap(err, "failed to unmarshal response into BlockDataStruct")
Expand All @@ -447,24 +430,10 @@ func ParseResponse(response []byte) (string, *eth.BeaconBlockAndBlobsDeneb, erro
}

decoder := json.NewDecoder(bytes.NewReader(blockDataStruct.Data))
data := new(shared.BeaconBlockContentsDeneb)
data := new(deneb.BlockContents)
if err := decoder.Decode(&data); err != nil {
return blockDataStruct.Version, nil, errors.Wrap(err, "failed to decode block contents")
}

beaconBlockContents := new(eth.BeaconBlockAndBlobsDeneb)

beaconBlockContents.Block, err = data.Block.ToConsensus()
if err != nil {
return blockDataStruct.Version, nil, err
}
beaconBlockContents.Blobs = make([]*eth.BlobSidecar, len(data.BlobSidecars))
for i, blob := range data.BlobSidecars {
beaconBlockContents.Blobs[i], err = blob.ToConsensus()
if err != nil {
return blockDataStruct.Version, nil, err
}
}

return blockDataStruct.Version, beaconBlockContents, nil
return blockDataStruct.Version, data, nil
}
75 changes: 64 additions & 11 deletions blobber_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,85 @@ import (
_ "embed"
"testing"

"github.com/ethereum/go-ethereum/common"
geth_common "github.com/ethereum/go-ethereum/common"
"github.com/marioevz/blobber"
"github.com/protolambda/zrnt/eth2/beacon/deneb"
"github.com/protolambda/zrnt/eth2/configs"
"github.com/protolambda/ztyp/tree"
)

//go:embed response_deneb.json
//go:embed slot_actions/response_deneb.json
var responseDeneb string

func TestResponseParse(T *testing.T) {
func TestResponseParse(t *testing.T) {
version, blockBlobResponse, err := blobber.ParseResponse([]byte(responseDeneb))
if err != nil {
T.Fatal(err)
t.Fatal(err)
} else if blockBlobResponse == nil {
T.Fatal("block is nil")
t.Fatal("block is nil")
}
if version != "deneb" {
T.Fatalf("wrong version: %s, expected deneb", version)
t.Fatalf("wrong version: %s, expected deneb", version)
}

expectedBlockRoot := geth_common.HexToHash("0x37977b8edac80973deb38f3888bff9483b45b057c188ec041273cfe4485e2695")

spec := configs.Mainnet

blockRoot := blockBlobResponse.Block.HashTreeRoot(spec, tree.GetHashFn())
bodyRoot := blockBlobResponse.Block.Body.HashTreeRoot(spec, tree.GetHashFn())

if !bytes.Equal(blockRoot[:], expectedBlockRoot[:]) {
t.Fatalf("wrong block root: %s, expected %s", blockRoot.String(), expectedBlockRoot.String())
}

if len(blockBlobResponse.Blobs) != 6 {
T.Fatalf("wrong number of sidecars: %d, expected 5", len(blockBlobResponse.Blobs))
t.Fatalf("wrong number of blobs: %d, expected 6", len(blockBlobResponse.Blobs))
}

signedBlockContents := deneb.SignedBlockContents{
SignedBlock: &deneb.SignedBeaconBlock{
Message: *blockBlobResponse.Block,
},
KZGProofs: blockBlobResponse.KZGProofs,
Blobs: blockBlobResponse.Blobs,
}

// Generate the sidecars
blobSidecars, err := signedBlockContents.GenerateSidecars(spec, tree.GetHashFn())
if err != nil {
t.Fatal(err)
}

// Verify the sidecars
if len(blobSidecars) != len(blockBlobResponse.Blobs) {
t.Fatalf("wrong number of blobs: %d, expected %d", len(blobSidecars), len(blockBlobResponse.Blobs))
}

expectedBlockRoot := common.HexToHash("0x37977b8edac80973deb38f3888bff9483b45b057c188ec041273cfe4485e2695")
for i, blobSidecar := range blobSidecars {
if blobSidecar.Index != deneb.BlobIndex(i) {
t.Fatalf("wrong blob index: %d, expected %d", blobSidecar.Index, i+1)
}

if blobSidecar.KZGCommitment != blockBlobResponse.Block.Body.BlobKZGCommitments[i] {
t.Fatalf("wrong blob commitment: %s, expected %s", blobSidecar.KZGCommitment.String(), blockBlobResponse.Block.Body.BlobKZGCommitments[i].String())
}

if blobSidecar.KZGProof != blockBlobResponse.KZGProofs[i] {
t.Fatalf("wrong blob proof: %s, expected %s", blobSidecar.KZGProof.String(), blockBlobResponse.KZGProofs[i].String())
}

if !bytes.Equal(blobSidecar.SignedBlockHeader.Message.BodyRoot[:], bodyRoot[:]) {
t.Fatalf("wrong blob body root: %s, expected %s", blobSidecar.SignedBlockHeader.Message.BodyRoot.String(), bodyRoot.String())
}

blockHeaderRoot := blobSidecar.SignedBlockHeader.Message.HashTreeRoot(tree.GetHashFn())
if !bytes.Equal(blockHeaderRoot[:], blockRoot[:]) {
t.Fatalf("wrong block header root: %s, expected %s", blockHeaderRoot.String(), blockRoot.String())
}

blockRoot := blockBlobResponse.Blobs[0].BlockRoot
if !bytes.Equal(expectedBlockRoot[:], blockRoot[:]) {
T.Fatalf("wrong block root: %x, expected %x", blockRoot[:], expectedBlockRoot[:])
if err := blobSidecar.VerifyProof(tree.GetHashFn()); err != nil {
t.Fatal(err)
}
}
}
12 changes: 12 additions & 0 deletions cmd/blobber.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ func main() {
validatorProxyPortStart int
maxDevP2PSessionReuses int
blobberID uint64
unsafeMode bool
)

flag.Var(
Expand Down Expand Up @@ -154,12 +155,23 @@ func main() {
"info",
"Sets the log level (trace, debug, info, warn, error, fatal, panic)",
)
flag.BoolVar(
&unsafeMode,
"enable-unsafe-mode",
false,
"Enable unsafe mode, only use this if you know what you're doing and never attempt to run this tool on mainnet.",
)

err := flag.CommandLine.Parse(os.Args[1:])
if err != nil {
panic(err)
}

if !unsafeMode {
fmt.Printf("WARNING: Some of the actions performed by this tool are unsafe and will get a validator SLASHED. Never run this tool on mainnet, and only run in test networks. If you know what you're doing, use the --enable-unsafe-mode flag to ignore this warning an proceed.\n\n")
os.Exit(1)
}

if len(clEndpoints) == 0 {
fatalf("at least one consensus layer client endpoint is required")
}
Expand Down
26 changes: 12 additions & 14 deletions common/blob_record.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,41 @@ package common
import (
"sync"

beacon_common "github.com/protolambda/zrnt/eth2/beacon/common"
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"github.com/protolambda/zrnt/eth2/beacon/common"
"github.com/protolambda/zrnt/eth2/beacon/deneb"
)

type BlobRecord struct {
sync.RWMutex
record map[beacon_common.Slot][]beacon_common.KZGCommitment
record map[common.Slot][]common.KZGCommitment
}

func NewBlobRecord() *BlobRecord {
return &BlobRecord{
record: make(map[beacon_common.Slot][]beacon_common.KZGCommitment),
record: make(map[common.Slot][]common.KZGCommitment),
}
}

func (br *BlobRecord) Add(slot beacon_common.Slot, blobSidecars ...*eth.BlobSidecar) {
func (br *BlobRecord) Add(slot common.Slot, blobSidecars ...*deneb.BlobSidecar) {
br.Lock()
defer br.Unlock()
for _, blobSidecar := range blobSidecars {
var kzg beacon_common.KZGCommitment
copy(kzg[:], blobSidecar.KzgCommitment)
br.record[slot] = append(br.record[slot], kzg)
br.record[slot] = append(br.record[slot], blobSidecar.KZGCommitment)
}
}

func (br *BlobRecord) GetSlots() []beacon_common.Slot {
func (br *BlobRecord) GetSlots() []common.Slot {
br.RLock()
defer br.RUnlock()
var slots []beacon_common.Slot
var slots []common.Slot
for slot := range br.record {
slots = append(slots, slot)
}
return slots
}

func GetAllSlots(brAll ...*BlobRecord) []beacon_common.Slot {
slots := make(map[beacon_common.Slot]struct{})
func GetAllSlots(brAll ...*BlobRecord) []common.Slot {
slots := make(map[common.Slot]struct{})
for _, br := range brAll {
br.RLock()
for slot := range br.record {
Expand All @@ -48,14 +46,14 @@ func GetAllSlots(brAll ...*BlobRecord) []beacon_common.Slot {
br.RUnlock()
}

var slotsSlice []beacon_common.Slot
var slotsSlice []common.Slot
for slot := range slots {
slotsSlice = append(slotsSlice, slot)
}
return slotsSlice
}

func (br *BlobRecord) Get(slot beacon_common.Slot) []beacon_common.KZGCommitment {
func (br *BlobRecord) Get(slot common.Slot) []common.KZGCommitment {
br.RLock()
defer br.RUnlock()
return br.record[slot]
Expand Down
Loading

0 comments on commit 9e2c13d

Please sign in to comment.