mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-14 18:32:42 +00:00
Gloas local block building MVP (#8754)
The flow for local block building is 1. Create execution payload and bid 2. Construct beacon block 3. Sign beacon block and publish 4. Sign execution payload and publish This PR adds the beacon block v4 flow , GET payload envelope and POST payload envelope (local block building only). The spec for these endpoints can be found here: https://github.com/ethereum/beacon-APIs/pull/552 and is subject to change. We needed a way to store the unsigned execution payload envelope associated to the execution payload bid that was included in the block. I introduced a new cache that stores these unsigned execution payload envelopes. the GET payload envelope queries this cache directly so that a proposer, after publishing a block, can fetch the payload envelope + sign and publish it. I kept payload signing and publishing within the validators block service to keep things simple for now. The idea was to build out a block production MVP for devnet 0, try not to affect any non gloas code paths and build things out in such a way that it will be easy to deprecate pre-gloas code paths later on (for example block production v2 and v3). We will eventually need to track which beacon node was queried for the block so that we can later query it for the payload. But thats not needed for the devnet. Co-Authored-By: Eitan Seri- Levi <eserilev@gmail.com> Co-Authored-By: Michael Sproul <michael@sigmaprime.io> Co-Authored-By: Jimmy Chen <jchen.tc@gmail.com> Co-Authored-By: Eitan Seri-Levi <eserilev@ucsc.edu>
This commit is contained in:
@@ -6,14 +6,15 @@ use beacon_chain::{
|
||||
AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType, test_spec,
|
||||
},
|
||||
};
|
||||
use bls::{AggregateSignature, Keypair, PublicKeyBytes, Signature, SignatureBytes};
|
||||
use bls::{AggregateSignature, Keypair, PublicKeyBytes, SecretKey, Signature, SignatureBytes};
|
||||
use eth2::{
|
||||
BeaconNodeHttpClient, Error,
|
||||
Error::ServerMessage,
|
||||
Timeouts,
|
||||
mixin::{RequestAccept, ResponseForkName, ResponseOptional},
|
||||
types::{
|
||||
BlockId as CoreBlockId, ForkChoiceNode, ProduceBlockV3Response, StateId as CoreStateId, *,
|
||||
BlockId as CoreBlockId, ForkChoiceNode, ProduceBlockV3Response, ProduceBlockV4Metadata,
|
||||
StateId as CoreStateId, *,
|
||||
},
|
||||
};
|
||||
use execution_layer::expected_gas_limit;
|
||||
@@ -47,7 +48,8 @@ use tree_hash::TreeHash;
|
||||
use types::ApplicationDomain;
|
||||
use types::{
|
||||
Domain, EthSpec, ExecutionBlockHash, Hash256, MainnetEthSpec, RelativeEpoch, SelectionProof,
|
||||
SignedRoot, SingleAttestation, Slot, attestation::AttestationBase,
|
||||
SignedExecutionPayloadEnvelope, SignedRoot, SingleAttestation, Slot,
|
||||
attestation::AttestationBase, consts::gloas::BUILDER_INDEX_SELF_BUILD,
|
||||
};
|
||||
|
||||
type E = MainnetEthSpec;
|
||||
@@ -3726,6 +3728,229 @@ impl ApiTester {
|
||||
self
|
||||
}
|
||||
|
||||
/// Get the proposer secret key and randao reveal for the given slot.
|
||||
async fn proposer_setup(
|
||||
&self,
|
||||
slot: Slot,
|
||||
epoch: Epoch,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
) -> (SecretKey, SignatureBytes) {
|
||||
let proposer_pubkey_bytes = self
|
||||
.client
|
||||
.get_validator_duties_proposer(epoch)
|
||||
.await
|
||||
.unwrap()
|
||||
.data
|
||||
.into_iter()
|
||||
.find(|duty| duty.slot == slot)
|
||||
.map(|duty| duty.pubkey)
|
||||
.unwrap();
|
||||
let proposer_pubkey = (&proposer_pubkey_bytes).try_into().unwrap();
|
||||
|
||||
let sk = self
|
||||
.validator_keypairs()
|
||||
.iter()
|
||||
.find(|kp| kp.pk == proposer_pubkey)
|
||||
.map(|kp| kp.sk.clone())
|
||||
.unwrap();
|
||||
|
||||
let randao_reveal = {
|
||||
let domain =
|
||||
self.chain
|
||||
.spec
|
||||
.get_domain(epoch, Domain::Randao, fork, genesis_validators_root);
|
||||
let message = epoch.signing_root(domain);
|
||||
sk.sign(message).into()
|
||||
};
|
||||
|
||||
(sk, randao_reveal)
|
||||
}
|
||||
|
||||
/// Assert block metadata and verify the envelope cache.
|
||||
fn assert_v4_block_metadata(
|
||||
&self,
|
||||
block: &BeaconBlock<E>,
|
||||
metadata: &ProduceBlockV4Metadata,
|
||||
slot: Slot,
|
||||
) {
|
||||
assert_eq!(
|
||||
metadata.consensus_version,
|
||||
block.to_ref().fork_name(&self.chain.spec).unwrap()
|
||||
);
|
||||
assert!(!metadata.consensus_block_value.is_zero());
|
||||
|
||||
let block_root = block.tree_hash_root();
|
||||
let envelope = self
|
||||
.chain
|
||||
.pending_payload_envelopes
|
||||
.read()
|
||||
.get(slot)
|
||||
.cloned()
|
||||
.expect("envelope should exist in pending cache for local building");
|
||||
assert_eq!(envelope.beacon_block_root, block_root);
|
||||
assert_eq!(envelope.slot, slot);
|
||||
}
|
||||
|
||||
/// Assert envelope fields match the expected block root and slot.
|
||||
fn assert_envelope_fields(
|
||||
&self,
|
||||
envelope: &ExecutionPayloadEnvelope<E>,
|
||||
block_root: Hash256,
|
||||
slot: Slot,
|
||||
) {
|
||||
assert_eq!(envelope.beacon_block_root, block_root);
|
||||
assert_eq!(envelope.slot, slot);
|
||||
assert_eq!(envelope.builder_index, BUILDER_INDEX_SELF_BUILD);
|
||||
assert_ne!(envelope.state_root, Hash256::ZERO);
|
||||
}
|
||||
|
||||
/// Sign an execution payload envelope.
|
||||
fn sign_envelope(
|
||||
&self,
|
||||
envelope: ExecutionPayloadEnvelope<E>,
|
||||
sk: &SecretKey,
|
||||
epoch: Epoch,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
) -> SignedExecutionPayloadEnvelope<E> {
|
||||
let domain =
|
||||
self.chain
|
||||
.spec
|
||||
.get_domain(epoch, Domain::BeaconBuilder, fork, genesis_validators_root);
|
||||
let signing_root = envelope.signing_root(domain);
|
||||
let signature = sk.sign(signing_root);
|
||||
|
||||
SignedExecutionPayloadEnvelope {
|
||||
message: envelope,
|
||||
signature,
|
||||
}
|
||||
}
|
||||
|
||||
/// Test V4 block production (JSON). Only runs if Gloas is scheduled.
|
||||
pub async fn test_block_production_v4(self) -> Self {
|
||||
if !self.chain.spec.is_gloas_scheduled() {
|
||||
return self;
|
||||
}
|
||||
|
||||
let fork = self.chain.canonical_head.cached_head().head_fork();
|
||||
let genesis_validators_root = self.chain.genesis_validators_root;
|
||||
|
||||
for _ in 0..E::slots_per_epoch() * 3 {
|
||||
let slot = self.chain.slot().unwrap();
|
||||
let epoch = self.chain.epoch().unwrap();
|
||||
let fork_name = self.chain.spec.fork_name_at_slot::<E>(slot);
|
||||
|
||||
if !fork_name.gloas_enabled() {
|
||||
self.chain.slot_clock.set_slot(slot.as_u64() + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
let (sk, randao_reveal) = self
|
||||
.proposer_setup(slot, epoch, &fork, genesis_validators_root)
|
||||
.await;
|
||||
|
||||
let (response, metadata) = self
|
||||
.client
|
||||
.get_validator_blocks_v4::<E>(slot, &randao_reveal, None, None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
let block = response.data;
|
||||
|
||||
self.assert_v4_block_metadata(&block, &metadata, slot);
|
||||
|
||||
let envelope = self
|
||||
.client
|
||||
.get_validator_execution_payload_envelope::<E>(slot, BUILDER_INDEX_SELF_BUILD)
|
||||
.await
|
||||
.unwrap()
|
||||
.data;
|
||||
|
||||
self.assert_envelope_fields(&envelope, block.tree_hash_root(), slot);
|
||||
|
||||
let signed_block = block.sign(&sk, &fork, genesis_validators_root, &self.chain.spec);
|
||||
let signed_block_request =
|
||||
PublishBlockRequest::try_from(Arc::new(signed_block.clone())).unwrap();
|
||||
self.client
|
||||
.post_beacon_blocks_v2(&signed_block_request, None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(self.chain.head_beacon_block(), Arc::new(signed_block));
|
||||
|
||||
let signed_envelope =
|
||||
self.sign_envelope(envelope, &sk, epoch, &fork, genesis_validators_root);
|
||||
self.client
|
||||
.post_beacon_execution_payload_envelope(&signed_envelope, fork_name)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
self.chain.slot_clock.set_slot(slot.as_u64() + 1);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Test V4 block production (SSZ). Only runs if Gloas is scheduled.
|
||||
pub async fn test_block_production_v4_ssz(self) -> Self {
|
||||
if !self.chain.spec.is_gloas_scheduled() {
|
||||
return self;
|
||||
}
|
||||
|
||||
let fork = self.chain.canonical_head.cached_head().head_fork();
|
||||
let genesis_validators_root = self.chain.genesis_validators_root;
|
||||
|
||||
for _ in 0..E::slots_per_epoch() * 3 {
|
||||
let slot = self.chain.slot().unwrap();
|
||||
let epoch = self.chain.epoch().unwrap();
|
||||
let fork_name = self.chain.spec.fork_name_at_slot::<E>(slot);
|
||||
|
||||
if !fork_name.gloas_enabled() {
|
||||
self.chain.slot_clock.set_slot(slot.as_u64() + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
let (sk, randao_reveal) = self
|
||||
.proposer_setup(slot, epoch, &fork, genesis_validators_root)
|
||||
.await;
|
||||
|
||||
let (block, metadata) = self
|
||||
.client
|
||||
.get_validator_blocks_v4_ssz::<E>(slot, &randao_reveal, None, None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
self.assert_v4_block_metadata(&block, &metadata, slot);
|
||||
|
||||
let envelope = self
|
||||
.client
|
||||
.get_validator_execution_payload_envelope_ssz::<E>(slot, BUILDER_INDEX_SELF_BUILD)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
self.assert_envelope_fields(&envelope, block.tree_hash_root(), slot);
|
||||
|
||||
let signed_block = block.sign(&sk, &fork, genesis_validators_root, &self.chain.spec);
|
||||
let signed_block_request =
|
||||
PublishBlockRequest::try_from(Arc::new(signed_block.clone())).unwrap();
|
||||
self.client
|
||||
.post_beacon_blocks_v2_ssz(&signed_block_request, None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(self.chain.head_beacon_block(), Arc::new(signed_block));
|
||||
|
||||
let signed_envelope =
|
||||
self.sign_envelope(envelope, &sk, epoch, &fork, genesis_validators_root);
|
||||
self.client
|
||||
.post_beacon_execution_payload_envelope_ssz(&signed_envelope, fork_name)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
self.chain.slot_clock.set_slot(slot.as_u64() + 1);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn test_block_production_no_verify_randao(self) -> Self {
|
||||
for _ in 0..E::slots_per_epoch() {
|
||||
let slot = self.chain.slot().unwrap();
|
||||
@@ -7459,6 +7684,22 @@ async fn block_production_v3_ssz_with_skip_slots() {
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn block_production_v4() {
|
||||
ApiTester::new_with_hard_forks()
|
||||
.await
|
||||
.test_block_production_v4()
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn block_production_v4_ssz() {
|
||||
ApiTester::new_with_hard_forks()
|
||||
.await
|
||||
.test_block_production_v4_ssz()
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn blinded_block_production_full_payload_premerge() {
|
||||
ApiTester::new().await.test_blinded_block_production().await;
|
||||
|
||||
Reference in New Issue
Block a user