mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 15:37:56 -05:00
EIP-7549: validator client (#14158)
* EIP-7549: validator client * server code * tests * build fix * review * remove context * Revert "Auxiliary commit to revert individual files from 16fed79a1ae0bbe4a08cb9819c2785d6e34958dd" This reverts commit f59e1459f3f7561e0483bc8542110794951585c5.
This commit is contained in:
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/network/httputil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
validatorpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1/validator-client"
|
||||
"github.com/prysmaticlabs/prysm/v5/runtime/version"
|
||||
prysmTime "github.com/prysmaticlabs/prysm/v5/time"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
"go.opencensus.io/trace"
|
||||
@@ -79,52 +80,84 @@ func (v *validator) SubmitAggregateAndProof(ctx context.Context, slot primitives
|
||||
// https://github.com/ethereum/consensus-specs/blob/v0.9.3/specs/validator/0_beacon-chain-validator.md#broadcast-aggregate
|
||||
v.waitToSlotTwoThirds(ctx, slot)
|
||||
|
||||
res, err := v.validatorClient.SubmitAggregateSelectionProof(ctx, ðpb.AggregateSelectionRequest{
|
||||
postElectra := slots.ToEpoch(slot) >= params.BeaconConfig().ElectraForkEpoch
|
||||
|
||||
aggSelectionRequest := ðpb.AggregateSelectionRequest{
|
||||
Slot: slot,
|
||||
CommitteeIndex: duty.CommitteeIndex,
|
||||
PublicKey: pubKey[:],
|
||||
SlotSignature: slotSig,
|
||||
}, duty.ValidatorIndex, uint64(len(duty.Committee)))
|
||||
if err != nil {
|
||||
// handle grpc not found
|
||||
s, ok := status.FromError(err)
|
||||
grpcNotFound := ok && s.Code() == codes.NotFound
|
||||
// handle http not found
|
||||
jsonErr := &httputil.DefaultJsonError{}
|
||||
httpNotFound := errors.As(err, &jsonErr) && jsonErr.Code == http.StatusNotFound
|
||||
|
||||
if grpcNotFound || httpNotFound {
|
||||
log.WithField("slot", slot).WithError(err).Warn("No attestations to aggregate")
|
||||
} else {
|
||||
log.WithField("slot", slot).WithError(err).Error("Could not submit aggregate selection proof to beacon node")
|
||||
if v.emitAccountMetrics {
|
||||
ValidatorAggFailVec.WithLabelValues(fmtKey).Inc()
|
||||
}
|
||||
}
|
||||
var agg ethpb.AggregateAttAndProof
|
||||
if postElectra {
|
||||
res, err := v.validatorClient.SubmitAggregateSelectionProofElectra(ctx, aggSelectionRequest, duty.ValidatorIndex, uint64(len(duty.Committee)))
|
||||
if err != nil {
|
||||
v.handleSubmitAggSelectionProofError(err, slot, fmtKey)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
agg = res.AggregateAndProof
|
||||
} else {
|
||||
res, err := v.validatorClient.SubmitAggregateSelectionProof(ctx, aggSelectionRequest, duty.ValidatorIndex, uint64(len(duty.Committee)))
|
||||
if err != nil {
|
||||
v.handleSubmitAggSelectionProofError(err, slot, fmtKey)
|
||||
return
|
||||
}
|
||||
agg = res.AggregateAndProof
|
||||
}
|
||||
|
||||
sig, err := v.aggregateAndProofSig(ctx, pubKey, res.AggregateAndProof, slot)
|
||||
sig, err := v.aggregateAndProofSig(ctx, pubKey, agg, slot)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not sign aggregate and proof")
|
||||
return
|
||||
}
|
||||
_, err = v.validatorClient.SubmitSignedAggregateSelectionProof(ctx, ðpb.SignedAggregateSubmitRequest{
|
||||
SignedAggregateAndProof: ðpb.SignedAggregateAttestationAndProof{
|
||||
Message: res.AggregateAndProof,
|
||||
Signature: sig,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not submit signed aggregate and proof to beacon node")
|
||||
if v.emitAccountMetrics {
|
||||
ValidatorAggFailVec.WithLabelValues(fmtKey).Inc()
|
||||
|
||||
if postElectra {
|
||||
msg, ok := agg.(*ethpb.AggregateAttestationAndProofElectra)
|
||||
if !ok {
|
||||
log.Errorf("Message is not %T", ðpb.AggregateAttestationAndProofElectra{})
|
||||
if v.emitAccountMetrics {
|
||||
ValidatorAggFailVec.WithLabelValues(fmtKey).Inc()
|
||||
}
|
||||
return
|
||||
}
|
||||
_, err = v.validatorClient.SubmitSignedAggregateSelectionProofElectra(ctx, ðpb.SignedAggregateSubmitElectraRequest{
|
||||
SignedAggregateAndProof: ðpb.SignedAggregateAttestationAndProofElectra{
|
||||
Message: msg,
|
||||
Signature: sig,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not submit signed aggregate and proof to beacon node")
|
||||
if v.emitAccountMetrics {
|
||||
ValidatorAggFailVec.WithLabelValues(fmtKey).Inc()
|
||||
}
|
||||
return
|
||||
}
|
||||
} else {
|
||||
msg, ok := agg.(*ethpb.AggregateAttestationAndProof)
|
||||
if !ok {
|
||||
log.Errorf("Message is not %T", ðpb.AggregateAttestationAndProof{})
|
||||
if v.emitAccountMetrics {
|
||||
ValidatorAggFailVec.WithLabelValues(fmtKey).Inc()
|
||||
}
|
||||
return
|
||||
}
|
||||
_, err = v.validatorClient.SubmitSignedAggregateSelectionProof(ctx, ðpb.SignedAggregateSubmitRequest{
|
||||
SignedAggregateAndProof: ðpb.SignedAggregateAttestationAndProof{
|
||||
Message: msg,
|
||||
Signature: sig,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not submit signed aggregate and proof to beacon node")
|
||||
if v.emitAccountMetrics {
|
||||
ValidatorAggFailVec.WithLabelValues(fmtKey).Inc()
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := v.saveSubmittedAtt(res.AggregateAndProof.Aggregate.Data, pubKey[:], true); err != nil {
|
||||
if err := v.saveSubmittedAtt(agg.AggregateVal().GetData(), pubKey[:], true); err != nil {
|
||||
log.WithError(err).Error("Could not add aggregator indices to logs")
|
||||
if v.emitAccountMetrics {
|
||||
ValidatorAggFailVec.WithLabelValues(fmtKey).Inc()
|
||||
@@ -196,29 +229,61 @@ func (v *validator) waitToSlotTwoThirds(ctx context.Context, slot primitives.Slo
|
||||
|
||||
// This returns the signature of validator signing over aggregate and
|
||||
// proof object.
|
||||
func (v *validator) aggregateAndProofSig(ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte, agg *ethpb.AggregateAttestationAndProof, slot primitives.Slot) ([]byte, error) {
|
||||
func (v *validator) aggregateAndProofSig(ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte, agg ethpb.AggregateAttAndProof, slot primitives.Slot) ([]byte, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "validator.aggregateAndProofSig")
|
||||
defer span.End()
|
||||
|
||||
d, err := v.domainData(ctx, slots.ToEpoch(agg.Aggregate.Data.Slot), params.BeaconConfig().DomainAggregateAndProof[:])
|
||||
d, err := v.domainData(ctx, slots.ToEpoch(agg.AggregateVal().GetData().Slot), params.BeaconConfig().DomainAggregateAndProof[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var sig bls.Signature
|
||||
root, err := signing.ComputeSigningRoot(agg, d.SignatureDomain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sig, err = v.km.Sign(ctx, &validatorpb.SignRequest{
|
||||
|
||||
signRequest := &validatorpb.SignRequest{
|
||||
PublicKey: pubKey[:],
|
||||
SigningRoot: root[:],
|
||||
SignatureDomain: d.SignatureDomain,
|
||||
Object: &validatorpb.SignRequest_AggregateAttestationAndProof{AggregateAttestationAndProof: agg},
|
||||
SigningSlot: slot,
|
||||
})
|
||||
}
|
||||
if agg.Version() >= version.Electra {
|
||||
aggregate, ok := agg.(*ethpb.AggregateAttestationAndProofElectra)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("wrong aggregate type (expected %T, got %T)", ðpb.AggregateAttestationAndProofElectra{}, agg)
|
||||
}
|
||||
signRequest.Object = &validatorpb.SignRequest_AggregateAttestationAndProofElectra{AggregateAttestationAndProofElectra: aggregate}
|
||||
} else {
|
||||
aggregate, ok := agg.(*ethpb.AggregateAttestationAndProof)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("wrong aggregate type (expected %T, got %T)", ðpb.AggregateAttestationAndProof{}, agg)
|
||||
}
|
||||
signRequest.Object = &validatorpb.SignRequest_AggregateAttestationAndProof{AggregateAttestationAndProof: aggregate}
|
||||
}
|
||||
|
||||
sig, err := v.km.Sign(ctx, signRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sig.Marshal(), nil
|
||||
}
|
||||
|
||||
func (v *validator) handleSubmitAggSelectionProofError(err error, slot primitives.Slot, hexPubkey string) {
|
||||
// handle grpc not found
|
||||
s, ok := status.FromError(err)
|
||||
grpcNotFound := ok && s.Code() == codes.NotFound
|
||||
// handle http not found
|
||||
jsonErr := &httputil.DefaultJsonError{}
|
||||
httpNotFound := errors.As(err, &jsonErr) && jsonErr.Code == http.StatusNotFound
|
||||
|
||||
if grpcNotFound || httpNotFound {
|
||||
log.WithField("slot", slot).WithError(err).Warn("No attestations to aggregate")
|
||||
} else {
|
||||
log.WithField("slot", slot).WithError(err).Error("Could not submit aggregate selection proof to beacon node")
|
||||
if v.emitAccountMetrics {
|
||||
ValidatorAggFailVec.WithLabelValues(hexPubkey).Inc()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ func TestSubmitAggregateAndProof_SignFails(t *testing.T) {
|
||||
|
||||
func TestSubmitAggregateAndProof_Ok(t *testing.T) {
|
||||
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
|
||||
t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
|
||||
t.Run(fmt.Sprintf("Phase 0 (SlashingProtectionMinimal:%v)", isSlashingProtectionMinimal), func(t *testing.T) {
|
||||
validator, m, validatorKey, finish := setup(t, isSlashingProtectionMinimal)
|
||||
defer finish()
|
||||
var pubKey [fieldparams.BLSPubkeyLength]byte
|
||||
@@ -132,6 +132,59 @@ func TestSubmitAggregateAndProof_Ok(t *testing.T) {
|
||||
validator.SubmitAggregateAndProof(context.Background(), 0, pubKey)
|
||||
})
|
||||
}
|
||||
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
|
||||
t.Run(fmt.Sprintf("Electra (SlashingProtectionMinimal:%v)", isSlashingProtectionMinimal), func(t *testing.T) {
|
||||
electraForkEpoch := uint64(1)
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.ElectraForkEpoch = primitives.Epoch(electraForkEpoch)
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
validator, m, validatorKey, finish := setup(t, isSlashingProtectionMinimal)
|
||||
defer finish()
|
||||
var pubKey [fieldparams.BLSPubkeyLength]byte
|
||||
copy(pubKey[:], validatorKey.PublicKey().Marshal())
|
||||
validator.duties = ðpb.DutiesResponse{
|
||||
CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
|
||||
{
|
||||
PublicKey: validatorKey.PublicKey().Marshal(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
m.validatorClient.EXPECT().DomainData(
|
||||
gomock.Any(), // ctx
|
||||
gomock.Any(), // epoch
|
||||
).Return(ðpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
|
||||
|
||||
m.validatorClient.EXPECT().SubmitAggregateSelectionProofElectra(
|
||||
gomock.Any(), // ctx
|
||||
gomock.AssignableToTypeOf(ðpb.AggregateSelectionRequest{}),
|
||||
gomock.Any(),
|
||||
gomock.Any(),
|
||||
).Return(ðpb.AggregateSelectionElectraResponse{
|
||||
AggregateAndProof: ðpb.AggregateAttestationAndProofElectra{
|
||||
AggregatorIndex: 0,
|
||||
Aggregate: util.HydrateAttestationElectra(ðpb.AttestationElectra{
|
||||
AggregationBits: make([]byte, 1),
|
||||
}),
|
||||
SelectionProof: make([]byte, 96),
|
||||
},
|
||||
}, nil)
|
||||
|
||||
m.validatorClient.EXPECT().DomainData(
|
||||
gomock.Any(), // ctx
|
||||
gomock.Any(), // epoch
|
||||
).Return(ðpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
|
||||
|
||||
m.validatorClient.EXPECT().SubmitSignedAggregateSelectionProofElectra(
|
||||
gomock.Any(), // ctx
|
||||
gomock.AssignableToTypeOf(ðpb.SignedAggregateSubmitElectraRequest{}),
|
||||
).Return(ðpb.SignedAggregateSubmitResponse{AttestationDataRoot: make([]byte, 32)}, nil)
|
||||
|
||||
validator.SubmitAggregateAndProof(context.Background(), params.BeaconConfig().SlotsPerEpoch.Mul(electraForkEpoch), pubKey)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubmitAggregateAndProof_Distributed(t *testing.T) {
|
||||
@@ -236,7 +289,7 @@ func TestWaitForSlotTwoThird_DoneContext_ReturnsImmediately(t *testing.T) {
|
||||
|
||||
func TestAggregateAndProofSignature_CanSignValidSignature(t *testing.T) {
|
||||
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
|
||||
t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
|
||||
t.Run(fmt.Sprintf("Phase 0 (SlashingProtectionMinimal:%v)", isSlashingProtectionMinimal), func(t *testing.T) {
|
||||
validator, m, validatorKey, finish := setup(t, isSlashingProtectionMinimal)
|
||||
defer finish()
|
||||
|
||||
@@ -260,4 +313,35 @@ func TestAggregateAndProofSignature_CanSignValidSignature(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
|
||||
t.Run(fmt.Sprintf("Electra (SlashingProtectionMinimal:%v)", isSlashingProtectionMinimal), func(t *testing.T) {
|
||||
electraForkEpoch := uint64(1)
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.ElectraForkEpoch = primitives.Epoch(electraForkEpoch)
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
validator, m, validatorKey, finish := setup(t, isSlashingProtectionMinimal)
|
||||
defer finish()
|
||||
|
||||
var pubKey [fieldparams.BLSPubkeyLength]byte
|
||||
copy(pubKey[:], validatorKey.PublicKey().Marshal())
|
||||
m.validatorClient.EXPECT().DomainData(
|
||||
gomock.Any(), // ctx
|
||||
ðpb.DomainRequest{Epoch: 0, Domain: params.BeaconConfig().DomainAggregateAndProof[:]},
|
||||
).Return(ðpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
|
||||
|
||||
agg := ðpb.AggregateAttestationAndProofElectra{
|
||||
AggregatorIndex: 0,
|
||||
Aggregate: util.HydrateAttestationElectra(ðpb.AttestationElectra{
|
||||
AggregationBits: bitfield.NewBitlist(1),
|
||||
}),
|
||||
SelectionProof: make([]byte, 96),
|
||||
}
|
||||
sig, err := validator.aggregateAndProofSig(context.Background(), pubKey, agg, params.BeaconConfig().SlotsPerEpoch.Mul(electraForkEpoch) /* slot */)
|
||||
require.NoError(t, err)
|
||||
_, err = bls.SignatureFromBytes(sig)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,14 +85,9 @@ func (v *validator) SubmitAttestation(ctx context.Context, slot primitives.Slot,
|
||||
return
|
||||
}
|
||||
|
||||
indexedAtt := ðpb.IndexedAttestation{
|
||||
AttestingIndices: []uint64{uint64(duty.ValidatorIndex)},
|
||||
Data: data,
|
||||
}
|
||||
|
||||
_, signingRoot, err := v.domainAndSigningRoot(ctx, indexedAtt.Data)
|
||||
sig, _, err := v.signAtt(ctx, pubKey, data, slot)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get domain and signing root from attestation")
|
||||
log.WithError(err).Error("Could not sign attestation")
|
||||
if v.emitAccountMetrics {
|
||||
ValidatorAttestFailVec.WithLabelValues(fmtKey).Inc()
|
||||
}
|
||||
@@ -100,9 +95,26 @@ func (v *validator) SubmitAttestation(ctx context.Context, slot primitives.Slot,
|
||||
return
|
||||
}
|
||||
|
||||
sig, _, err := v.signAtt(ctx, pubKey, data, slot)
|
||||
postElectra := slots.ToEpoch(slot) >= params.BeaconConfig().ElectraForkEpoch
|
||||
|
||||
var indexedAtt ethpb.IndexedAtt
|
||||
if postElectra {
|
||||
indexedAtt = ðpb.IndexedAttestationElectra{
|
||||
AttestingIndices: []uint64{uint64(duty.ValidatorIndex)},
|
||||
Data: data,
|
||||
Signature: sig,
|
||||
}
|
||||
} else {
|
||||
indexedAtt = ðpb.IndexedAttestation{
|
||||
AttestingIndices: []uint64{uint64(duty.ValidatorIndex)},
|
||||
Data: data,
|
||||
Signature: sig,
|
||||
}
|
||||
}
|
||||
|
||||
_, signingRoot, err := v.domainAndSigningRoot(ctx, indexedAtt.GetData())
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not sign attestation")
|
||||
log.WithError(err).Error("Could not get domain and signing root from attestation")
|
||||
if v.emitAccountMetrics {
|
||||
ValidatorAttestFailVec.WithLabelValues(fmtKey).Inc()
|
||||
}
|
||||
@@ -127,25 +139,42 @@ func (v *validator) SubmitAttestation(ctx context.Context, slot primitives.Slot,
|
||||
return
|
||||
}
|
||||
|
||||
aggregationBitfield := bitfield.NewBitlist(uint64(len(duty.Committee)))
|
||||
aggregationBitfield.SetBitAt(indexInCommittee, true)
|
||||
attestation := ðpb.Attestation{
|
||||
Data: data,
|
||||
AggregationBits: aggregationBitfield,
|
||||
Signature: sig,
|
||||
// TODO: Extend to Electra
|
||||
phase0Att, ok := indexedAtt.(*ethpb.IndexedAttestation)
|
||||
if ok {
|
||||
// Send the attestation to the beacon node.
|
||||
if err := v.db.SlashableAttestationCheck(ctx, phase0Att, pubKey, signingRoot, v.emitAccountMetrics, ValidatorAttestFailVec); err != nil {
|
||||
log.WithError(err).Error("Failed attestation slashing protection check")
|
||||
log.WithFields(
|
||||
attestationLogFields(pubKey, indexedAtt),
|
||||
).Debug("Attempted slashable attestation details")
|
||||
tracing.AnnotateError(span, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Set the signature of the attestation and send it out to the beacon node.
|
||||
indexedAtt.Signature = sig
|
||||
if err := v.db.SlashableAttestationCheck(ctx, indexedAtt, pubKey, signingRoot, v.emitAccountMetrics, ValidatorAttestFailVec); err != nil {
|
||||
log.WithError(err).Error("Failed attestation slashing protection check")
|
||||
log.WithFields(
|
||||
attestationLogFields(pubKey, indexedAtt),
|
||||
).Debug("Attempted slashable attestation details")
|
||||
tracing.AnnotateError(span, err)
|
||||
return
|
||||
aggregationBitfield := bitfield.NewBitlist(uint64(len(duty.Committee)))
|
||||
aggregationBitfield.SetBitAt(indexInCommittee, true)
|
||||
committeeBits := primitives.NewAttestationCommitteeBits()
|
||||
|
||||
var attResp *ethpb.AttestResponse
|
||||
if postElectra {
|
||||
attestation := ðpb.AttestationElectra{
|
||||
Data: data,
|
||||
AggregationBits: aggregationBitfield,
|
||||
CommitteeBits: committeeBits,
|
||||
Signature: sig,
|
||||
}
|
||||
attestation.CommitteeBits.SetBitAt(uint64(req.CommitteeIndex), true)
|
||||
attResp, err = v.validatorClient.ProposeAttestationElectra(ctx, attestation)
|
||||
} else {
|
||||
attestation := ðpb.Attestation{
|
||||
Data: data,
|
||||
AggregationBits: aggregationBitfield,
|
||||
Signature: sig,
|
||||
}
|
||||
attResp, err = v.validatorClient.ProposeAttestation(ctx, attestation)
|
||||
}
|
||||
attResp, err := v.validatorClient.ProposeAttestation(ctx, attestation)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not submit attestation to beacon node")
|
||||
if v.emitAccountMetrics {
|
||||
@@ -167,12 +196,16 @@ func (v *validator) SubmitAttestation(ctx context.Context, slot primitives.Slot,
|
||||
span.AddAttributes(
|
||||
trace.Int64Attribute("slot", int64(slot)), // lint:ignore uintcast -- This conversion is OK for tracing.
|
||||
trace.StringAttribute("attestationHash", fmt.Sprintf("%#x", attResp.AttestationDataRoot)),
|
||||
trace.Int64Attribute("committeeIndex", int64(data.CommitteeIndex)),
|
||||
trace.StringAttribute("blockRoot", fmt.Sprintf("%#x", data.BeaconBlockRoot)),
|
||||
trace.Int64Attribute("justifiedEpoch", int64(data.Source.Epoch)),
|
||||
trace.Int64Attribute("targetEpoch", int64(data.Target.Epoch)),
|
||||
trace.StringAttribute("bitfield", fmt.Sprintf("%#x", aggregationBitfield)),
|
||||
trace.StringAttribute("aggregationBitfield", fmt.Sprintf("%#x", aggregationBitfield)),
|
||||
)
|
||||
if postElectra {
|
||||
span.AddAttributes(trace.StringAttribute("committeeBitfield", fmt.Sprintf("%#x", committeeBits)))
|
||||
} else {
|
||||
span.AddAttributes(trace.Int64Attribute("committeeIndex", int64(data.CommitteeIndex)))
|
||||
}
|
||||
|
||||
if v.emitAccountMetrics {
|
||||
ValidatorAttestSuccessVec.WithLabelValues(fmtKey).Inc()
|
||||
@@ -296,16 +329,16 @@ func (v *validator) waitOneThirdOrValidBlock(ctx context.Context, slot primitive
|
||||
}
|
||||
}
|
||||
|
||||
func attestationLogFields(pubKey [fieldparams.BLSPubkeyLength]byte, indexedAtt *ethpb.IndexedAttestation) logrus.Fields {
|
||||
func attestationLogFields(pubKey [fieldparams.BLSPubkeyLength]byte, indexedAtt ethpb.IndexedAtt) logrus.Fields {
|
||||
return logrus.Fields{
|
||||
"pubkey": fmt.Sprintf("%#x", pubKey),
|
||||
"slot": indexedAtt.Data.Slot,
|
||||
"committeeIndex": indexedAtt.Data.CommitteeIndex,
|
||||
"blockRoot": fmt.Sprintf("%#x", indexedAtt.Data.BeaconBlockRoot),
|
||||
"sourceEpoch": indexedAtt.Data.Source.Epoch,
|
||||
"sourceRoot": fmt.Sprintf("%#x", indexedAtt.Data.Source.Root),
|
||||
"targetEpoch": indexedAtt.Data.Target.Epoch,
|
||||
"targetRoot": fmt.Sprintf("%#x", indexedAtt.Data.Target.Root),
|
||||
"signature": fmt.Sprintf("%#x", indexedAtt.Signature),
|
||||
"slot": indexedAtt.GetData().Slot,
|
||||
"committeeIndex": indexedAtt.GetData().CommitteeIndex,
|
||||
"blockRoot": fmt.Sprintf("%#x", indexedAtt.GetData().BeaconBlockRoot),
|
||||
"sourceEpoch": indexedAtt.GetData().Source.Epoch,
|
||||
"sourceRoot": fmt.Sprintf("%#x", indexedAtt.GetData().Source.Root),
|
||||
"targetEpoch": indexedAtt.GetData().Target.Epoch,
|
||||
"targetRoot": fmt.Sprintf("%#x", indexedAtt.GetData().Target.Root),
|
||||
"signature": fmt.Sprintf("%#x", indexedAtt.GetSignature()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ func TestAttestToBlockHead_SubmitAttestation_RequestFailure(t *testing.T) {
|
||||
|
||||
func TestAttestToBlockHead_AttestsCorrectly(t *testing.T) {
|
||||
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
|
||||
t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
|
||||
t.Run(fmt.Sprintf("Phase 0 (SlashingProtectionMinimal:%v)", isSlashingProtectionMinimal), func(t *testing.T) {
|
||||
validator, m, validatorKey, finish := setup(t, isSlashingProtectionMinimal)
|
||||
defer finish()
|
||||
hook := logTest.NewGlobal()
|
||||
@@ -167,6 +167,89 @@ func TestAttestToBlockHead_AttestsCorrectly(t *testing.T) {
|
||||
root, err := signing.ComputeSigningRoot(expectedAttestation.Data, make([]byte, 32))
|
||||
require.NoError(t, err)
|
||||
|
||||
sig, err := validator.km.Sign(context.Background(), &validatorpb.SignRequest{
|
||||
PublicKey: validatorKey.PublicKey().Marshal(),
|
||||
SigningRoot: root[:],
|
||||
})
|
||||
require.NoError(t, err)
|
||||
expectedAttestation.Signature = sig.Marshal()
|
||||
if !reflect.DeepEqual(generatedAttestation, expectedAttestation) {
|
||||
t.Errorf("Incorrectly attested head, wanted %v, received %v", expectedAttestation, generatedAttestation)
|
||||
diff, _ := messagediff.PrettyDiff(expectedAttestation, generatedAttestation)
|
||||
t.Log(diff)
|
||||
}
|
||||
require.LogsDoNotContain(t, hook, "Could not")
|
||||
})
|
||||
}
|
||||
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
|
||||
t.Run(fmt.Sprintf("Electra (SlashingProtectionMinimal:%v)", isSlashingProtectionMinimal), func(t *testing.T) {
|
||||
electraForkEpoch := uint64(1)
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.ElectraForkEpoch = primitives.Epoch(electraForkEpoch)
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
validator, m, validatorKey, finish := setup(t, isSlashingProtectionMinimal)
|
||||
defer finish()
|
||||
hook := logTest.NewGlobal()
|
||||
validatorIndex := primitives.ValidatorIndex(7)
|
||||
committee := []primitives.ValidatorIndex{0, 3, 4, 2, validatorIndex, 6, 8, 9, 10}
|
||||
var pubKey [fieldparams.BLSPubkeyLength]byte
|
||||
copy(pubKey[:], validatorKey.PublicKey().Marshal())
|
||||
validator.duties = ðpb.DutiesResponse{CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
|
||||
{
|
||||
PublicKey: validatorKey.PublicKey().Marshal(),
|
||||
CommitteeIndex: 5,
|
||||
Committee: committee,
|
||||
ValidatorIndex: validatorIndex,
|
||||
},
|
||||
}}
|
||||
|
||||
beaconBlockRoot := bytesutil.ToBytes32([]byte("A"))
|
||||
targetRoot := bytesutil.ToBytes32([]byte("B"))
|
||||
sourceRoot := bytesutil.ToBytes32([]byte("C"))
|
||||
m.validatorClient.EXPECT().AttestationData(
|
||||
gomock.Any(), // ctx
|
||||
gomock.AssignableToTypeOf(ðpb.AttestationDataRequest{}),
|
||||
).Return(ðpb.AttestationData{
|
||||
BeaconBlockRoot: beaconBlockRoot[:],
|
||||
Target: ðpb.Checkpoint{Root: targetRoot[:]},
|
||||
Source: ðpb.Checkpoint{Root: sourceRoot[:], Epoch: 3},
|
||||
}, nil)
|
||||
|
||||
m.validatorClient.EXPECT().DomainData(
|
||||
gomock.Any(), // ctx
|
||||
gomock.Any(), // epoch
|
||||
).Times(2).Return(ðpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
|
||||
|
||||
var generatedAttestation *ethpb.AttestationElectra
|
||||
m.validatorClient.EXPECT().ProposeAttestationElectra(
|
||||
gomock.Any(), // ctx
|
||||
gomock.AssignableToTypeOf(ðpb.AttestationElectra{}),
|
||||
).Do(func(_ context.Context, att *ethpb.AttestationElectra) {
|
||||
generatedAttestation = att
|
||||
}).Return(ðpb.AttestResponse{}, nil /* error */)
|
||||
|
||||
validator.SubmitAttestation(context.Background(), params.BeaconConfig().SlotsPerEpoch.Mul(electraForkEpoch), pubKey)
|
||||
|
||||
aggregationBitfield := bitfield.NewBitlist(uint64(len(committee)))
|
||||
aggregationBitfield.SetBitAt(4, true)
|
||||
committeeBits := primitives.NewAttestationCommitteeBits()
|
||||
committeeBits.SetBitAt(5, true)
|
||||
expectedAttestation := ðpb.AttestationElectra{
|
||||
Data: ðpb.AttestationData{
|
||||
BeaconBlockRoot: beaconBlockRoot[:],
|
||||
Target: ðpb.Checkpoint{Root: targetRoot[:]},
|
||||
Source: ðpb.Checkpoint{Root: sourceRoot[:], Epoch: 3},
|
||||
},
|
||||
AggregationBits: aggregationBitfield,
|
||||
CommitteeBits: committeeBits,
|
||||
Signature: make([]byte, 96),
|
||||
}
|
||||
|
||||
root, err := signing.ComputeSigningRoot(expectedAttestation.Data, make([]byte, 32))
|
||||
require.NoError(t, err)
|
||||
|
||||
sig, err := validator.km.Sign(context.Background(), &validatorpb.SignRequest{
|
||||
PublicKey: validatorKey.PublicKey().Marshal(),
|
||||
SigningRoot: root[:],
|
||||
|
||||
@@ -154,6 +154,10 @@ func (c *beaconApiValidatorClient) ProposeAttestation(ctx context.Context, in *e
|
||||
})
|
||||
}
|
||||
|
||||
func (c *beaconApiValidatorClient) ProposeAttestationElectra(ctx context.Context, in *ethpb.AttestationElectra) (*ethpb.AttestResponse, error) {
|
||||
return nil, errors.New("ProposeAttestationElectra is not implemented")
|
||||
}
|
||||
|
||||
func (c *beaconApiValidatorClient) ProposeBeaconBlock(ctx context.Context, in *ethpb.GenericSignedBeaconBlock) (*ethpb.ProposeResponse, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "beacon-api.ProposeBeaconBlock")
|
||||
defer span.End()
|
||||
@@ -185,6 +189,10 @@ func (c *beaconApiValidatorClient) SubmitAggregateSelectionProof(ctx context.Con
|
||||
})
|
||||
}
|
||||
|
||||
func (c *beaconApiValidatorClient) SubmitAggregateSelectionProofElectra(ctx context.Context, in *ethpb.AggregateSelectionRequest, index primitives.ValidatorIndex, committeeLength uint64) (*ethpb.AggregateSelectionElectraResponse, error) {
|
||||
return nil, errors.New("SubmitAggregateSelectionProofElectra is not implemented")
|
||||
}
|
||||
|
||||
func (c *beaconApiValidatorClient) SubmitSignedAggregateSelectionProof(ctx context.Context, in *ethpb.SignedAggregateSubmitRequest) (*ethpb.SignedAggregateSubmitResponse, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "beacon-api.SubmitSignedAggregateSelectionProof")
|
||||
defer span.End()
|
||||
@@ -194,6 +202,10 @@ func (c *beaconApiValidatorClient) SubmitSignedAggregateSelectionProof(ctx conte
|
||||
})
|
||||
}
|
||||
|
||||
func (c *beaconApiValidatorClient) SubmitSignedAggregateSelectionProofElectra(ctx context.Context, in *ethpb.SignedAggregateSubmitElectraRequest) (*ethpb.SignedAggregateSubmitResponse, error) {
|
||||
return nil, errors.New("SubmitSignedAggregateSelectionProofElectra is not implemented")
|
||||
}
|
||||
|
||||
func (c *beaconApiValidatorClient) SubmitSignedContributionAndProof(ctx context.Context, in *ethpb.SignedContributionAndProof) (*empty.Empty, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "beacon-api.SubmitSignedContributionAndProof")
|
||||
defer span.End()
|
||||
|
||||
@@ -292,7 +292,7 @@ func TestGetValidatorCount(t *testing.T) {
|
||||
}
|
||||
|
||||
chainClient := mock.NewMockChainClient(ctrl)
|
||||
chainClient.EXPECT().ListValidators(
|
||||
chainClient.EXPECT().Validators(
|
||||
gomock.Any(),
|
||||
gomock.Any(),
|
||||
).Return(
|
||||
@@ -300,7 +300,7 @@ func TestGetValidatorCount(t *testing.T) {
|
||||
nil,
|
||||
)
|
||||
|
||||
chainClient.EXPECT().GetChainHead(
|
||||
chainClient.EXPECT().ChainHead(
|
||||
gomock.Any(),
|
||||
gomock.Any(),
|
||||
).Return(
|
||||
|
||||
@@ -71,6 +71,10 @@ func (c *grpcValidatorClient) ProposeAttestation(ctx context.Context, in *ethpb.
|
||||
return c.beaconNodeValidatorClient.ProposeAttestation(ctx, in)
|
||||
}
|
||||
|
||||
func (c *grpcValidatorClient) ProposeAttestationElectra(ctx context.Context, in *ethpb.AttestationElectra) (*ethpb.AttestResponse, error) {
|
||||
return c.beaconNodeValidatorClient.ProposeAttestationElectra(ctx, in)
|
||||
}
|
||||
|
||||
func (c *grpcValidatorClient) ProposeBeaconBlock(ctx context.Context, in *ethpb.GenericSignedBeaconBlock) (*ethpb.ProposeResponse, error) {
|
||||
return c.beaconNodeValidatorClient.ProposeBeaconBlock(ctx, in)
|
||||
}
|
||||
@@ -87,10 +91,18 @@ func (c *grpcValidatorClient) SubmitAggregateSelectionProof(ctx context.Context,
|
||||
return c.beaconNodeValidatorClient.SubmitAggregateSelectionProof(ctx, in)
|
||||
}
|
||||
|
||||
func (c *grpcValidatorClient) SubmitAggregateSelectionProofElectra(ctx context.Context, in *ethpb.AggregateSelectionRequest, _ primitives.ValidatorIndex, _ uint64) (*ethpb.AggregateSelectionElectraResponse, error) {
|
||||
return c.beaconNodeValidatorClient.SubmitAggregateSelectionProofElectra(ctx, in)
|
||||
}
|
||||
|
||||
func (c *grpcValidatorClient) SubmitSignedAggregateSelectionProof(ctx context.Context, in *ethpb.SignedAggregateSubmitRequest) (*ethpb.SignedAggregateSubmitResponse, error) {
|
||||
return c.beaconNodeValidatorClient.SubmitSignedAggregateSelectionProof(ctx, in)
|
||||
}
|
||||
|
||||
func (c *grpcValidatorClient) SubmitSignedAggregateSelectionProofElectra(ctx context.Context, in *ethpb.SignedAggregateSubmitElectraRequest) (*ethpb.SignedAggregateSubmitResponse, error) {
|
||||
return c.beaconNodeValidatorClient.SubmitSignedAggregateSelectionProofElectra(ctx, in)
|
||||
}
|
||||
|
||||
func (c *grpcValidatorClient) SubmitSignedContributionAndProof(ctx context.Context, in *ethpb.SignedContributionAndProof) (*empty.Empty, error) {
|
||||
return c.beaconNodeValidatorClient.SubmitSignedContributionAndProof(ctx, in)
|
||||
}
|
||||
|
||||
@@ -134,8 +134,11 @@ type ValidatorClient interface {
|
||||
FeeRecipientByPubKey(ctx context.Context, in *ethpb.FeeRecipientByPubKeyRequest) (*ethpb.FeeRecipientByPubKeyResponse, error)
|
||||
AttestationData(ctx context.Context, in *ethpb.AttestationDataRequest) (*ethpb.AttestationData, error)
|
||||
ProposeAttestation(ctx context.Context, in *ethpb.Attestation) (*ethpb.AttestResponse, error)
|
||||
ProposeAttestationElectra(ctx context.Context, in *ethpb.AttestationElectra) (*ethpb.AttestResponse, error)
|
||||
SubmitAggregateSelectionProof(ctx context.Context, in *ethpb.AggregateSelectionRequest, index primitives.ValidatorIndex, committeeLength uint64) (*ethpb.AggregateSelectionResponse, error)
|
||||
SubmitAggregateSelectionProofElectra(ctx context.Context, in *ethpb.AggregateSelectionRequest, _ primitives.ValidatorIndex, _ uint64) (*ethpb.AggregateSelectionElectraResponse, error)
|
||||
SubmitSignedAggregateSelectionProof(ctx context.Context, in *ethpb.SignedAggregateSubmitRequest) (*ethpb.SignedAggregateSubmitResponse, error)
|
||||
SubmitSignedAggregateSelectionProofElectra(ctx context.Context, in *ethpb.SignedAggregateSubmitElectraRequest) (*ethpb.SignedAggregateSubmitResponse, error)
|
||||
ProposeExit(ctx context.Context, in *ethpb.SignedVoluntaryExit) (*ethpb.ProposeExitResponse, error)
|
||||
SubscribeCommitteeSubnets(ctx context.Context, in *ethpb.CommitteeSubnetsSubscribeRequest, duties []*ethpb.DutiesResponse_Duty) (*empty.Empty, error)
|
||||
CheckDoppelGanger(ctx context.Context, in *ethpb.DoppelGangerRequest) (*ethpb.DoppelGangerResponse, error)
|
||||
|
||||
@@ -47,7 +47,7 @@ func TestValidator_HandleKeyReload(t *testing.T) {
|
||||
PublicKeys: [][]byte{inactive.pub[:], active.pub[:]},
|
||||
},
|
||||
).Return(resp, nil)
|
||||
prysmChainClient.EXPECT().GetValidatorCount(
|
||||
prysmChainClient.EXPECT().ValidatorCount(
|
||||
gomock.Any(),
|
||||
"head",
|
||||
[]validator2.Status{validator2.Active},
|
||||
@@ -83,7 +83,7 @@ func TestValidator_HandleKeyReload(t *testing.T) {
|
||||
PublicKeys: [][]byte{kp.pub[:]},
|
||||
},
|
||||
).Return(resp, nil)
|
||||
prysmChainClient.EXPECT().GetValidatorCount(
|
||||
prysmChainClient.EXPECT().ValidatorCount(
|
||||
gomock.Any(),
|
||||
"head",
|
||||
[]validator2.Status{validator2.Active},
|
||||
|
||||
@@ -315,7 +315,7 @@ func TestCanonicalHeadSlot_FailedRPC(t *testing.T) {
|
||||
chainClient: client,
|
||||
genesisTime: 1,
|
||||
}
|
||||
client.EXPECT().GetChainHead(
|
||||
client.EXPECT().ChainHead(
|
||||
gomock.Any(),
|
||||
gomock.Any(),
|
||||
).Return(nil, errors.New("failed"))
|
||||
@@ -330,7 +330,7 @@ func TestCanonicalHeadSlot_OK(t *testing.T) {
|
||||
v := validator{
|
||||
chainClient: client,
|
||||
}
|
||||
client.EXPECT().GetChainHead(
|
||||
client.EXPECT().ChainHead(
|
||||
gomock.Any(),
|
||||
gomock.Any(),
|
||||
).Return(ðpb.ChainHead{HeadSlot: 0}, nil)
|
||||
@@ -369,7 +369,7 @@ func TestWaitMultipleActivation_LogsActivationEpochOK(t *testing.T) {
|
||||
resp,
|
||||
nil,
|
||||
)
|
||||
prysmChainClient.EXPECT().GetValidatorCount(
|
||||
prysmChainClient.EXPECT().ValidatorCount(
|
||||
gomock.Any(),
|
||||
"head",
|
||||
[]validatorType.Status{validatorType.Active},
|
||||
|
||||
@@ -73,7 +73,7 @@ func TestWaitActivation_StreamSetupFails_AttemptsToReconnect(t *testing.T) {
|
||||
PublicKeys: [][]byte{kp.pub[:]},
|
||||
},
|
||||
).Return(clientStream, errors.New("failed stream")).Return(clientStream, nil)
|
||||
prysmChainClient.EXPECT().GetValidatorCount(
|
||||
prysmChainClient.EXPECT().ValidatorCount(
|
||||
gomock.Any(),
|
||||
"head",
|
||||
[]validatorType.Status{validatorType.Active},
|
||||
@@ -104,7 +104,7 @@ func TestWaitForActivation_ReceiveErrorFromStream_AttemptsReconnection(t *testin
|
||||
PublicKeys: [][]byte{kp.pub[:]},
|
||||
},
|
||||
).Return(clientStream, nil)
|
||||
prysmChainClient.EXPECT().GetValidatorCount(
|
||||
prysmChainClient.EXPECT().ValidatorCount(
|
||||
gomock.Any(),
|
||||
"head",
|
||||
[]validatorType.Status{validatorType.Active},
|
||||
@@ -143,7 +143,7 @@ func TestWaitActivation_LogsActivationEpochOK(t *testing.T) {
|
||||
PublicKeys: [][]byte{kp.pub[:]},
|
||||
},
|
||||
).Return(clientStream, nil)
|
||||
prysmChainClient.EXPECT().GetValidatorCount(
|
||||
prysmChainClient.EXPECT().ValidatorCount(
|
||||
gomock.Any(),
|
||||
"head",
|
||||
[]validatorType.Status{validatorType.Active},
|
||||
@@ -178,7 +178,7 @@ func TestWaitForActivation_Exiting(t *testing.T) {
|
||||
PublicKeys: [][]byte{kp.pub[:]},
|
||||
},
|
||||
).Return(clientStream, nil)
|
||||
prysmChainClient.EXPECT().GetValidatorCount(
|
||||
prysmChainClient.EXPECT().ValidatorCount(
|
||||
gomock.Any(),
|
||||
"head",
|
||||
[]validatorType.Status{validatorType.Active},
|
||||
@@ -221,7 +221,7 @@ func TestWaitForActivation_RefetchKeys(t *testing.T) {
|
||||
PublicKeys: [][]byte{kp.pub[:]},
|
||||
},
|
||||
).Return(clientStream, nil)
|
||||
prysmChainClient.EXPECT().GetValidatorCount(
|
||||
prysmChainClient.EXPECT().ValidatorCount(
|
||||
gomock.Any(),
|
||||
"head",
|
||||
[]validatorType.Status{validatorType.Active},
|
||||
@@ -278,7 +278,7 @@ func TestWaitForActivation_AccountsChanged(t *testing.T) {
|
||||
time.Sleep(time.Second * 2)
|
||||
return inactiveClientStream, nil
|
||||
})
|
||||
prysmChainClient.EXPECT().GetValidatorCount(
|
||||
prysmChainClient.EXPECT().ValidatorCount(
|
||||
gomock.Any(),
|
||||
"head",
|
||||
[]validatorType.Status{validatorType.Active},
|
||||
@@ -370,7 +370,7 @@ func TestWaitForActivation_AccountsChanged(t *testing.T) {
|
||||
time.Sleep(time.Second * 2)
|
||||
return inactiveClientStream, nil
|
||||
})
|
||||
prysmChainClient.EXPECT().GetValidatorCount(
|
||||
prysmChainClient.EXPECT().ValidatorCount(
|
||||
gomock.Any(),
|
||||
"head",
|
||||
[]validatorType.Status{validatorType.Active},
|
||||
@@ -431,7 +431,7 @@ func TestWaitActivation_NotAllValidatorsActivatedOK(t *testing.T) {
|
||||
gomock.Any(),
|
||||
gomock.Any(),
|
||||
).Return(clientStream, nil)
|
||||
prysmChainClient.EXPECT().GetValidatorCount(
|
||||
prysmChainClient.EXPECT().ValidatorCount(
|
||||
gomock.Any(),
|
||||
"head",
|
||||
[]validatorType.Status{validatorType.Active},
|
||||
|
||||
@@ -60,7 +60,7 @@ func TestGetBeaconStatus_OK(t *testing.T) {
|
||||
GenesisTime: timeStamp,
|
||||
DepositContractAddress: []byte("hello"),
|
||||
}, nil)
|
||||
chainClient.EXPECT().GetChainHead(
|
||||
chainClient.EXPECT().ChainHead(
|
||||
gomock.Any(), // ctx
|
||||
gomock.Any(),
|
||||
).Return(ðpb.ChainHead{
|
||||
@@ -230,7 +230,7 @@ func TestServer_GetValidators(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
beaconChainClient := validatormock.NewMockChainClient(ctrl)
|
||||
if tt.wantErr == "" {
|
||||
beaconChainClient.EXPECT().ListValidators(
|
||||
beaconChainClient.EXPECT().Validators(
|
||||
gomock.Any(), // ctx
|
||||
tt.expectedReq,
|
||||
).Return(tt.chainResp, nil)
|
||||
|
||||
Reference in New Issue
Block a user