Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core/validatorapi: check proposal data against consensus one #3292

Merged
merged 6 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions app/eth2wrap/eth2wrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -462,27 +462,27 @@ func TestLazyDomain(t *testing.T) {
{
name: "mainnet fork",
in: eth2util.Mainnet.GenesisForkVersionHex[2:],
expRes: "040000008c6ebbceb21209e6af5ab7db4a3027998c412c0eb0e15fbc1ee75617",
expRes: "04000000a39ec13dbafa3a331644f8d3a1513e57898fab998fec78f5ada4b8b0",
},
{
name: "goerli fork",
in: eth2util.Goerli.GenesisForkVersionHex[2:],
expRes: "04000000628941ef21d1fe8c7134720add10bb91e3b02c007e0046d2472c6695",
expRes: "04000000f1e25bda59286379f9a2b3ffeb090d650a4db4cfd089e1cc72388a33",
},
{
name: "gnosis fork",
in: eth2util.Gnosis.GenesisForkVersionHex[2:],
expRes: "04000000398beb768264920602d7d79f88da05cac0550ae4108753fd846408b5",
expRes: "040000007c97bfcba5d28a3cdef2ab010944574e387f4b3c7963c215eed87f32",
},
{
name: "sepolia fork",
in: eth2util.Sepolia.GenesisForkVersionHex[2:],
expRes: "040000007191d9b3c210dbffc7810b6ccb436c1b3897b6772452924b20f6f5f2",
expRes: "0400000005b54270938f654bd779212d3be2a63f806a4f58794d455393d8dad8",
},
{
name: "holesky fork",
in: eth2util.Holesky.GenesisForkVersionHex[2:],
expRes: "040000002b3e2c2d17a0d820f3099580a72d1bc743b17616ff7851f32aa303ad",
expRes: "0400000017e2dad36f1d3595152042a9ad23430197557e2e7e82bc7f7fc72972",
},
{
name: "unknown fork",
Expand Down
9 changes: 2 additions & 7 deletions app/eth2wrap/synthproposer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,10 @@ func TestSynthProposer(t *testing.T) {
activeVals++
return cached(ctx)
}
signedBeaconBlock := bmock.SignedBeaconBlock
bmock.SignedBeaconBlockFunc = func(ctx context.Context, blockID string) (*eth2spec.VersionedSignedBeaconBlock, error) {
opts := &eth2api.SignedBeaconBlockOpts{Block: blockID}
resp, err := signedBeaconBlock(ctx, opts)
if err != nil {
return nil, err
}
resp := testutil.RandomCapellaVersionedSignedBeaconBlock()

return resp.Data, nil
return resp, nil
}

eth2Cl := eth2wrap.WithSyntheticDuties(bmock)
Expand Down
134 changes: 128 additions & 6 deletions core/validatorapi/validatorapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@

eth2api "github.com/attestantio/go-eth2-client/api"
eth2v1 "github.com/attestantio/go-eth2-client/api/v1"
eth2spec "github.com/attestantio/go-eth2-client/spec"
"github.com/attestantio/go-eth2-client/spec/altair"
eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0"
ssz "github.com/ferranbt/fastssz"
"go.opentelemetry.io/otel/trace"

"github.com/obolnetwork/charon/app/errors"
Expand Down Expand Up @@ -394,19 +396,120 @@
return wrapResponse(proposal), nil
}

// propDataMatchesDuty checks that the VC-signed proposal data and prop are the same.
func propDataMatchesDuty(opts *eth2api.SubmitProposalOpts, prop *eth2api.VersionedProposal) error {
ourPropIdx, err := prop.ProposerIndex()
if err != nil {
return errors.Wrap(err, "cannot fetch validator index from dutydb proposal")
}

Check warning on line 404 in core/validatorapi/validatorapi.go

View check run for this annotation

Codecov / codecov/patch

core/validatorapi/validatorapi.go#L403-L404

Added lines #L403 - L404 were not covered by tests

vcPropIdx, err := opts.Proposal.ProposerIndex()
if err != nil {
return errors.Wrap(err, "cannot fetch validator index from VC proposal")
}

Check warning on line 409 in core/validatorapi/validatorapi.go

View check run for this annotation

Codecov / codecov/patch

core/validatorapi/validatorapi.go#L408-L409

Added lines #L408 - L409 were not covered by tests

if ourPropIdx != vcPropIdx {
return errors.New(
"dutydb and VC proposals have different index",
z.U64("vc", uint64(vcPropIdx)),
z.U64("dutydb", uint64(ourPropIdx)),
)
}

Check warning on line 417 in core/validatorapi/validatorapi.go

View check run for this annotation

Codecov / codecov/patch

core/validatorapi/validatorapi.go#L412-L417

Added lines #L412 - L417 were not covered by tests

if opts.Proposal.Blinded != prop.Blinded {
return errors.New(
"dutydb and VC proposals have different blinded value",
z.Bool("vc", opts.Proposal.Blinded),
z.Bool("dutydb", prop.Blinded),
)
}

Check warning on line 425 in core/validatorapi/validatorapi.go

View check run for this annotation

Codecov / codecov/patch

core/validatorapi/validatorapi.go#L420-L425

Added lines #L420 - L425 were not covered by tests

if opts.Proposal.Version != prop.Version {
return errors.New(
"dutydb and VC proposals have different version",
z.Str("vc", opts.Proposal.Version.String()),
z.Str("dutydb", prop.Version.String()),
)
}

Check warning on line 433 in core/validatorapi/validatorapi.go

View check run for this annotation

Codecov / codecov/patch

core/validatorapi/validatorapi.go#L428-L433

Added lines #L428 - L433 were not covered by tests

checkHashes := func(d1, d2 ssz.HashRoot) error {
ddb, err := d1.HashTreeRoot()
if err != nil {
return errors.Wrap(err, "hash tree root dutydb")
}

Check warning on line 439 in core/validatorapi/validatorapi.go

View check run for this annotation

Codecov / codecov/patch

core/validatorapi/validatorapi.go#L438-L439

Added lines #L438 - L439 were not covered by tests

if d2 == nil {
return errors.New("validator client proposal data for the associated dutydb proposal is nil")
}

Check warning on line 443 in core/validatorapi/validatorapi.go

View check run for this annotation

Codecov / codecov/patch

core/validatorapi/validatorapi.go#L442-L443

Added lines #L442 - L443 were not covered by tests

vc, err := d2.HashTreeRoot()
if err != nil {
return errors.Wrap(err, "hash tree root dutydb")
}

Check warning on line 448 in core/validatorapi/validatorapi.go

View check run for this annotation

Codecov / codecov/patch

core/validatorapi/validatorapi.go#L447-L448

Added lines #L447 - L448 were not covered by tests

if ddb != vc {
return errors.New("dutydb and VC proposal data have different hash tree root")
}

return nil
}

switch prop.Version {
case eth2spec.DataVersionPhase0:
return checkHashes(prop.Phase0, opts.Proposal.Phase0.Message)
case eth2spec.DataVersionAltair:
return checkHashes(prop.Altair, opts.Proposal.Altair.Message)

Check warning on line 461 in core/validatorapi/validatorapi.go

View check run for this annotation

Codecov / codecov/patch

core/validatorapi/validatorapi.go#L458-L461

Added lines #L458 - L461 were not covered by tests
case eth2spec.DataVersionBellatrix:
switch prop.Blinded {
case false:
return checkHashes(prop.Bellatrix, opts.Proposal.Bellatrix.Message)
case true:
return checkHashes(prop.BellatrixBlinded, opts.Proposal.BellatrixBlinded.Message)
}
case eth2spec.DataVersionCapella:
switch prop.Blinded {
case false:
return checkHashes(prop.Capella, opts.Proposal.Capella.Message)
case true:
return checkHashes(prop.CapellaBlinded, opts.Proposal.CapellaBlinded.Message)
}
case eth2spec.DataVersionDeneb:
switch prop.Blinded {
case false:
return checkHashes(prop.Deneb.Block, opts.Proposal.Deneb.SignedBlock.Message)
case true:
return checkHashes(prop.DenebBlinded, opts.Proposal.DenebBlinded.Message)
}
case eth2spec.DataVersionUnknown:
return errors.New("unexpected block version", z.Str("version", prop.Version.String()))

Check warning on line 484 in core/validatorapi/validatorapi.go

View check run for this annotation

Codecov / codecov/patch

core/validatorapi/validatorapi.go#L483-L484

Added lines #L483 - L484 were not covered by tests
}

return nil

Check warning on line 487 in core/validatorapi/validatorapi.go

View check run for this annotation

Codecov / codecov/patch

core/validatorapi/validatorapi.go#L487

Added line #L487 was not covered by tests
}

func (c Component) SubmitProposal(ctx context.Context, opts *eth2api.SubmitProposalOpts) error {
slot, err := opts.Proposal.Slot()
if err != nil {
return err
}

pubkey, err := c.getProposerPubkey(ctx, core.NewProposerDuty(uint64(slot)))
duty := core.NewProposerDuty(uint64(slot))

pubkey, err := c.getProposerPubkey(ctx, duty)
if err != nil {
return err
}

prop, err := c.awaitProposalFunc(ctx, uint64(slot))
if err != nil {
return errors.Wrap(err, "could not fetch block definition from dutydb")
}

Check warning on line 506 in core/validatorapi/validatorapi.go

View check run for this annotation

Codecov / codecov/patch

core/validatorapi/validatorapi.go#L505-L506

Added lines #L505 - L506 were not covered by tests

if err := propDataMatchesDuty(opts, prop); err != nil {
return errors.Wrap(err, "consensus proposal and VC-submitted one do not match")
}

// Save Partially Signed Block to ParSigDB
duty := core.NewProposerDuty(uint64(slot))
ctx = log.WithCtx(ctx, z.Any("duty", duty))

signedData, err := core.NewPartialVersionedSignedProposal(opts.Proposal, c.shareIdx)
Expand Down Expand Up @@ -440,15 +543,34 @@
return err
}

pubkey, err := c.getProposerPubkey(ctx, core.NewProposerDuty(uint64(slot)))
duty := core.NewProposerDuty(uint64(slot))
ctx = log.WithCtx(ctx, z.Any("duty", duty))

pubkey, err := c.getProposerPubkey(ctx, duty)
if err != nil {
return err
}

// Save Partially Signed Blinded Block to ParSigDB
duty := core.NewProposerDuty(uint64(slot))
ctx = log.WithCtx(ctx, z.Any("duty", duty))
prop, err := c.awaitProposalFunc(ctx, uint64(slot))
if err != nil {
return errors.Wrap(err, "could not fetch block definition from dutydb")
}

Check warning on line 557 in core/validatorapi/validatorapi.go

View check run for this annotation

Codecov / codecov/patch

core/validatorapi/validatorapi.go#L556-L557

Added lines #L556 - L557 were not covered by tests

if err := propDataMatchesDuty(&eth2api.SubmitProposalOpts{
Common: opts.Common,
Proposal: &eth2api.VersionedSignedProposal{
Version: opts.Proposal.Version,
Blinded: true,
BellatrixBlinded: opts.Proposal.Bellatrix,
CapellaBlinded: opts.Proposal.Capella,
DenebBlinded: opts.Proposal.Deneb,
},
BroadcastValidation: opts.BroadcastValidation,
}, prop); err != nil {
return errors.Wrap(err, "consensus proposal and VC-submitted one do not match")
}

// Save Partially Signed Blinded Block to ParSigDB
signedData, err := core.NewPartialVersionedSignedBlindedProposal(opts.Proposal, c.shareIdx)
if err != nil {
return err
Expand Down
Loading
Loading