Add sanity checks for bundle from builder (#13319)

* Add sanity checks for bundle from builder

* Add more checks to BlobsBundle.ToProto()

* Fix minor typo

* Fix tests & add new ones

* Add tests for ToProto

* Add "not" to error message

---------

Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
This commit is contained in:
Justin Traglia 2023-12-13 09:54:00 -06:00 committed by GitHub
parent ea59b1ec71
commit 52b9b65adb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 305 additions and 14 deletions

View File

@ -1021,6 +1021,16 @@ type BlobsBundle struct {
// ToProto returns a BlobsBundle Proto.
func (b BlobsBundle) ToProto() (*v1.BlobsBundle, error) {
if len(b.Blobs) > fieldparams.MaxBlobsPerBlock {
return nil, fmt.Errorf("blobs length %d is more than max %d", len(b.Blobs), fieldparams.MaxBlobsPerBlock)
}
if len(b.Commitments) != len(b.Blobs) {
return nil, fmt.Errorf("commitments length %d does not equal blobs length %d", len(b.Commitments), len(b.Blobs))
}
if len(b.Proofs) != len(b.Blobs) {
return nil, fmt.Errorf("proofs length %d does not equal blobs length %d", len(b.Proofs), len(b.Blobs))
}
commitments := make([][]byte, len(b.Commitments))
for i := range b.Commitments {
if len(b.Commitments[i]) != fieldparams.BLSPubkeyLength {
@ -1035,9 +1045,6 @@ func (b BlobsBundle) ToProto() (*v1.BlobsBundle, error) {
}
proofs[i] = bytesutil.SafeCopyBytes(b.Proofs[i])
}
if len(b.Blobs) > fieldparams.MaxBlobsPerBlock {
return nil, fmt.Errorf("blobs length %d is more than max %d", len(b.Blobs), fieldparams.MaxBlobsPerBlock)
}
blobs := make([][]byte, len(b.Blobs))
for i := range b.Blobs {
if len(b.Blobs[i]) != fieldparams.BlobLength {

View File

@ -628,6 +628,156 @@ var testExampleExecutionPayloadDeneb = fmt.Sprintf(`{
}
}`, hexutil.Encode(make([]byte, fieldparams.BlobLength)))
var testExampleExecutionPayloadDenebTooManyBlobs = fmt.Sprintf(`{
"version": "deneb",
"data": {
"execution_payload":{
"parent_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"fee_recipient": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
"state_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"receipts_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"logs_bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"prev_randao": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"block_number": "1",
"gas_limit": "1",
"gas_used": "1",
"timestamp": "1",
"extra_data": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"base_fee_per_gas": "452312848583266388373324160190187140051835877600158453279131187530910662656",
"block_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"transactions": [
"0x02f878831469668303f51d843b9ac9f9843b9aca0082520894c93269b73096998db66be0441e836d873535cb9c8894a19041886f000080c001a031cc29234036afbf9a1fb9476b463367cb1f957ac0b919b69bbc798436e604aaa018c4e9c3914eb27aadd0b91e10b18655739fcf8c1fc398763a9f1beecb8ddc86"
],
"withdrawals": [
{
"index": "1",
"validator_index": "1",
"address": "0xcf8e0d4e9587369b2301d0790347320302cc0943",
"amount": "1"
}
],
"blob_gas_used": "2",
"excess_blob_gas": "3"
},
"blobs_bundle": {
"commitments": [
"0x8dab030c51e16e84be9caab84ee3d0b8bbec1db4a0e4de76439da8424d9b957370a10a78851f97e4b54d2ce1ab0d686f"
],
"proofs": [
"0xb4021b0de10f743893d4f71e1bf830c019e832958efd6795baf2f83b8699a9eccc5dc99015d8d4d8ec370d0cc333c06a"
],
"blobs": [
"%s",
"%s",
"%s",
"%s",
"%s",
"%s",
"%s"
]
}
}
}`, hexutil.Encode(make([]byte, fieldparams.BlobLength)), // 1
hexutil.Encode(make([]byte, fieldparams.BlobLength)), // 2
hexutil.Encode(make([]byte, fieldparams.BlobLength)), // 3
hexutil.Encode(make([]byte, fieldparams.BlobLength)), // 4
hexutil.Encode(make([]byte, fieldparams.BlobLength)), // 5
hexutil.Encode(make([]byte, fieldparams.BlobLength)), // 6
hexutil.Encode(make([]byte, fieldparams.BlobLength)), // 7
)
var testExampleExecutionPayloadDenebDifferentCommitmentCount = fmt.Sprintf(`{
"version": "deneb",
"data": {
"execution_payload":{
"parent_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"fee_recipient": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
"state_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"receipts_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"logs_bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"prev_randao": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"block_number": "1",
"gas_limit": "1",
"gas_used": "1",
"timestamp": "1",
"extra_data": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"base_fee_per_gas": "452312848583266388373324160190187140051835877600158453279131187530910662656",
"block_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"transactions": [
"0x02f878831469668303f51d843b9ac9f9843b9aca0082520894c93269b73096998db66be0441e836d873535cb9c8894a19041886f000080c001a031cc29234036afbf9a1fb9476b463367cb1f957ac0b919b69bbc798436e604aaa018c4e9c3914eb27aadd0b91e10b18655739fcf8c1fc398763a9f1beecb8ddc86"
],
"withdrawals": [
{
"index": "1",
"validator_index": "1",
"address": "0xcf8e0d4e9587369b2301d0790347320302cc0943",
"amount": "1"
}
],
"blob_gas_used": "2",
"excess_blob_gas": "3"
},
"blobs_bundle": {
"commitments": [
"0x8dab030c51e16e84be9caab84ee3d0b8bbec1db4a0e4de76439da8424d9b957370a10a78851f97e4b54d2ce1ab0d686f",
"0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
],
"proofs": [
"0xb4021b0de10f743893d4f71e1bf830c019e832958efd6795baf2f83b8699a9eccc5dc99015d8d4d8ec370d0cc333c06a"
],
"blobs": [
"%s"
]
}
}
}`, hexutil.Encode(make([]byte, fieldparams.BlobLength)))
var testExampleExecutionPayloadDenebDifferentProofCount = fmt.Sprintf(`{
"version": "deneb",
"data": {
"execution_payload":{
"parent_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"fee_recipient": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
"state_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"receipts_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"logs_bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"prev_randao": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"block_number": "1",
"gas_limit": "1",
"gas_used": "1",
"timestamp": "1",
"extra_data": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"base_fee_per_gas": "452312848583266388373324160190187140051835877600158453279131187530910662656",
"block_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"transactions": [
"0x02f878831469668303f51d843b9ac9f9843b9aca0082520894c93269b73096998db66be0441e836d873535cb9c8894a19041886f000080c001a031cc29234036afbf9a1fb9476b463367cb1f957ac0b919b69bbc798436e604aaa018c4e9c3914eb27aadd0b91e10b18655739fcf8c1fc398763a9f1beecb8ddc86"
],
"withdrawals": [
{
"index": "1",
"validator_index": "1",
"address": "0xcf8e0d4e9587369b2301d0790347320302cc0943",
"amount": "1"
}
],
"blob_gas_used": "2",
"excess_blob_gas": "3"
},
"blobs_bundle": {
"commitments": [
"0x8dab030c51e16e84be9caab84ee3d0b8bbec1db4a0e4de76439da8424d9b957370a10a78851f97e4b54d2ce1ab0d686f"
],
"proofs": [
"0xb4021b0de10f743893d4f71e1bf830c019e832958efd6795baf2f83b8699a9eccc5dc99015d8d4d8ec370d0cc333c06a",
"0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
],
"blobs": [
"%s"
]
}
}
}`, hexutil.Encode(make([]byte, fieldparams.BlobLength)))
func TestExecutionPayloadResponseUnmarshal(t *testing.T) {
epr := &ExecPayloadResponse{}
require.NoError(t, json.Unmarshal([]byte(testExampleExecutionPayload), epr))
@ -1004,7 +1154,6 @@ func TestExecutionPayloadResponseCapellaToProto(t *testing.T) {
},
}
require.DeepEqual(t, expected, p)
}
func TestExecutionPayloadResponseDenebToProto(t *testing.T) {
@ -1083,7 +1232,27 @@ func TestExecutionPayloadResponseDenebToProto(t *testing.T) {
}
require.DeepEqual(t, blobsBundle, expectedBlobs)
}
func TestExecutionPayloadResponseDenebToProtoInvalidBlobCount(t *testing.T) {
hr := &ExecPayloadResponseDeneb{}
require.NoError(t, json.Unmarshal([]byte(testExampleExecutionPayloadDenebTooManyBlobs), hr))
_, _, err := hr.ToProto()
require.ErrorContains(t, "blobs length 7 is more than max 6", err)
}
func TestExecutionPayloadResponseDenebToProtoDifferentCommitmentCount(t *testing.T) {
hr := &ExecPayloadResponseDeneb{}
require.NoError(t, json.Unmarshal([]byte(testExampleExecutionPayloadDenebDifferentCommitmentCount), hr))
_, _, err := hr.ToProto()
require.ErrorContains(t, "commitments length 2 does not equal blobs length 1", err)
}
func TestExecutionPayloadResponseDenebToProtoDifferentProofCount(t *testing.T) {
hr := &ExecPayloadResponseDeneb{}
require.NoError(t, json.Unmarshal([]byte(testExampleExecutionPayloadDenebDifferentProofCount), hr))
_, _, err := hr.ToProto()
require.ErrorContains(t, "proofs length 2 does not equal blobs length 1", err)
}
func pbEth1Data() *eth.Eth1Data {

View File

@ -165,7 +165,7 @@ func WithFinalizedStateAtStartUp(st state.BeaconState) Option {
}
}
// WithClockSychronizer sets the ClockSetter/ClockWaiter values to be used by services that need to block until
// WithClockSynchronizer sets the ClockSetter/ClockWaiter values to be used by services that need to block until
// the genesis timestamp is known (ClockWaiter) or which determine the genesis timestamp (ClockSetter).
func WithClockSynchronizer(gs *startup.ClockSynchronizer) Option {
return func(s *Service) error {

View File

@ -1,6 +1,7 @@
package validator
import (
"bytes"
"context"
"fmt"
@ -126,9 +127,33 @@ func unblindBlobsSidecars(block interfaces.SignedBeaconBlock, bundle *enginev1.B
if err != nil {
return nil, err
}
body := block.Block().Body()
blockCommitments, err := body.BlobKzgCommitments()
if err != nil {
return nil, err
}
// Ensure there are equal counts of blobs/commitments/proofs.
if len(bundle.KzgCommitments) != len(bundle.Blobs) {
return nil, errors.New("mismatch commitments count")
}
if len(bundle.Proofs) != len(bundle.Blobs) {
return nil, errors.New("mismatch proofs count")
}
// Verify that commitments in the bundle match the block.
if len(bundle.KzgCommitments) != len(blockCommitments) {
return nil, errors.New("commitment count doesn't match block")
}
for i, commitment := range blockCommitments {
if !bytes.Equal(bundle.KzgCommitments[i], commitment) {
return nil, errors.New("commitment value doesn't match block")
}
}
sidecars := make([]*ethpb.BlobSidecar, len(bundle.Blobs))
for i, b := range bundle.Blobs {
proof, err := consensusblocks.MerkleProofKZGCommitment(block.Block().Body(), i)
proof, err := consensusblocks.MerkleProofKZGCommitment(body, i)
if err != nil {
return nil, err
}

View File

@ -27,6 +27,8 @@ func Test_unblindBuilderBlock(t *testing.T) {
pDeneb.BlobGasUsed = 789
denebblk, denebsidecars := util.GenerateTestDenebBlockWithSidecar(t, [32]byte{}, 0, fieldparams.MaxBlobsPerBlock)
denebCommitments, err := denebblk.Block().Body().BlobKzgCommitments()
require.NoError(t, err)
execution, err := denebblk.Block().Body().Execution()
require.NoError(t, err)
denebPayload, err := execution.PbDeneb()
@ -278,7 +280,7 @@ func Test_unblindBuilderBlock(t *testing.T) {
HasConfigured: true,
PayloadDeneb: denebPayload,
BlobBundle: &v1.BlobsBundle{
KzgCommitments: [][]byte{{'c', 0}, {'c', 1}, {'c', 2}, {'c', 3}, {'c', 4}, {'c', 5}},
KzgCommitments: denebCommitments,
Proofs: [][]byte{{'d', 0}, {'d', 1}, {'d', 2}, {'d', 3}, {'d', 4}, {'d', 5}},
Blobs: blobs,
},
@ -292,6 +294,94 @@ func Test_unblindBuilderBlock(t *testing.T) {
}(),
returnedBlobSidecars: denebsidecars,
},
{
name: "deneb mismatch commitments count",
blk: func() interfaces.SignedBeaconBlock {
blindedBlock, err := denebblk.ToBlinded()
require.NoError(t, err)
b, err := blindedBlock.PbBlindedDenebBlock()
require.NoError(t, err)
wb, err := blocks.NewSignedBeaconBlock(b)
require.NoError(t, err)
return wb
}(),
mock: &builderTest.MockBuilderService{
HasConfigured: true,
PayloadDeneb: denebPayload,
BlobBundle: &v1.BlobsBundle{
KzgCommitments: [][]byte{{'c', 0}, {'c', 1}, {'c', 2}, {'c', 3}, {'c', 4}},
Proofs: [][]byte{{'d', 0}, {'d', 1}, {'d', 2}, {'d', 3}, {'d', 4}, {'d', 5}},
Blobs: blobs,
},
},
err: "mismatch commitments count",
},
{
name: "deneb mismatch proofs count",
blk: func() interfaces.SignedBeaconBlock {
blindedBlock, err := denebblk.ToBlinded()
require.NoError(t, err)
b, err := blindedBlock.PbBlindedDenebBlock()
require.NoError(t, err)
wb, err := blocks.NewSignedBeaconBlock(b)
require.NoError(t, err)
return wb
}(),
mock: &builderTest.MockBuilderService{
HasConfigured: true,
PayloadDeneb: denebPayload,
BlobBundle: &v1.BlobsBundle{
KzgCommitments: [][]byte{{'c', 0}, {'c', 1}, {'c', 2}, {'c', 3}, {'c', 4}, {'c', 5}},
Proofs: [][]byte{{'d', 0}, {'d', 1}, {'d', 2}, {'d', 3}, {'d', 4}},
Blobs: blobs,
},
},
err: "mismatch proofs count",
},
{
name: "deneb different count commitments bundle vs block",
blk: func() interfaces.SignedBeaconBlock {
blindedBlock, err := denebblk.ToBlinded()
require.NoError(t, err)
b, err := blindedBlock.PbBlindedDenebBlock()
require.NoError(t, err)
wb, err := blocks.NewSignedBeaconBlock(b)
require.NoError(t, err)
return wb
}(),
mock: &builderTest.MockBuilderService{
HasConfigured: true,
PayloadDeneb: denebPayload,
BlobBundle: &v1.BlobsBundle{
KzgCommitments: [][]byte{{'c', 0}, {'c', 1}, {'c', 2}, {'c', 3}, {'c', 4}},
Proofs: [][]byte{{'d', 0}, {'d', 1}, {'d', 2}, {'d', 3}, {'d', 4}},
Blobs: blobs[:5],
},
},
err: "commitment count doesn't match block",
},
{
name: "deneb different value commitments bundle vs block",
blk: func() interfaces.SignedBeaconBlock {
blindedBlock, err := denebblk.ToBlinded()
require.NoError(t, err)
b, err := blindedBlock.PbBlindedDenebBlock()
require.NoError(t, err)
wb, err := blocks.NewSignedBeaconBlock(b)
require.NoError(t, err)
return wb
}(),
mock: &builderTest.MockBuilderService{
HasConfigured: true,
PayloadDeneb: denebPayload,
BlobBundle: &v1.BlobsBundle{
KzgCommitments: [][]byte{{'c', 0}, {'c', 1}, {'c', 2}, {'c', 3}, {'c', 4}, {'c', 5}},
Proofs: [][]byte{{'d', 0}, {'d', 1}, {'d', 2}, {'d', 3}, {'d', 4}, {'d', 5}},
Blobs: blobs,
},
},
err: "commitment value doesn't match block",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {

View File

@ -103,7 +103,7 @@ func (bv *BlobVerifier) recordResult(req Requirement, err *error) {
func (bv *BlobVerifier) BlobIndexInBounds() (err error) {
defer bv.recordResult(RequireBlobIndexInBounds, &err)
if bv.blob.Index >= fieldparams.MaxBlobsPerBlock {
log.WithFields(logging.BlobFields(bv.blob)).Debug("Sidecar index > MAX_BLOBS_PER_BLOCK")
log.WithFields(logging.BlobFields(bv.blob)).Debug("Sidecar index >= MAX_BLOBS_PER_BLOCK")
return ErrBlobIndexInvalid
}
return nil
@ -117,7 +117,7 @@ func (bv *BlobVerifier) SlotNotTooEarly() (err error) {
if bv.clock.CurrentSlot() == bv.blob.Slot() {
return nil
}
// subtract the max clock disparity from the start slot time
// Subtract the max clock disparity from the start slot time.
validAfter := bv.clock.SlotStart(bv.blob.Slot()).Add(-1 * params.BeaconNetworkConfig().MaximumGossipClockDisparity)
// If the difference between now and gt is greater than maximum clock disparity, the block is too far in the future.
if bv.clock.Now().Before(validAfter) {
@ -158,14 +158,14 @@ func (bv *BlobVerifier) ValidProposerSignature(ctx context.Context) (err error)
return nil
}
// retrieve the parent state to fallback to full verification
// Retrieve the parent state to fallback to full verification.
parent, err := bv.parentState(ctx)
if err != nil {
log.WithFields(logging.BlobFields(bv.blob)).WithError(err).Debug("could not replay parent state for blob signature verification")
return ErrInvalidProposerSignature
}
// Full verification, which will subsequently be cached for anything sharing the signature cache.
if err := bv.sc.VerifySignature(sd, parent); err != nil {
if err = bv.sc.VerifySignature(sd, parent); err != nil {
log.WithFields(logging.BlobFields(bv.blob)).WithError(err).Debug("signature verification failed")
return ErrInvalidProposerSignature
}
@ -225,7 +225,7 @@ func (bv *BlobVerifier) SidecarDescendsFromFinalized() (err error) {
// [REJECT] The sidecar's inclusion proof is valid as verified by verify_blob_sidecar_inclusion_proof(blob_sidecar).
func (bv *BlobVerifier) SidecarInclusionProven() (err error) {
defer bv.recordResult(RequireSidecarInclusionProven, &err)
if err := blocks.VerifyKZGInclusionProof(bv.blob); err != nil {
if err = blocks.VerifyKZGInclusionProof(bv.blob); err != nil {
log.WithError(err).WithFields(logging.BlobFields(bv.blob)).Debug("sidecar inclusion proof verification failed")
return ErrSidecarInclusionProofInvalid
}
@ -237,7 +237,7 @@ func (bv *BlobVerifier) SidecarInclusionProven() (err error) {
// verify_blob_kzg_proof(blob_sidecar.blob, blob_sidecar.kzg_commitment, blob_sidecar.kzg_proof).
func (bv *BlobVerifier) SidecarKzgProofVerified() (err error) {
defer bv.recordResult(RequireSidecarKzgProofVerified, &err)
if err := bv.verifyBlobCommitment(bv.blob); err != nil {
if err = bv.verifyBlobCommitment(bv.blob); err != nil {
log.WithError(err).WithFields(logging.BlobFields(bv.blob)).Debug("kzg commitment proof verification failed")
return ErrSidecarKzgProofInvalid
}

View File

@ -24,7 +24,7 @@ var (
errInvalidInclusionProof = errors.New("invalid KZG commitment inclusion proof")
)
// VerifyKZGIncusionProof verifies the Merkle proof in a Blob sidecar against
// VerifyKZGInclusionProof verifies the Merkle proof in a Blob sidecar against
// the beacon block body root.
func VerifyKZGInclusionProof(blob ROBlob) error {
if blob.SignedBlockHeader == nil {