mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-06 10:11:44 +00:00
Move block production to gloas file (no logic change).
This commit is contained in:
@@ -3,6 +3,7 @@ use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
|
||||
use bls::Signature;
|
||||
use execution_layer::{BlockProposalContentsType, BuilderParams};
|
||||
use operation_pool::CompactAttestationRef;
|
||||
use ssz::Encode;
|
||||
use state_processing::common::get_attesting_indices_from_state;
|
||||
@@ -12,19 +13,21 @@ use state_processing::{
|
||||
BlockSignatureStrategy, ConsensusContext, VerifyBlockRoot, VerifySignatures,
|
||||
};
|
||||
use state_processing::{VerifyOperation, state_advance::complete_state_advance};
|
||||
use tracing::{Span, debug, debug_span, error, trace, warn};
|
||||
use tracing::{Span, debug, debug_span, error, instrument, trace, warn};
|
||||
use tree_hash::TreeHash;
|
||||
use types::{
|
||||
Attestation, AttestationElectra, AttesterSlashing, AttesterSlashingElectra, BeaconBlock,
|
||||
BeaconBlockBodyGloas, BeaconBlockGloas, BeaconState, Deposit, Eth1Data, EthSpec,
|
||||
ExecutionPayloadEnvelope, FullPayload, Graffiti, Hash256, PayloadAttestation, ProposerSlashing,
|
||||
RelativeEpoch, SignedBeaconBlock, SignedBlsToExecutionChange, SignedExecutionPayloadBid,
|
||||
SignedVoluntaryExit, Slot, SyncAggregate,
|
||||
Address, Attestation, AttestationElectra, AttesterSlashing, AttesterSlashingElectra,
|
||||
BeaconBlock, BeaconBlockBodyGloas, BeaconBlockGloas, BeaconState, BlockProductionVersion,
|
||||
BuilderIndex, Deposit, Eth1Data, EthSpec, ExecutionPayloadBid, ExecutionPayloadEnvelope,
|
||||
ExecutionPayloadGloas, ExecutionRequests, FullPayload, Graffiti, Hash256, PayloadAttestation,
|
||||
ProposerSlashing, RelativeEpoch, SignedBeaconBlock, SignedBlsToExecutionChange,
|
||||
SignedExecutionPayloadBid, SignedVoluntaryExit, Slot, SyncAggregate,
|
||||
};
|
||||
|
||||
use crate::execution_payload::get_execution_payload;
|
||||
use crate::{
|
||||
BeaconChain, BeaconChainTypes, BlockProductionError, ProduceBlockVerification,
|
||||
execution_payload_bid::ExecutionPayloadData, graffiti_calculator::GraffitiSettings, metrics,
|
||||
BeaconChain, BeaconChainError, BeaconChainTypes, BlockProductionError,
|
||||
ProduceBlockVerification, graffiti_calculator::GraffitiSettings, metrics,
|
||||
};
|
||||
|
||||
pub struct PartialBeaconBlock<E: EthSpec> {
|
||||
@@ -44,7 +47,16 @@ pub struct PartialBeaconBlock<E: EthSpec> {
|
||||
bls_to_execution_changes: Vec<SignedBlsToExecutionChange>,
|
||||
}
|
||||
|
||||
// We'll need to add that once we include trusted/trustless bids
|
||||
/// Data needed to construct an ExecutionPayloadEnvelope.
|
||||
/// The envelope requires the beacon_block_root which can only be computed after the block exists.
|
||||
pub struct ExecutionPayloadData<E: types::EthSpec> {
|
||||
pub payload: ExecutionPayloadGloas<E>,
|
||||
pub execution_requests: ExecutionRequests<E>,
|
||||
pub builder_index: BuilderIndex,
|
||||
pub slot: Slot,
|
||||
pub state_root: Hash256,
|
||||
}
|
||||
|
||||
impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
pub async fn produce_block_with_verification_gloas(
|
||||
self: &Arc<Self>,
|
||||
@@ -53,14 +65,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
graffiti_settings: GraffitiSettings,
|
||||
verification: ProduceBlockVerification,
|
||||
_builder_boost_factor: Option<u64>,
|
||||
) -> Result<
|
||||
(
|
||||
BeaconBlock<T::EthSpec, FullPayload<T::EthSpec>>,
|
||||
BeaconState<T::EthSpec>,
|
||||
u64,
|
||||
),
|
||||
BlockProductionError,
|
||||
> {
|
||||
) -> Result<(BeaconBlock<T::EthSpec, FullPayload<T::EthSpec>>, u64), BlockProductionError> {
|
||||
metrics::inc_counter(&metrics::BLOCK_PRODUCTION_REQUESTS);
|
||||
let _complete_timer = metrics::start_timer(&metrics::BLOCK_PRODUCTION_TIMES);
|
||||
// Part 1/2 (blocking)
|
||||
@@ -105,14 +110,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
randao_reveal: Signature,
|
||||
graffiti_settings: GraffitiSettings,
|
||||
verification: ProduceBlockVerification,
|
||||
) -> Result<
|
||||
(
|
||||
BeaconBlock<T::EthSpec, FullPayload<T::EthSpec>>,
|
||||
BeaconState<T::EthSpec>,
|
||||
u64,
|
||||
),
|
||||
BlockProductionError,
|
||||
> {
|
||||
) -> Result<(BeaconBlock<T::EthSpec, FullPayload<T::EthSpec>>, u64), BlockProductionError> {
|
||||
// Part 1/3 (blocking)
|
||||
//
|
||||
// Perform the state advance and block-packing functions.
|
||||
@@ -419,14 +417,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
payload_data: Option<ExecutionPayloadData<T::EthSpec>>,
|
||||
mut state: BeaconState<T::EthSpec>,
|
||||
verification: ProduceBlockVerification,
|
||||
) -> Result<
|
||||
(
|
||||
BeaconBlock<T::EthSpec, FullPayload<T::EthSpec>>,
|
||||
BeaconState<T::EthSpec>,
|
||||
u64,
|
||||
),
|
||||
BlockProductionError,
|
||||
> {
|
||||
) -> Result<(BeaconBlock<T::EthSpec, FullPayload<T::EthSpec>>, u64), BlockProductionError> {
|
||||
let PartialBeaconBlock {
|
||||
slot,
|
||||
proposer_index,
|
||||
@@ -602,6 +593,157 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
"Produced beacon block"
|
||||
);
|
||||
|
||||
Ok((block, state, consensus_block_value))
|
||||
Ok((block, consensus_block_value))
|
||||
}
|
||||
|
||||
// TODO(gloas) introduce `ProposerPreferences` so we can build out trustless
|
||||
// bid building. Right now this only works for local building.
|
||||
/// Produce an `ExecutionPayloadBid` for some `slot` upon the given `state`.
|
||||
/// This function assumes we've already done the state advance.
|
||||
///
|
||||
/// Returns the signed bid, the state, and optionally the payload data needed to construct
|
||||
/// the `ExecutionPayloadEnvelope` after the beacon block is created.
|
||||
///
|
||||
/// For local building, payload data is always returned (`Some`).
|
||||
/// For trustless building, the builder provides the envelope separately, so `None` is returned.
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub async fn produce_execution_payload_bid(
|
||||
self: Arc<Self>,
|
||||
state: BeaconState<T::EthSpec>,
|
||||
state_root_opt: Option<Hash256>,
|
||||
produce_at_slot: Slot,
|
||||
bid_value: u64,
|
||||
builder_index: BuilderIndex,
|
||||
) -> Result<
|
||||
(
|
||||
SignedExecutionPayloadBid<T::EthSpec>,
|
||||
BeaconState<T::EthSpec>,
|
||||
Option<ExecutionPayloadData<T::EthSpec>>,
|
||||
),
|
||||
BlockProductionError,
|
||||
> {
|
||||
// TODO(gloas) For non local building, add sanity check on value
|
||||
// The builder MUST have enough excess balance to fulfill this bid (i.e. `value`) and all pending payments.
|
||||
|
||||
// TODO(gloas) add metrics for execution payload bid production
|
||||
|
||||
let parent_root = if state.slot() > 0 {
|
||||
*state
|
||||
.get_block_root(state.slot() - 1)
|
||||
.map_err(|_| BlockProductionError::UnableToGetBlockRootFromState)?
|
||||
} else {
|
||||
state.latest_block_header().canonical_root()
|
||||
};
|
||||
|
||||
let proposer_index = state.get_beacon_proposer_index(state.slot(), &self.spec)? as u64;
|
||||
|
||||
let pubkey = state
|
||||
.validators()
|
||||
.get(proposer_index as usize)
|
||||
.map(|v| v.pubkey)
|
||||
.ok_or(BlockProductionError::BeaconChain(Box::new(
|
||||
BeaconChainError::ValidatorIndexUnknown(proposer_index as usize),
|
||||
)))?;
|
||||
|
||||
let builder_params = BuilderParams {
|
||||
pubkey,
|
||||
slot: state.slot(),
|
||||
chain_health: self
|
||||
.is_healthy(&parent_root)
|
||||
.map_err(|e| BlockProductionError::BeaconChain(Box::new(e)))?,
|
||||
};
|
||||
|
||||
// TODO(gloas) this should be BlockProductionVersion::V4
|
||||
// V3 is okay for now as long as we're not connected to a builder
|
||||
// TODO(gloas) add builder boost factor
|
||||
let prepare_payload_handle = get_execution_payload(
|
||||
self.clone(),
|
||||
&state,
|
||||
parent_root,
|
||||
proposer_index,
|
||||
builder_params,
|
||||
None,
|
||||
BlockProductionVersion::V3,
|
||||
)?;
|
||||
|
||||
let block_contents_type = prepare_payload_handle
|
||||
.await
|
||||
.map_err(BlockProductionError::TokioJoin)?
|
||||
.ok_or(BlockProductionError::ShuttingDown)??;
|
||||
|
||||
let (execution_payload, blob_kzg_commitments, execution_requests) =
|
||||
match block_contents_type {
|
||||
BlockProposalContentsType::Full(block_proposal_contents) => {
|
||||
let (payload, blob_kzg_commitments, _, execution_requests, _) =
|
||||
block_proposal_contents.deconstruct();
|
||||
|
||||
if let Some(blob_kzg_commitments) = blob_kzg_commitments
|
||||
&& let Some(execution_requests) = execution_requests
|
||||
{
|
||||
(
|
||||
payload.execution_payload(),
|
||||
blob_kzg_commitments,
|
||||
execution_requests,
|
||||
)
|
||||
} else {
|
||||
return Err(BlockProductionError::MissingKzgCommitment(
|
||||
"No KZG commitments from the payload".to_owned(),
|
||||
));
|
||||
}
|
||||
}
|
||||
// TODO(gloas) we should never receive a blinded response.
|
||||
// Should return some type of `Unexpected` error variant as this should never happen
|
||||
// in the V4 block production flow
|
||||
BlockProposalContentsType::Blinded(_) => {
|
||||
return Err(BlockProductionError::GloasNotImplemented);
|
||||
}
|
||||
};
|
||||
|
||||
let state_root = state_root_opt.ok_or_else(|| BlockProductionError::MissingStateRoot)?;
|
||||
|
||||
// TODO(gloas) this is just a dummy error variant for now
|
||||
let execution_payload_gloas = execution_payload
|
||||
.as_gloas()
|
||||
.map_err(|_| BlockProductionError::GloasNotImplemented)?
|
||||
.to_owned();
|
||||
|
||||
let bid = ExecutionPayloadBid::<T::EthSpec> {
|
||||
parent_block_hash: state.latest_block_hash()?.to_owned(),
|
||||
parent_block_root: state.get_latest_block_root(state_root),
|
||||
block_hash: execution_payload.block_hash(),
|
||||
prev_randao: execution_payload.prev_randao(),
|
||||
fee_recipient: Address::ZERO,
|
||||
gas_limit: execution_payload.gas_limit(),
|
||||
builder_index,
|
||||
slot: produce_at_slot,
|
||||
value: bid_value,
|
||||
execution_payment: 0,
|
||||
blob_kzg_commitments,
|
||||
};
|
||||
|
||||
// Store payload data for envelope construction after block is created
|
||||
let payload_data = ExecutionPayloadData {
|
||||
payload: execution_payload_gloas,
|
||||
execution_requests,
|
||||
builder_index,
|
||||
slot: produce_at_slot,
|
||||
state_root,
|
||||
};
|
||||
|
||||
// TODO(gloas) this is only local building
|
||||
// we'll need to implement builder signature for the trustless path
|
||||
Ok((
|
||||
SignedExecutionPayloadBid {
|
||||
message: bid,
|
||||
// TODO(gloas) return better error variant here
|
||||
signature: Signature::infinity()
|
||||
.map_err(|_| BlockProductionError::GloasNotImplemented)?,
|
||||
},
|
||||
state,
|
||||
// Local building always returns payload data.
|
||||
// Trustless building would return None here.
|
||||
Some(payload_data),
|
||||
))
|
||||
}
|
||||
}
|
||||
1
beacon_node/beacon_chain/src/block_production/mod.rs
Normal file
1
beacon_node/beacon_chain/src/block_production/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
mod gloas;
|
||||
@@ -1,177 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use bls::Signature;
|
||||
use execution_layer::{BlockProposalContentsType, BuilderParams};
|
||||
use tracing::instrument;
|
||||
use types::{
|
||||
Address, BeaconState, BlockProductionVersion, BuilderIndex, ExecutionPayloadBid,
|
||||
ExecutionPayloadGloas, ExecutionRequests, Hash256, SignedExecutionPayloadBid, Slot,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
BeaconChain, BeaconChainError, BeaconChainTypes, BlockProductionError,
|
||||
execution_payload::get_execution_payload,
|
||||
};
|
||||
|
||||
/// Data needed to construct an ExecutionPayloadEnvelope.
|
||||
/// The envelope requires the beacon_block_root which can only be computed after the block exists.
|
||||
pub struct ExecutionPayloadData<E: types::EthSpec> {
|
||||
pub payload: ExecutionPayloadGloas<E>,
|
||||
pub execution_requests: ExecutionRequests<E>,
|
||||
pub builder_index: BuilderIndex,
|
||||
pub slot: Slot,
|
||||
pub state_root: Hash256,
|
||||
}
|
||||
|
||||
impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
// TODO(gloas) introduce `ProposerPreferences` so we can build out trustless
|
||||
// bid building. Right now this only works for local building.
|
||||
/// Produce an `ExecutionPayloadBid` for some `slot` upon the given `state`.
|
||||
/// This function assumes we've already done the state advance.
|
||||
///
|
||||
/// Returns the signed bid, the state, and optionally the payload data needed to construct
|
||||
/// the `ExecutionPayloadEnvelope` after the beacon block is created.
|
||||
///
|
||||
/// For local building, payload data is always returned (`Some`).
|
||||
/// For trustless building, the builder provides the envelope separately, so `None` is returned.
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub async fn produce_execution_payload_bid(
|
||||
self: Arc<Self>,
|
||||
state: BeaconState<T::EthSpec>,
|
||||
state_root_opt: Option<Hash256>,
|
||||
produce_at_slot: Slot,
|
||||
bid_value: u64,
|
||||
builder_index: BuilderIndex,
|
||||
) -> Result<
|
||||
(
|
||||
SignedExecutionPayloadBid<T::EthSpec>,
|
||||
BeaconState<T::EthSpec>,
|
||||
Option<ExecutionPayloadData<T::EthSpec>>,
|
||||
),
|
||||
BlockProductionError,
|
||||
> {
|
||||
// TODO(gloas) For non local building, add sanity check on value
|
||||
// The builder MUST have enough excess balance to fulfill this bid (i.e. `value`) and all pending payments.
|
||||
|
||||
// TODO(gloas) add metrics for execution payload bid production
|
||||
|
||||
let parent_root = if state.slot() > 0 {
|
||||
*state
|
||||
.get_block_root(state.slot() - 1)
|
||||
.map_err(|_| BlockProductionError::UnableToGetBlockRootFromState)?
|
||||
} else {
|
||||
state.latest_block_header().canonical_root()
|
||||
};
|
||||
|
||||
let proposer_index = state.get_beacon_proposer_index(state.slot(), &self.spec)? as u64;
|
||||
|
||||
let pubkey = state
|
||||
.validators()
|
||||
.get(proposer_index as usize)
|
||||
.map(|v| v.pubkey)
|
||||
.ok_or(BlockProductionError::BeaconChain(Box::new(
|
||||
BeaconChainError::ValidatorIndexUnknown(proposer_index as usize),
|
||||
)))?;
|
||||
|
||||
let builder_params = BuilderParams {
|
||||
pubkey,
|
||||
slot: state.slot(),
|
||||
chain_health: self
|
||||
.is_healthy(&parent_root)
|
||||
.map_err(|e| BlockProductionError::BeaconChain(Box::new(e)))?,
|
||||
};
|
||||
|
||||
// TODO(gloas) this should be BlockProductionVersion::V4
|
||||
// V3 is okay for now as long as we're not connected to a builder
|
||||
// TODO(gloas) add builder boost factor
|
||||
let prepare_payload_handle = get_execution_payload(
|
||||
self.clone(),
|
||||
&state,
|
||||
parent_root,
|
||||
proposer_index,
|
||||
builder_params,
|
||||
None,
|
||||
BlockProductionVersion::V3,
|
||||
)?;
|
||||
|
||||
let block_contents_type = prepare_payload_handle
|
||||
.await
|
||||
.map_err(BlockProductionError::TokioJoin)?
|
||||
.ok_or(BlockProductionError::ShuttingDown)??;
|
||||
|
||||
let (execution_payload, blob_kzg_commitments, execution_requests) =
|
||||
match block_contents_type {
|
||||
BlockProposalContentsType::Full(block_proposal_contents) => {
|
||||
let (payload, blob_kzg_commitments, _, execution_requests, _) =
|
||||
block_proposal_contents.deconstruct();
|
||||
|
||||
if let Some(blob_kzg_commitments) = blob_kzg_commitments
|
||||
&& let Some(execution_requests) = execution_requests
|
||||
{
|
||||
(
|
||||
payload.execution_payload(),
|
||||
blob_kzg_commitments,
|
||||
execution_requests,
|
||||
)
|
||||
} else {
|
||||
return Err(BlockProductionError::MissingKzgCommitment(
|
||||
"No KZG commitments from the payload".to_owned(),
|
||||
));
|
||||
}
|
||||
}
|
||||
// TODO(gloas) we should never receive a blinded response.
|
||||
// Should return some type of `Unexpected` error variant as this should never happen
|
||||
// in the V4 block production flow
|
||||
BlockProposalContentsType::Blinded(_) => {
|
||||
return Err(BlockProductionError::GloasNotImplemented);
|
||||
}
|
||||
};
|
||||
|
||||
let state_root = state_root_opt.ok_or_else(|| BlockProductionError::MissingStateRoot)?;
|
||||
|
||||
// TODO(gloas) this is just a dummy error variant for now
|
||||
let execution_payload_gloas = execution_payload
|
||||
.as_gloas()
|
||||
.map_err(|_| BlockProductionError::GloasNotImplemented)?
|
||||
.to_owned();
|
||||
|
||||
let bid = ExecutionPayloadBid::<T::EthSpec> {
|
||||
parent_block_hash: state.latest_block_hash()?.to_owned(),
|
||||
parent_block_root: state.get_latest_block_root(state_root),
|
||||
block_hash: execution_payload.block_hash(),
|
||||
prev_randao: execution_payload.prev_randao(),
|
||||
fee_recipient: Address::ZERO,
|
||||
gas_limit: execution_payload.gas_limit(),
|
||||
builder_index,
|
||||
slot: produce_at_slot,
|
||||
value: bid_value,
|
||||
execution_payment: 0,
|
||||
blob_kzg_commitments,
|
||||
};
|
||||
|
||||
// Store payload data for envelope construction after block is created
|
||||
let payload_data = ExecutionPayloadData {
|
||||
payload: execution_payload_gloas,
|
||||
execution_requests,
|
||||
builder_index,
|
||||
slot: produce_at_slot,
|
||||
state_root,
|
||||
};
|
||||
|
||||
// TODO(gloas) this is only local building
|
||||
// we'll need to implement builder signature for the trustless path
|
||||
Ok((
|
||||
SignedExecutionPayloadBid {
|
||||
message: bid,
|
||||
// TODO(gloas) return better error variant here
|
||||
signature: Signature::infinity()
|
||||
.map_err(|_| BlockProductionError::GloasNotImplemented)?,
|
||||
},
|
||||
state,
|
||||
// Local building always returns payload data.
|
||||
// Trustless building would return None here.
|
||||
Some(payload_data),
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
pub mod attestation_rewards;
|
||||
pub mod attestation_simulator;
|
||||
pub mod attestation_verification;
|
||||
mod beacon_block;
|
||||
pub mod beacon_block_reward;
|
||||
mod beacon_block_streamer;
|
||||
mod beacon_chain;
|
||||
@@ -10,6 +9,7 @@ pub mod beacon_proposer_cache;
|
||||
mod beacon_snapshot;
|
||||
pub mod bellatrix_readiness;
|
||||
pub mod blob_verification;
|
||||
mod block_production;
|
||||
pub mod block_reward;
|
||||
mod block_times_cache;
|
||||
mod block_verification;
|
||||
@@ -24,7 +24,6 @@ mod early_attester_cache;
|
||||
mod errors;
|
||||
pub mod events;
|
||||
pub mod execution_payload;
|
||||
pub mod execution_payload_bid;
|
||||
pub mod fetch_blobs;
|
||||
pub mod fork_choice_signal;
|
||||
pub mod fork_revert;
|
||||
|
||||
@@ -39,6 +39,7 @@ impl<E: EthSpec> PendingPayloadEnvelopes<E> {
|
||||
|
||||
/// Insert a pending envelope into the cache.
|
||||
pub fn insert(&mut self, slot: Slot, envelope: ExecutionPayloadEnvelope<E>) {
|
||||
// TODO(gloas): we may want to check for duplicates here, which shouldn't be allowed
|
||||
self.envelopes.insert(slot, envelope);
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ pub async fn produce_block_v4<T: BeaconChainTypes>(
|
||||
|
||||
let graffiti_settings = GraffitiSettings::new(query.graffiti, query.graffiti_policy);
|
||||
|
||||
let (block, _state, consensus_block_value) = chain
|
||||
let (block, consensus_block_value) = chain
|
||||
.produce_block_with_verification_gloas(
|
||||
randao_reveal,
|
||||
slot,
|
||||
@@ -83,7 +83,7 @@ pub async fn produce_block_v4<T: BeaconChainTypes>(
|
||||
warp_utils::reject::custom_bad_request(format!("failed to fetch a block: {:?}", e))
|
||||
})?;
|
||||
|
||||
build_response_v4(chain, block, consensus_block_value, accept_header)
|
||||
build_response_v4::<T>(block, consensus_block_value, accept_header, &chain.spec)
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
@@ -131,14 +131,14 @@ pub async fn produce_block_v3<T: BeaconChainTypes>(
|
||||
}
|
||||
|
||||
pub fn build_response_v4<T: BeaconChainTypes>(
|
||||
chain: Arc<BeaconChain<T>>,
|
||||
block: BeaconBlock<T::EthSpec, FullPayload<T::EthSpec>>,
|
||||
consensus_block_value: u64,
|
||||
accept_header: Option<api_types::Accept>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Response<Body>, warp::Rejection> {
|
||||
let fork_name = block
|
||||
.to_ref()
|
||||
.fork_name(&chain.spec)
|
||||
.fork_name(&spec)
|
||||
.map_err(inconsistent_fork_rejection)?;
|
||||
let consensus_block_value_wei =
|
||||
Uint256::from(consensus_block_value) * Uint256::from(1_000_000_000u64);
|
||||
|
||||
Reference in New Issue
Block a user