Beacon api: produce block should skip mev-boost (#11488)

* Beacon api: produce block should skip mev-boost

* Update comments

* Additional test and comments

* Update beacon-chain/rpc/prysm/v1alpha1/validator/proposer_bellatrix.go

Co-authored-by: Preston Van Loon <preston@prysmaticlabs.com>

* Fix suggestion

Co-authored-by: Preston Van Loon <preston@prysmaticlabs.com>
This commit is contained in:
terencechain
2022-09-23 11:45:26 -07:00
committed by GitHub
parent 2ea66a8467
commit 211c5c2c5c
6 changed files with 738 additions and 593 deletions

View File

@@ -270,6 +270,12 @@ func (vs *Server) GetSyncCommitteeDuties(ctx context.Context, req *ethpbv2.SyncC
}
// ProduceBlockV2 requests the beacon node to produce a valid unsigned beacon block, which can then be signed by a proposer and submitted.
// By definition `/eth/v2/validator/blocks/{slot}`, does not produce block using mev-boost and relayer network.
// The following endpoint states that the returned object is a BeaconBlock, not a BlindedBeaconBlock. As such, the block must return a full ExecutionPayload:
// https://ethereum.github.io/beacon-APIs/?urls.primaryName=v2.3.0#/Validator/produceBlockV2
//
// To use mev-boost and relayer network. It's recommended to use the following endpoint:
// https://github.com/ethereum/beacon-APIs/blob/master/apis/validator/blinded_block.yaml
func (vs *Server) ProduceBlockV2(ctx context.Context, req *ethpbv1.ProduceBlockRequest) (*ethpbv2.ProduceBlockResponseV2, error) {
ctx, span := trace.StartSpan(ctx, "validator.ProduceBlockV2")
defer span.End()
@@ -283,6 +289,7 @@ func (vs *Server) ProduceBlockV2(ctx context.Context, req *ethpbv1.ProduceBlockR
Slot: req.Slot,
RandaoReveal: req.RandaoReveal,
Graffiti: req.Graffiti,
SkipMevBoost: true, // Skip mev-boost and relayer network
}
v1alpha1resp, err := vs.V1Alpha1Server.GetBeaconBlock(ctx, v1alpha1req)
if err != nil {
@@ -334,6 +341,12 @@ func (vs *Server) ProduceBlockV2(ctx context.Context, req *ethpbv1.ProduceBlockR
// ProduceBlockV2SSZ requests the beacon node to produce a valid unsigned beacon block, which can then be signed by a proposer and submitted.
//
// The produced block is in SSZ form.
// By definition `/eth/v2/validator/blocks/{slot}/ssz`, does not produce block using mev-boost and relayer network:
// The following endpoint states that the returned object is a BeaconBlock, not a BlindedBeaconBlock. As such, the block must return a full ExecutionPayload:
// https://ethereum.github.io/beacon-APIs/?urls.primaryName=v2.3.0#/Validator/produceBlockV2
//
// To use mev-boost and relayer network. It's recommended to use the following endpoint:
// https://github.com/ethereum/beacon-APIs/blob/master/apis/validator/blinded_block.yaml
func (vs *Server) ProduceBlockV2SSZ(ctx context.Context, req *ethpbv1.ProduceBlockRequest) (*ethpbv2.SSZContainer, error) {
ctx, span := trace.StartSpan(ctx, "validator.ProduceBlockV2SSZ")
defer span.End()
@@ -347,6 +360,7 @@ func (vs *Server) ProduceBlockV2SSZ(ctx context.Context, req *ethpbv1.ProduceBlo
Slot: req.Slot,
RandaoReveal: req.RandaoReveal,
Graffiti: req.Graffiti,
SkipMevBoost: true, // Skip mev-boost and relayer network
}
v1alpha1resp, err := vs.V1Alpha1Server.GetBeaconBlock(ctx, v1alpha1req)
if err != nil {

View File

@@ -935,6 +935,9 @@ func TestProduceBlockV2(t *testing.T) {
StateGen: stategen.New(db),
SyncCommitteePool: synccommittee.NewStore(),
ProposerSlotIndexCache: cache.NewProposerPayloadIDsCache(),
BlockBuilder: &builderTest.MockBuilderService{
HasConfigured: true,
},
}
proposerSlashings := make([]*ethpbalpha.ProposerSlashing, params.BeaconConfig().MaxProposerSlashings)
@@ -1004,6 +1007,9 @@ func TestProduceBlockV2(t *testing.T) {
Graffiti: graffiti[:],
}
v1Server.V1Alpha1Server.BeaconDB = db
require.NoError(t, v1Alpha1Server.BeaconDB.SaveRegistrationsByValidatorIDs(ctx, []types.ValidatorIndex{348},
[]*ethpbalpha.ValidatorRegistrationV1{{FeeRecipient: bytesutil.PadTo([]byte{}, fieldparams.FeeRecipientLength), Pubkey: bytesutil.PadTo([]byte{}, fieldparams.BLSPubkeyLength)}}))
resp, err := v1Server.ProduceBlockV2(ctx, req)
require.NoError(t, err)
assert.Equal(t, ethpbv2.Version_BELLATRIX, resp.Version)
@@ -1424,6 +1430,9 @@ func TestProduceBlockV2SSZ(t *testing.T) {
StateGen: stategen.New(db),
SyncCommitteePool: synccommittee.NewStore(),
ProposerSlotIndexCache: cache.NewProposerPayloadIDsCache(),
BlockBuilder: &builderTest.MockBuilderService{
HasConfigured: true,
},
}
proposerSlashings := make([]*ethpbalpha.ProposerSlashing, 1)
@@ -1490,6 +1499,9 @@ func TestProduceBlockV2SSZ(t *testing.T) {
Graffiti: graffiti[:],
}
v1Server.V1Alpha1Server.BeaconDB = db
require.NoError(t, v1Alpha1Server.BeaconDB.SaveRegistrationsByValidatorIDs(ctx, []types.ValidatorIndex{348},
[]*ethpbalpha.ValidatorRegistrationV1{{FeeRecipient: bytesutil.PadTo([]byte{}, fieldparams.FeeRecipientLength), Pubkey: bytesutil.PadTo([]byte{}, fieldparams.BLSPubkeyLength)}}))
resp, err := v1Server.ProduceBlockV2SSZ(ctx, req)
require.NoError(t, err)
assert.Equal(t, ethpbv2.Version_BELLATRIX, resp.Version)

View File

@@ -44,22 +44,24 @@ func (vs *Server) getBellatrixBeaconBlock(ctx context.Context, req *ethpb.BlockR
return nil, err
}
registered, err := vs.validatorRegistered(ctx, altairBlk.ProposerIndex)
if registered && err == nil {
builderReady, b, err := vs.GetAndBuildBlindBlock(ctx, altairBlk)
if err != nil {
// In the event of an error, the node should fall back to default execution engine for building block.
log.WithError(err).Error("Failed to build a block from external builder, falling " +
"back to local execution client")
builderGetPayloadMissCount.Inc()
} else if builderReady {
return b, nil
if !req.SkipMevBoost {
registered, err := vs.validatorRegistered(ctx, altairBlk.ProposerIndex)
if registered && err == nil {
builderReady, b, err := vs.GetAndBuildBlindBlock(ctx, altairBlk)
if err != nil {
// In the event of an error, the node should fall back to default execution engine for building block.
log.WithError(err).Error("Failed to build a block from external builder, falling " +
"back to local execution client")
builderGetPayloadMissCount.Inc()
} else if builderReady {
return b, nil
}
} else if err != nil {
log.WithError(err).WithFields(logrus.Fields{
"slot": req.Slot,
"validatorIndex": altairBlk.ProposerIndex,
}).Error("Could not determine validator has registered. Defaulting to local execution client")
}
} else if err != nil {
log.WithFields(logrus.Fields{
"slot": req.Slot,
"validatorIndex": altairBlk.ProposerIndex,
}).Errorf("Could not determine validator has registered. Default to local execution client: %v", err)
}
payload, err := vs.getExecutionPayload(ctx, req.Slot, altairBlk.ProposerIndex, bytesutil.ToBytes32(altairBlk.ParentRoot))
if err != nil {

View File

@@ -629,6 +629,111 @@ func TestServer_GetBellatrixBeaconBlock_HappyCase(t *testing.T) {
require.DeepEqual(t, emptyPayload, bellatrixBlk.Bellatrix.Body.ExecutionPayload) // Payload should equal.
}
func TestServer_GetBellatrixBeaconBlock_LocalProgressingWithBuilderSkipped(t *testing.T) {
db := dbTest.SetupDB(t)
ctx := context.Background()
hook := logTest.NewGlobal()
terminalBlockHash := bytesutil.PadTo([]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, 32)
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.BellatrixForkEpoch = 2
cfg.AltairForkEpoch = 1
cfg.TerminalBlockHash = common.BytesToHash(terminalBlockHash)
cfg.TerminalBlockHashActivationEpoch = 2
params.OverrideBeaconConfig(cfg)
beaconState, privKeys := util.DeterministicGenesisState(t, 64)
stateRoot, err := beaconState.HashTreeRoot(ctx)
require.NoError(t, err, "Could not hash genesis state")
genesis := consensusblocks.NewGenesisBlock(stateRoot[:])
wsb, err := blocks.NewSignedBeaconBlock(genesis)
require.NoError(t, err)
require.NoError(t, db.SaveBlock(ctx, wsb), "Could not save genesis block")
parentRoot, err := genesis.Block.HashTreeRoot()
require.NoError(t, err, "Could not get signing root")
require.NoError(t, db.SaveState(ctx, beaconState, parentRoot), "Could not save genesis state")
require.NoError(t, db.SaveHeadBlockRoot(ctx, parentRoot), "Could not save genesis state")
bellatrixSlot, err := slots.EpochStart(params.BeaconConfig().BellatrixForkEpoch)
require.NoError(t, err)
emptyPayload := &v1.ExecutionPayload{
ParentHash: make([]byte, fieldparams.RootLength),
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
StateRoot: make([]byte, fieldparams.RootLength),
ReceiptsRoot: make([]byte, fieldparams.RootLength),
LogsBloom: make([]byte, fieldparams.LogsBloomLength),
PrevRandao: make([]byte, fieldparams.RootLength),
BaseFeePerGas: make([]byte, fieldparams.RootLength),
BlockHash: make([]byte, fieldparams.RootLength),
}
var scBits [fieldparams.SyncAggregateSyncCommitteeBytesLength]byte
blk := &ethpb.SignedBeaconBlockBellatrix{
Block: &ethpb.BeaconBlockBellatrix{
Slot: bellatrixSlot + 1,
ParentRoot: parentRoot[:],
StateRoot: genesis.Block.StateRoot,
Body: &ethpb.BeaconBlockBodyBellatrix{
RandaoReveal: genesis.Block.Body.RandaoReveal,
Graffiti: genesis.Block.Body.Graffiti,
Eth1Data: genesis.Block.Body.Eth1Data,
SyncAggregate: &ethpb.SyncAggregate{SyncCommitteeBits: scBits[:], SyncCommitteeSignature: make([]byte, 96)},
ExecutionPayload: emptyPayload,
},
},
Signature: genesis.Signature,
}
blkRoot, err := blk.Block.HashTreeRoot()
require.NoError(t, err, "Could not get signing root")
require.NoError(t, db.SaveState(ctx, beaconState, blkRoot), "Could not save genesis state")
require.NoError(t, db.SaveHeadBlockRoot(ctx, blkRoot), "Could not save genesis state")
proposerServer := &Server{
HeadFetcher: &blockchainTest.ChainService{State: beaconState, Root: parentRoot[:], Optimistic: false},
TimeFetcher: &blockchainTest.ChainService{Genesis: time.Now()},
SyncChecker: &mockSync.Sync{IsSyncing: false},
BlockReceiver: &blockchainTest.ChainService{},
HeadUpdater: &blockchainTest.ChainService{},
ChainStartFetcher: &mockExecution.Chain{},
Eth1InfoFetcher: &mockExecution.Chain{},
MockEth1Votes: true,
AttPool: attestations.NewPool(),
SlashingsPool: slashings.NewPool(),
ExitPool: voluntaryexits.NewPool(),
StateGen: stategen.New(db),
SyncCommitteePool: synccommittee.NewStore(),
ExecutionEngineCaller: &mockExecution.EngineClient{
PayloadIDBytes: &v1.PayloadIDBytes{1},
ExecutionPayload: emptyPayload,
},
BeaconDB: db,
ProposerSlotIndexCache: cache.NewProposerPayloadIDsCache(),
BlockBuilder: &builderTest.MockBuilderService{},
}
proposerServer.ProposerSlotIndexCache.SetProposerAndPayloadIDs(17, 11, [8]byte{'a'}, parentRoot)
randaoReveal, err := util.RandaoReveal(beaconState, 0, privKeys)
require.NoError(t, err)
// Configure builder, this should fail if it's not local engine processing
proposerServer.BlockBuilder = &builderTest.MockBuilderService{HasConfigured: true, ErrGetHeader: errors.New("bad)")}
block, err := proposerServer.getBellatrixBeaconBlock(ctx, &ethpb.BlockRequest{
Slot: bellatrixSlot + 1,
RandaoReveal: randaoReveal,
SkipMevBoost: true,
})
require.NoError(t, err)
bellatrixBlk, ok := block.GetBlock().(*ethpb.GenericBeaconBlock_Bellatrix)
require.Equal(t, true, ok)
require.LogsContain(t, hook, "Computed state root")
require.DeepEqual(t, emptyPayload, bellatrixBlk.Bellatrix.Body.ExecutionPayload) // Payload should equal.
}
func TestServer_GetBellatrixBeaconBlock_BuilderCase(t *testing.T) {
db := dbTest.SetupDB(t)
ctx := context.Background()

File diff suppressed because it is too large Load Diff

View File

@@ -522,6 +522,8 @@ message BlockRequest {
// Validator's 32 byte graffiti message for the new block.
bytes graffiti = 3 [(ethereum.eth.ext.ssz_size) = "32"];
// Signal server to skip outsourcing block request from mev-boost/relayer so that returned block will always be a local block.
bool skip_mev_boost = 4;
}
message ProposeResponse {