feat: add validator blob signing (#12730)

This commit is contained in:
terencechain
2023-08-14 08:27:21 -07:00
committed by Preston Van Loon
parent 6314f7fcbf
commit 96a9a6fc16
6 changed files with 187 additions and 10 deletions

View File

@@ -26,6 +26,10 @@ func NewSignedBeaconBlockFromGeneric(gb *eth.GenericSignedBeaconBlock) (interfac
return blocks.NewSignedBeaconBlock(bb.Capella)
case *eth.GenericSignedBeaconBlock_BlindedCapella:
return blocks.NewSignedBeaconBlock(bb.BlindedCapella)
case *eth.GenericSignedBeaconBlock_Deneb:
return blocks.NewSignedBeaconBlock(bb.Deneb.Block)
case *eth.GenericSignedBeaconBlock_BlindedDeneb:
return blocks.NewSignedBeaconBlock(bb.BlindedDeneb.Block)
// Generic Signed Beacon Block Deneb can't be used here as it is not a block, but block content with blobs
default:
return nil, errors.Wrapf(blocks.ErrUnsupportedSignedBeaconBlock, "unable to create block from type %T", gb)

View File

@@ -6,6 +6,7 @@ go_library(
"aggregate.go",
"attest.go",
"attest_protect.go",
"blob.go",
"key_reload.go",
"log.go",
"metrics.go",
@@ -101,6 +102,7 @@ go_test(
"aggregate_test.go",
"attest_protect_test.go",
"attest_test.go",
"blob_test.go",
"key_reload_test.go",
"metrics_test.go",
"propose_protect_test.go",
@@ -138,6 +140,7 @@ go_test(
"//proto/prysm/v1alpha1:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library",
"//runtime:go_default_library",
"//runtime/version:go_default_library",
"//testing/assert:go_default_library",
"//testing/mock:go_default_library",
"//testing/require:go_default_library",

39
validator/client/blob.go Normal file
View File

@@ -0,0 +1,39 @@
package client
import (
"context"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/signing"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/config/params"
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
validatorpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client"
"github.com/prysmaticlabs/prysm/v4/time/slots"
)
func (v *validator) signBlob(ctx context.Context, blob *ethpb.BlobSidecar, pubKey [fieldparams.BLSPubkeyLength]byte) ([]byte, error) {
epoch := slots.ToEpoch(blob.Slot)
domain, err := v.domainData(ctx, epoch, params.BeaconConfig().DomainBlobSidecar[:])
if err != nil {
return nil, errors.Wrap(err, domainDataErr)
}
if domain == nil {
return nil, errors.New(domainDataErr)
}
sr, err := signing.ComputeSigningRoot(blob, domain.SignatureDomain)
if err != nil {
return nil, errors.Wrap(err, signingRootErr)
}
sig, err := v.keyManager.Sign(ctx, &validatorpb.SignRequest{
PublicKey: pubKey[:],
SigningRoot: sr[:],
SignatureDomain: domain.SignatureDomain,
Object: &validatorpb.SignRequest_Blob{Blob: blob},
SigningSlot: blob.Slot,
})
if err != nil {
return nil, errors.Wrap(err, "could not sign block proposal")
}
return sig.Marshal(), nil
}

View File

@@ -0,0 +1,51 @@
package client
import (
"context"
"testing"
"github.com/golang/mock/gomock"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/signing"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/config/params"
"github.com/prysmaticlabs/prysm/v4/crypto/bls"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v4/testing/require"
)
func Test_validator_signBlob(t *testing.T) {
v, m, vk, finish := setup(t)
defer finish()
m.validatorClient.EXPECT().
DomainData(gomock.Any(), // ctx
&ethpb.DomainRequest{
Domain: params.BeaconConfig().DomainBlobSidecar[:],
}). // epoch
Return(&ethpb.DomainResponse{
SignatureDomain: bytesutil.PadTo([]byte("signatureDomain"), 32),
}, nil)
blob := &ethpb.BlobSidecar{
BlockRoot: bytesutil.PadTo([]byte("blockRoot"), 32),
Index: 1,
Slot: 2,
BlockParentRoot: bytesutil.PadTo([]byte("blockParentRoot"), 32),
ProposerIndex: 3,
Blob: bytesutil.PadTo([]byte("blob"), fieldparams.BlobLength),
KzgCommitment: bytesutil.PadTo([]byte("kzgCommitment"), 48),
KzgProof: bytesutil.PadTo([]byte("kzgPRoof"), 48),
}
ctx := context.Background()
sig, err := v.signBlob(ctx, blob, [48]byte(vk.PublicKey().Marshal()))
require.NoError(t, err)
pb, err := bls.PublicKeyFromBytes(vk.PublicKey().Marshal())
require.NoError(t, err)
signature, err := bls.SignatureFromBytes(sig)
require.NoError(t, err)
sr, err := signing.ComputeSigningRoot(blob, bytesutil.PadTo([]byte("signatureDomain"), 32))
require.NoError(t, err)
require.Equal(t, true, signature.Verify(pb, sr[:]))
}

View File

@@ -121,16 +121,46 @@ func (v *validator) ProposeBlock(ctx context.Context, slot primitives.Slot, pubK
return
}
// Propose and broadcast block via beacon node
proposal, err := blk.PbGenericBlock()
if err != nil {
log.WithError(err).Error("Failed to create proposal request")
if v.emitAccountMetrics {
ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc()
var genericSignedBlock *ethpb.GenericSignedBeaconBlock
if blk.Version() >= version.Deneb && !blk.IsBlinded() {
signedBlobs := make([]*ethpb.SignedBlobSidecar, len(b.GetDeneb().Blobs))
for _, blob := range b.GetDeneb().Blobs {
blobSig, err := v.signBlob(ctx, blob, pubKey)
if err != nil {
log.WithError(err).Error("Failed to sign blob")
return
}
signedBlobs = append(signedBlobs, &ethpb.SignedBlobSidecar{
Message: blob,
Signature: blobSig,
})
}
denebBlock, err := blk.PbDenebBlock()
if err != nil {
log.WithError(err).Error("Failed to get deneb block")
return
}
genericSignedBlock = &ethpb.GenericSignedBeaconBlock{
Block: &ethpb.GenericSignedBeaconBlock_Deneb{
Deneb: &ethpb.SignedBeaconBlockAndBlobsDeneb{
Block: denebBlock,
Blobs: signedBlobs,
},
},
}
} else {
// Propose and broadcast block via beacon node
genericSignedBlock, err = blk.PbGenericBlock()
if err != nil {
log.WithError(err).Error("Failed to create proposal request")
if v.emitAccountMetrics {
ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc()
}
return
}
return
}
blkResp, err := v.validatorClient.ProposeBeaconBlock(ctx, proposal)
blkResp, err := v.validatorClient.ProposeBeaconBlock(ctx, genericSignedBlock)
if err != nil {
log.WithField("blockSlot", slot).WithError(err).Error("Failed to propose block")
if v.emitAccountMetrics {

View File

@@ -20,6 +20,7 @@ import (
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
validatorpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client"
"github.com/prysmaticlabs/prysm/v4/runtime/version"
"github.com/prysmaticlabs/prysm/v4/testing/assert"
"github.com/prysmaticlabs/prysm/v4/testing/require"
"github.com/prysmaticlabs/prysm/v4/testing/util"
@@ -508,8 +509,9 @@ func TestProposeBlock_BroadcastsBlock_WithGraffiti(t *testing.T) {
func testProposeBlock(t *testing.T, graffiti []byte) {
tests := []struct {
name string
block *ethpb.GenericBeaconBlock
name string
block *ethpb.GenericBeaconBlock
version int
}{
{
name: "phase0",
@@ -583,6 +585,43 @@ func testProposeBlock(t *testing.T, graffiti []byte) {
},
},
},
{
name: "deneb block and blobs",
version: version.Deneb,
block: &ethpb.GenericBeaconBlock{
Block: &ethpb.GenericBeaconBlock_Deneb{
Deneb: func() *ethpb.BeaconBlockAndBlobsDeneb {
blk := util.NewBeaconBlockDeneb()
blk.Block.Body.Graffiti = graffiti
return &ethpb.BeaconBlockAndBlobsDeneb{
Block: blk.Block,
Blobs: []*ethpb.BlobSidecar{
{
BlockRoot: bytesutil.PadTo([]byte("blockRoot"), 32),
Index: 1,
Slot: 2,
BlockParentRoot: bytesutil.PadTo([]byte("blockParentRoot"), 32),
ProposerIndex: 3,
Blob: bytesutil.PadTo([]byte("blob"), fieldparams.BlobLength),
KzgCommitment: bytesutil.PadTo([]byte("kzgCommitment"), 48),
KzgProof: bytesutil.PadTo([]byte("kzgPRoof"), 48),
},
{
BlockRoot: bytesutil.PadTo([]byte("blockRoot1"), 32),
Index: 4,
Slot: 5,
BlockParentRoot: bytesutil.PadTo([]byte("blockParentRoot1"), 32),
ProposerIndex: 6,
Blob: bytesutil.PadTo([]byte("blob1"), fieldparams.BlobLength),
KzgCommitment: bytesutil.PadTo([]byte("kzgCommitment1"), 48),
KzgProof: bytesutil.PadTo([]byte("kzgPRoof1"), 48),
},
},
}
}(),
},
},
},
}
for _, tt := range tests {
@@ -617,6 +656,17 @@ func testProposeBlock(t *testing.T, graffiti []byte) {
var sentBlock interfaces.ReadOnlySignedBeaconBlock
var err error
if tt.version == version.Deneb {
m.validatorClient.EXPECT().DomainData(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
m.validatorClient.EXPECT().DomainData(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
}
m.validatorClient.EXPECT().ProposeBeaconBlock(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&ethpb.GenericSignedBeaconBlock{}),