mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
240 lines
10 KiB
Rust
240 lines
10 KiB
Rust
use crate::{mode::MiningMode, Storage};
|
|
use futures_util::{future::BoxFuture, FutureExt};
|
|
use reth_beacon_consensus::{BeaconEngineMessage, ForkchoiceStatus};
|
|
use reth_interfaces::consensus::ForkchoiceState;
|
|
use reth_node_api::EngineTypes;
|
|
use reth_primitives::{Block, ChainSpec, IntoRecoveredTransaction, SealedBlockWithSenders};
|
|
use reth_provider::{CanonChainTracker, CanonStateNotificationSender, Chain, StateProviderFactory};
|
|
use reth_stages::PipelineEvent;
|
|
use reth_transaction_pool::{TransactionPool, ValidPoolTransaction};
|
|
use std::{
|
|
collections::VecDeque,
|
|
future::Future,
|
|
pin::Pin,
|
|
sync::Arc,
|
|
task::{Context, Poll},
|
|
};
|
|
use tokio::sync::{mpsc::UnboundedSender, oneshot};
|
|
use tokio_stream::wrappers::UnboundedReceiverStream;
|
|
use tracing::{debug, error, warn};
|
|
|
|
/// A Future that listens for new ready transactions and puts new blocks into storage
|
|
pub struct MiningTask<Client, Pool: TransactionPool, Engine: EngineTypes> {
|
|
/// The configured chain spec
|
|
chain_spec: Arc<ChainSpec>,
|
|
/// The client used to interact with the state
|
|
client: Client,
|
|
/// The active miner
|
|
miner: MiningMode,
|
|
/// Single active future that inserts a new block into `storage`
|
|
insert_task: Option<BoxFuture<'static, Option<UnboundedReceiverStream<PipelineEvent>>>>,
|
|
/// Shared storage to insert new blocks
|
|
storage: Storage,
|
|
/// Pool where transactions are stored
|
|
pool: Pool,
|
|
/// backlog of sets of transactions ready to be mined
|
|
queued: VecDeque<Vec<Arc<ValidPoolTransaction<<Pool as TransactionPool>::Transaction>>>>,
|
|
// TODO: ideally this would just be a sender of hashes
|
|
to_engine: UnboundedSender<BeaconEngineMessage<Engine>>,
|
|
/// Used to notify consumers of new blocks
|
|
canon_state_notification: CanonStateNotificationSender,
|
|
/// The pipeline events to listen on
|
|
pipe_line_events: Option<UnboundedReceiverStream<PipelineEvent>>,
|
|
}
|
|
|
|
// === impl MiningTask ===
|
|
|
|
impl<Client, Pool: TransactionPool, Engine: EngineTypes> MiningTask<Client, Pool, Engine> {
|
|
/// Creates a new instance of the task
|
|
pub(crate) fn new(
|
|
chain_spec: Arc<ChainSpec>,
|
|
miner: MiningMode,
|
|
to_engine: UnboundedSender<BeaconEngineMessage<Engine>>,
|
|
canon_state_notification: CanonStateNotificationSender,
|
|
storage: Storage,
|
|
client: Client,
|
|
pool: Pool,
|
|
) -> Self {
|
|
Self {
|
|
chain_spec,
|
|
client,
|
|
miner,
|
|
insert_task: None,
|
|
storage,
|
|
pool,
|
|
to_engine,
|
|
canon_state_notification,
|
|
queued: Default::default(),
|
|
pipe_line_events: None,
|
|
}
|
|
}
|
|
|
|
/// Sets the pipeline events to listen on.
|
|
pub fn set_pipeline_events(&mut self, events: UnboundedReceiverStream<PipelineEvent>) {
|
|
self.pipe_line_events = Some(events);
|
|
}
|
|
}
|
|
|
|
impl<Client, Pool, Engine> Future for MiningTask<Client, Pool, Engine>
|
|
where
|
|
Client: StateProviderFactory + CanonChainTracker + Clone + Unpin + 'static,
|
|
Pool: TransactionPool + Unpin + 'static,
|
|
<Pool as TransactionPool>::Transaction: IntoRecoveredTransaction,
|
|
Engine: EngineTypes + 'static,
|
|
{
|
|
type Output = ();
|
|
|
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
|
let this = self.get_mut();
|
|
|
|
// this drives block production and
|
|
loop {
|
|
if let Poll::Ready(transactions) = this.miner.poll(&this.pool, cx) {
|
|
// miner returned a set of transaction that we feed to the producer
|
|
this.queued.push_back(transactions);
|
|
}
|
|
|
|
if this.insert_task.is_none() {
|
|
if this.queued.is_empty() {
|
|
// nothing to insert
|
|
break
|
|
}
|
|
|
|
// ready to queue in new insert task
|
|
let storage = this.storage.clone();
|
|
let transactions = this.queued.pop_front().expect("not empty");
|
|
|
|
let to_engine = this.to_engine.clone();
|
|
let client = this.client.clone();
|
|
let chain_spec = Arc::clone(&this.chain_spec);
|
|
let pool = this.pool.clone();
|
|
let events = this.pipe_line_events.take();
|
|
let canon_state_notification = this.canon_state_notification.clone();
|
|
|
|
// Create the mining future that creates a block, notifies the engine that drives
|
|
// the pipeline
|
|
this.insert_task = Some(Box::pin(async move {
|
|
let mut storage = storage.write().await;
|
|
|
|
let (transactions, senders): (Vec<_>, Vec<_>) = transactions
|
|
.into_iter()
|
|
.map(|tx| {
|
|
let recovered = tx.to_recovered_transaction();
|
|
let signer = recovered.signer();
|
|
(recovered.into_signed(), signer)
|
|
})
|
|
.unzip();
|
|
|
|
match storage.build_and_execute(transactions.clone(), &client, chain_spec) {
|
|
Ok((new_header, bundle_state)) => {
|
|
// clear all transactions from pool
|
|
pool.remove_transactions(
|
|
transactions.iter().map(|tx| tx.hash()).collect(),
|
|
);
|
|
|
|
let state = ForkchoiceState {
|
|
head_block_hash: new_header.hash,
|
|
finalized_block_hash: new_header.hash,
|
|
safe_block_hash: new_header.hash,
|
|
};
|
|
drop(storage);
|
|
|
|
// TODO: make this a future
|
|
// await the fcu call rx for SYNCING, then wait for a VALID response
|
|
loop {
|
|
// send the new update to the engine, this will trigger the engine
|
|
// to download and execute the block we just inserted
|
|
let (tx, rx) = oneshot::channel();
|
|
let _ = to_engine.send(BeaconEngineMessage::ForkchoiceUpdated {
|
|
state,
|
|
payload_attrs: None,
|
|
tx,
|
|
});
|
|
debug!(target: "consensus::auto", ?state, "Sent fork choice update");
|
|
|
|
match rx.await.unwrap() {
|
|
Ok(fcu_response) => {
|
|
match fcu_response.forkchoice_status() {
|
|
ForkchoiceStatus::Valid => break,
|
|
ForkchoiceStatus::Invalid => {
|
|
error!(target: "consensus::auto", ?fcu_response, "Forkchoice update returned invalid response");
|
|
return None
|
|
}
|
|
ForkchoiceStatus::Syncing => {
|
|
debug!(target: "consensus::auto", ?fcu_response, "Forkchoice update returned SYNCING, waiting for VALID");
|
|
// wait for the next fork choice update
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
Err(err) => {
|
|
error!(target: "consensus::auto", ?err, "Autoseal fork choice update failed");
|
|
return None
|
|
}
|
|
}
|
|
}
|
|
|
|
// seal the block
|
|
let block = Block {
|
|
header: new_header.clone().unseal(),
|
|
body: transactions,
|
|
ommers: vec![],
|
|
withdrawals: None,
|
|
};
|
|
let sealed_block = block.seal_slow();
|
|
|
|
let sealed_block_with_senders =
|
|
SealedBlockWithSenders::new(sealed_block, senders)
|
|
.expect("senders are valid");
|
|
|
|
// update canon chain for rpc
|
|
client.set_canonical_head(new_header.clone());
|
|
client.set_safe(new_header.clone());
|
|
client.set_finalized(new_header.clone());
|
|
|
|
debug!(target: "consensus::auto", header=?sealed_block_with_senders.hash(), "sending block notification");
|
|
|
|
let chain = Arc::new(Chain::new(
|
|
vec![sealed_block_with_senders],
|
|
bundle_state,
|
|
None,
|
|
));
|
|
|
|
// send block notification
|
|
let _ = canon_state_notification
|
|
.send(reth_provider::CanonStateNotification::Commit { new: chain });
|
|
}
|
|
Err(err) => {
|
|
warn!(target: "consensus::auto", ?err, "failed to execute block")
|
|
}
|
|
}
|
|
|
|
events
|
|
}));
|
|
}
|
|
|
|
if let Some(mut fut) = this.insert_task.take() {
|
|
match fut.poll_unpin(cx) {
|
|
Poll::Ready(events) => {
|
|
this.pipe_line_events = events;
|
|
}
|
|
Poll::Pending => {
|
|
this.insert_task = Some(fut);
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Poll::Pending
|
|
}
|
|
}
|
|
|
|
impl<Client, Pool: TransactionPool, Engine: EngineTypes> std::fmt::Debug
|
|
for MiningTask<Client, Pool, Engine>
|
|
{
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("MiningTask").finish_non_exhaustive()
|
|
}
|
|
}
|