mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-20 05:14:35 +00:00
blob production
This commit is contained in:
@@ -6,13 +6,16 @@ use crate::{
|
||||
};
|
||||
use crate::{http_metrics::metrics, validator_store::ValidatorStore};
|
||||
use environment::RuntimeContext;
|
||||
use eth2::types::Graffiti;
|
||||
use eth2::types::{Graffiti, VariableList};
|
||||
use slog::{crit, debug, error, info, trace, warn};
|
||||
use slot_clock::SlotClock;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
use types::{BlindedPayload, BlockType, EthSpec, ExecPayload, FullPayload, PublicKeyBytes, Slot};
|
||||
use types::{
|
||||
BlindedPayload, BlobsSidecar, BlockType, EthSpec, ExecPayload, ForkName, FullPayload,
|
||||
PublicKeyBytes, Slot,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum BlockError {
|
||||
@@ -316,126 +319,285 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockService<T, E> {
|
||||
let proposer_index = self.validator_store.validator_index(&validator_pubkey);
|
||||
let validator_pubkey_ref = &validator_pubkey;
|
||||
|
||||
// Request block from first responsive beacon node.
|
||||
let block = self
|
||||
.beacon_nodes
|
||||
.first_success(
|
||||
RequireSynced::No,
|
||||
OfflineOnFailure::Yes,
|
||||
|beacon_node| async move {
|
||||
let block = match Payload::block_type() {
|
||||
BlockType::Full => {
|
||||
let _get_timer = metrics::start_timer_vec(
|
||||
&metrics::BLOCK_SERVICE_TIMES,
|
||||
&[metrics::BEACON_BLOCK_HTTP_GET],
|
||||
);
|
||||
beacon_node
|
||||
.get_validator_blocks::<E, Payload>(
|
||||
slot,
|
||||
randao_reveal_ref,
|
||||
graffiti.as_ref(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
BlockError::Recoverable(format!(
|
||||
"Error from beacon node when producing block: {:?}",
|
||||
e
|
||||
))
|
||||
})?
|
||||
.data
|
||||
}
|
||||
BlockType::Blinded => {
|
||||
let _get_timer = metrics::start_timer_vec(
|
||||
&metrics::BLOCK_SERVICE_TIMES,
|
||||
&[metrics::BLINDED_BEACON_BLOCK_HTTP_GET],
|
||||
);
|
||||
beacon_node
|
||||
.get_validator_blinded_blocks::<E, Payload>(
|
||||
slot,
|
||||
randao_reveal_ref,
|
||||
graffiti.as_ref(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
BlockError::Recoverable(format!(
|
||||
"Error from beacon node when producing block: {:?}",
|
||||
e
|
||||
))
|
||||
})?
|
||||
.data
|
||||
}
|
||||
};
|
||||
match self.context.eth2_config.spec.fork_name_at_slot::<E>(slot) {
|
||||
ForkName::Base | ForkName::Altair | ForkName::Merge => {
|
||||
// Request block from first responsive beacon node.
|
||||
let block = self
|
||||
.beacon_nodes
|
||||
.first_success(
|
||||
RequireSynced::No,
|
||||
OfflineOnFailure::Yes,
|
||||
|beacon_node| async move {
|
||||
let block = match Payload::block_type() {
|
||||
BlockType::Full => {
|
||||
let _get_timer = metrics::start_timer_vec(
|
||||
&metrics::BLOCK_SERVICE_TIMES,
|
||||
&[metrics::BEACON_BLOCK_HTTP_GET],
|
||||
);
|
||||
beacon_node
|
||||
.get_validator_blocks::<E, Payload>(
|
||||
slot,
|
||||
randao_reveal_ref,
|
||||
graffiti.as_ref(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
BlockError::Recoverable(format!(
|
||||
"Error from beacon node when producing block: {:?}",
|
||||
e
|
||||
))
|
||||
})?
|
||||
.data
|
||||
}
|
||||
BlockType::Blinded => {
|
||||
let _get_timer = metrics::start_timer_vec(
|
||||
&metrics::BLOCK_SERVICE_TIMES,
|
||||
&[metrics::BLINDED_BEACON_BLOCK_HTTP_GET],
|
||||
);
|
||||
beacon_node
|
||||
.get_validator_blinded_blocks::<E, Payload>(
|
||||
slot,
|
||||
randao_reveal_ref,
|
||||
graffiti.as_ref(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
BlockError::Recoverable(format!(
|
||||
"Error from beacon node when producing block: {:?}",
|
||||
e
|
||||
))
|
||||
})?
|
||||
.data
|
||||
}
|
||||
};
|
||||
|
||||
if proposer_index != Some(block.proposer_index()) {
|
||||
return Err(BlockError::Recoverable(
|
||||
"Proposer index does not match block proposer. Beacon chain re-orged"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
if proposer_index != Some(block.proposer_index()) {
|
||||
return Err(BlockError::Recoverable(
|
||||
"Proposer index does not match block proposer. Beacon chain re-orged"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok::<_, BlockError>(block)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
Ok::<_, BlockError>(block)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let signed_block = self_ref
|
||||
.validator_store
|
||||
.sign_block::<Payload>(*validator_pubkey_ref, block, current_slot)
|
||||
.await
|
||||
.map_err(|e| BlockError::Recoverable(format!("Unable to sign block: {:?}", e)))?;
|
||||
let signed_block = self_ref
|
||||
.validator_store
|
||||
.sign_block::<Payload>(*validator_pubkey_ref, block, current_slot)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
BlockError::Recoverable(format!("Unable to sign block: {:?}", e))
|
||||
})?;
|
||||
|
||||
// Publish block with first available beacon node.
|
||||
self.beacon_nodes
|
||||
.first_success(
|
||||
RequireSynced::No,
|
||||
OfflineOnFailure::Yes,
|
||||
|beacon_node| async {
|
||||
match Payload::block_type() {
|
||||
BlockType::Full => {
|
||||
let _post_timer = metrics::start_timer_vec(
|
||||
&metrics::BLOCK_SERVICE_TIMES,
|
||||
&[metrics::BEACON_BLOCK_HTTP_POST],
|
||||
);
|
||||
beacon_node
|
||||
.post_beacon_blocks(&signed_block)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
BlockError::Irrecoverable(format!(
|
||||
"Error from beacon node when publishing block: {:?}",
|
||||
e
|
||||
))
|
||||
})?
|
||||
}
|
||||
BlockType::Blinded => {
|
||||
let _post_timer = metrics::start_timer_vec(
|
||||
&metrics::BLOCK_SERVICE_TIMES,
|
||||
&[metrics::BLINDED_BEACON_BLOCK_HTTP_POST],
|
||||
);
|
||||
beacon_node
|
||||
.post_beacon_blinded_blocks(&signed_block)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
BlockError::Irrecoverable(format!(
|
||||
"Error from beacon node when publishing block: {:?}",
|
||||
e
|
||||
))
|
||||
})?
|
||||
}
|
||||
}
|
||||
Ok::<_, BlockError>(())
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
info!(
|
||||
log,
|
||||
"Successfully published block";
|
||||
"block_type" => ?Payload::block_type(),
|
||||
"deposits" => signed_block.message().body().deposits().len(),
|
||||
"attestations" => signed_block.message().body().attestations().len(),
|
||||
"graffiti" => ?graffiti.map(|g| g.as_utf8_lossy()),
|
||||
"slot" => signed_block.slot().as_u64(),
|
||||
);
|
||||
}
|
||||
ForkName::Eip4844 => {
|
||||
if matches!(Payload::block_type(), BlockType::Blinded) {
|
||||
//FIXME(sean)
|
||||
crit!(
|
||||
log,
|
||||
"`--builder-payloads` not yet supported for EIP-4844 fork"
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Request block from first responsive beacon node.
|
||||
let block_and_blobs = self
|
||||
.beacon_nodes
|
||||
.first_success(
|
||||
RequireSynced::No,
|
||||
OfflineOnFailure::Yes,
|
||||
|beacon_node| async move {
|
||||
|
||||
let _get_timer = metrics::start_timer_vec(
|
||||
&metrics::BLOCK_SERVICE_TIMES,
|
||||
&[metrics::BEACON_BLOCK_HTTP_GET],
|
||||
);
|
||||
let block_and_blobs = beacon_node
|
||||
.get_validator_blocks_and_blobs::<E, Payload>(
|
||||
slot,
|
||||
randao_reveal_ref,
|
||||
graffiti.as_ref(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
BlockError::Recoverable(format!(
|
||||
"Error from beacon node when producing block: {:?}",
|
||||
e
|
||||
))
|
||||
})?
|
||||
.data;
|
||||
|
||||
if proposer_index != Some(block_and_blobs.block.proposer_index()) {
|
||||
return Err(BlockError::Recoverable(
|
||||
"Proposer index does not match block proposer. Beacon chain re-orged"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok::<_, BlockError>(block_and_blobs)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let blobs_sidecar = BlobsSidecar {
|
||||
beacon_block_root: block_and_blobs.block.canonical_root(),
|
||||
beacon_block_slot: block_and_blobs.block.slot(),
|
||||
blobs: VariableList::from(block_and_blobs.blobs),
|
||||
kzg_aggregate_proof: block_and_blobs.kzg_aggregate_proof,
|
||||
};
|
||||
|
||||
let block = block_and_blobs.block;
|
||||
let block_publish_future = async {
|
||||
let signed_block = self_ref
|
||||
.validator_store
|
||||
.sign_block::<Payload>(*validator_pubkey_ref, block, current_slot)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
BlockError::Recoverable(format!("Unable to sign block: {:?}", e))
|
||||
})?;
|
||||
|
||||
// Publish block with first available beacon node.
|
||||
self.beacon_nodes
|
||||
.first_success(
|
||||
RequireSynced::No,
|
||||
OfflineOnFailure::Yes,
|
||||
|beacon_node| async {
|
||||
let _post_timer = metrics::start_timer_vec(
|
||||
&metrics::BLOCK_SERVICE_TIMES,
|
||||
&[metrics::BEACON_BLOCK_HTTP_POST],
|
||||
);
|
||||
beacon_node
|
||||
.post_beacon_blocks(&signed_block)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
BlockError::Irrecoverable(format!(
|
||||
"Error from beacon node when publishing block: {:?}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
Ok::<_, BlockError>(())
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
info!(
|
||||
log,
|
||||
"Successfully published block";
|
||||
"block_type" => ?Payload::block_type(),
|
||||
"deposits" => signed_block.message().body().deposits().len(),
|
||||
"attestations" => signed_block.message().body().attestations().len(),
|
||||
"graffiti" => ?graffiti.map(|g| g.as_utf8_lossy()),
|
||||
"slot" => signed_block.slot().as_u64(),
|
||||
);
|
||||
|
||||
// Publish block with first available beacon node.
|
||||
self.beacon_nodes
|
||||
.first_success(
|
||||
RequireSynced::No,
|
||||
OfflineOnFailure::Yes,
|
||||
|beacon_node| async {
|
||||
match Payload::block_type() {
|
||||
BlockType::Full => {
|
||||
let _post_timer = metrics::start_timer_vec(
|
||||
&metrics::BLOCK_SERVICE_TIMES,
|
||||
&[metrics::BEACON_BLOCK_HTTP_POST],
|
||||
);
|
||||
beacon_node
|
||||
.post_beacon_blocks(&signed_block)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
BlockError::Irrecoverable(format!(
|
||||
"Error from beacon node when publishing block: {:?}",
|
||||
e
|
||||
))
|
||||
})?
|
||||
}
|
||||
BlockType::Blinded => {
|
||||
let _post_timer = metrics::start_timer_vec(
|
||||
&metrics::BLOCK_SERVICE_TIMES,
|
||||
&[metrics::BLINDED_BEACON_BLOCK_HTTP_POST],
|
||||
);
|
||||
beacon_node
|
||||
.post_beacon_blinded_blocks(&signed_block)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
BlockError::Irrecoverable(format!(
|
||||
"Error from beacon node when publishing block: {:?}",
|
||||
e
|
||||
))
|
||||
})?
|
||||
}
|
||||
}
|
||||
Ok::<_, BlockError>(())
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
};
|
||||
|
||||
let blob_publish_future = async {
|
||||
let signed_blobs = self_ref
|
||||
.validator_store
|
||||
.sign_blobs(*validator_pubkey_ref, blobs_sidecar, current_slot)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
BlockError::Recoverable(format!("Unable to sign blob: {:?}", e))
|
||||
})?;
|
||||
|
||||
// Publish block with first available beacon node.
|
||||
self.beacon_nodes
|
||||
.first_success(
|
||||
RequireSynced::No,
|
||||
OfflineOnFailure::Yes,
|
||||
|beacon_node| async {
|
||||
let _post_timer = metrics::start_timer_vec(
|
||||
&metrics::BLOCK_SERVICE_TIMES,
|
||||
&[metrics::BEACON_BLOB_HTTP_POST],
|
||||
);
|
||||
beacon_node.post_beacon_blobs(&signed_blobs).await.map_err(
|
||||
|e| {
|
||||
BlockError::Irrecoverable(format!(
|
||||
"Error from beacon node when publishing blob: {:?}",
|
||||
e
|
||||
))
|
||||
},
|
||||
)?;
|
||||
Ok::<_, BlockError>(())
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
info!(
|
||||
log,
|
||||
"Successfully published blobs";
|
||||
"block_type" => ?Payload::block_type(),
|
||||
"slot" => signed_blobs.message.beacon_block_slot.as_u64(),
|
||||
"block_root" => ?signed_blobs.message.beacon_block_root,
|
||||
"blobs_len" => signed_blobs.message.blobs.len(),
|
||||
);
|
||||
|
||||
Ok::<_, BlockError>(())
|
||||
};
|
||||
|
||||
let (res_block, res_blob) = tokio::join!(block_publish_future, blob_publish_future);
|
||||
|
||||
res_block?;
|
||||
res_blob?;
|
||||
}
|
||||
}
|
||||
|
||||
info!(
|
||||
log,
|
||||
"Successfully published block";
|
||||
"block_type" => ?Payload::block_type(),
|
||||
"deposits" => signed_block.message().body().deposits().len(),
|
||||
"attestations" => signed_block.message().body().attestations().len(),
|
||||
"graffiti" => ?graffiti.map(|g| g.as_utf8_lossy()),
|
||||
"slot" => signed_block.slot().as_u64(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ pub const BEACON_BLOCK: &str = "beacon_block";
|
||||
pub const BEACON_BLOCK_HTTP_GET: &str = "beacon_block_http_get";
|
||||
pub const BLINDED_BEACON_BLOCK_HTTP_GET: &str = "blinded_beacon_block_http_get";
|
||||
pub const BEACON_BLOCK_HTTP_POST: &str = "beacon_block_http_post";
|
||||
pub const BEACON_BLOB_HTTP_POST: &str = "beacon_blob_http_post";
|
||||
pub const BLINDED_BEACON_BLOCK_HTTP_POST: &str = "blinded_beacon_block_http_post";
|
||||
pub const ATTESTATIONS: &str = "attestations";
|
||||
pub const ATTESTATIONS_HTTP_GET: &str = "attestations_http_get";
|
||||
@@ -57,6 +58,11 @@ lazy_static::lazy_static! {
|
||||
"Total count of attempted block signings",
|
||||
&["status"]
|
||||
);
|
||||
pub static ref SIGNED_BLOBS_TOTAL: Result<IntCounterVec> = try_create_int_counter_vec(
|
||||
"vc_signed_beacon_blobs_total",
|
||||
"Total count of attempted blob signings",
|
||||
&["status"]
|
||||
);
|
||||
pub static ref SIGNED_ATTESTATIONS_TOTAL: Result<IntCounterVec> = try_create_int_counter_vec(
|
||||
"vc_signed_attestations_total",
|
||||
"Total count of attempted Attestation signings",
|
||||
|
||||
@@ -37,6 +37,7 @@ pub enum Error {
|
||||
pub enum SignableMessage<'a, T: EthSpec, Payload: ExecPayload<T> = FullPayload<T>> {
|
||||
RandaoReveal(Epoch),
|
||||
BeaconBlock(&'a BeaconBlock<T, Payload>),
|
||||
BlobsSidecar(&'a BlobsSidecar<T>),
|
||||
AttestationData(&'a AttestationData),
|
||||
SignedAggregateAndProof(&'a AggregateAndProof<T>),
|
||||
SelectionProof(Slot),
|
||||
@@ -58,6 +59,7 @@ impl<'a, T: EthSpec, Payload: ExecPayload<T>> SignableMessage<'a, T, Payload> {
|
||||
match self {
|
||||
SignableMessage::RandaoReveal(epoch) => epoch.signing_root(domain),
|
||||
SignableMessage::BeaconBlock(b) => b.signing_root(domain),
|
||||
SignableMessage::BlobsSidecar(b) => b.signing_root(domain),
|
||||
SignableMessage::AttestationData(a) => a.signing_root(domain),
|
||||
SignableMessage::SignedAggregateAndProof(a) => a.signing_root(domain),
|
||||
SignableMessage::SelectionProof(slot) => slot.signing_root(domain),
|
||||
@@ -180,6 +182,7 @@ impl SigningMethod {
|
||||
Web3SignerObject::RandaoReveal { epoch }
|
||||
}
|
||||
SignableMessage::BeaconBlock(block) => Web3SignerObject::beacon_block(block)?,
|
||||
SignableMessage::BlobsSidecar(blob) => Web3SignerObject::BlobsSidecar(blob),
|
||||
SignableMessage::AttestationData(a) => Web3SignerObject::Attestation(a),
|
||||
SignableMessage::SignedAggregateAndProof(a) => {
|
||||
Web3SignerObject::AggregateAndProof(a)
|
||||
|
||||
@@ -11,6 +11,7 @@ pub enum MessageType {
|
||||
AggregateAndProof,
|
||||
Attestation,
|
||||
BlockV2,
|
||||
BlobsSidecar,
|
||||
Deposit,
|
||||
RandaoReveal,
|
||||
VoluntaryExit,
|
||||
@@ -50,6 +51,8 @@ pub enum Web3SignerObject<'a, T: EthSpec, Payload: ExecPayload<T>> {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
block_header: Option<BeaconBlockHeader>,
|
||||
},
|
||||
//FIXME(sean) just guessing here
|
||||
BlobsSidecar(&'a BlobsSidecar<T>),
|
||||
#[allow(dead_code)]
|
||||
Deposit {
|
||||
pubkey: PublicKeyBytes,
|
||||
@@ -105,6 +108,7 @@ impl<'a, T: EthSpec, Payload: ExecPayload<T>> Web3SignerObject<'a, T, Payload> {
|
||||
Web3SignerObject::AggregateAndProof(_) => MessageType::AggregateAndProof,
|
||||
Web3SignerObject::Attestation(_) => MessageType::Attestation,
|
||||
Web3SignerObject::BeaconBlock { .. } => MessageType::BlockV2,
|
||||
Web3SignerObject::BlobsSidecar(_) => MessageType::BlobsSidecar,
|
||||
Web3SignerObject::Deposit { .. } => MessageType::Deposit,
|
||||
Web3SignerObject::RandaoReveal { .. } => MessageType::RandaoReveal,
|
||||
Web3SignerObject::VoluntaryExit(_) => MessageType::VoluntaryExit,
|
||||
|
||||
@@ -19,11 +19,12 @@ use std::sync::Arc;
|
||||
use task_executor::TaskExecutor;
|
||||
use types::{
|
||||
attestation::Error as AttestationError, graffiti::GraffitiString, Address, AggregateAndProof,
|
||||
Attestation, BeaconBlock, BlindedPayload, ChainSpec, ContributionAndProof, Domain, Epoch,
|
||||
EthSpec, ExecPayload, Fork, Graffiti, Hash256, Keypair, PublicKeyBytes, SelectionProof,
|
||||
Signature, SignedAggregateAndProof, SignedBeaconBlock, SignedContributionAndProof, SignedRoot,
|
||||
SignedValidatorRegistrationData, Slot, SyncAggregatorSelectionData, SyncCommitteeContribution,
|
||||
SyncCommitteeMessage, SyncSelectionProof, SyncSubnetId, ValidatorRegistrationData,
|
||||
Attestation, BeaconBlock, BlindedPayload, BlobsSidecar, ChainSpec, ContributionAndProof,
|
||||
Domain, Epoch, EthSpec, ExecPayload, Fork, FullPayload, Graffiti, Hash256, Keypair,
|
||||
PublicKeyBytes, SelectionProof, Signature, SignedAggregateAndProof, SignedBeaconBlock,
|
||||
SignedBlobsSidecar, SignedContributionAndProof, SignedRoot, SignedValidatorRegistrationData,
|
||||
Slot, SyncAggregatorSelectionData, SyncCommitteeContribution, SyncCommitteeMessage,
|
||||
SyncSelectionProof, SyncSubnetId, ValidatorRegistrationData,
|
||||
};
|
||||
use validator_dir::ValidatorDir;
|
||||
|
||||
@@ -531,6 +532,42 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn sign_blobs(
|
||||
&self,
|
||||
validator_pubkey: PublicKeyBytes,
|
||||
blobs_sidecar: BlobsSidecar<E>,
|
||||
current_slot: Slot,
|
||||
) -> Result<SignedBlobsSidecar<E>, Error> {
|
||||
let slot = blobs_sidecar.beacon_block_slot;
|
||||
|
||||
// Make sure the blob slot is not higher than the current slot to avoid potential attacks.
|
||||
if slot > current_slot {
|
||||
warn!(
|
||||
self.log,
|
||||
"Not signing blob with slot greater than current slot";
|
||||
"blob_slot" => slot.as_u64(),
|
||||
"current_slot" => current_slot.as_u64()
|
||||
);
|
||||
return Err(Error::GreaterThanCurrentSlot { slot, current_slot });
|
||||
}
|
||||
|
||||
let signing_epoch = slot.epoch(E::slots_per_epoch());
|
||||
let signing_context = self.signing_context(Domain::BlobsSideCar, signing_epoch);
|
||||
|
||||
metrics::inc_counter_vec(&metrics::SIGNED_BLOBS_TOTAL, &[metrics::SUCCESS]);
|
||||
|
||||
let signing_method = self.doppelganger_checked_signing_method(validator_pubkey)?;
|
||||
let signature = signing_method
|
||||
.get_signature::<E, FullPayload<E>>(
|
||||
SignableMessage::BlobsSidecar(&blobs_sidecar),
|
||||
signing_context,
|
||||
&self.spec,
|
||||
&self.task_executor,
|
||||
)
|
||||
.await?;
|
||||
Ok(SignedBlobsSidecar::from_blob(blobs_sidecar, signature))
|
||||
}
|
||||
|
||||
pub async fn sign_attestation(
|
||||
&self,
|
||||
validator_pubkey: PublicKeyBytes,
|
||||
|
||||
Reference in New Issue
Block a user