Aura
Aura (Authority-round) is one of the consensus in substrate.
//! Aura works by having a list of authorities A who are expected to roughly
//! agree on the current time. Time is divided up into discrete slots of t
//! seconds each. For each slot s, the author of that slot is A[s % |A|].
//!
//! The author is allowed to issue one block but not more during that slot,
//! and it will be built upon the longest valid chain that has been seen.
//!
//! Blocks from future steps will be either deferred or rejected depending on how
//! far in the future they are.
//!
//! NOTE: Aura itself is designed to be generic over the crypto used.
Startup
node would call start_aura() which in trun will invoke build_aura_worker and then sc_consensus_slots::start_slot_worker() when role.is_authority(). The returned future is a slot worker, looping to author the block.
build_aura_worker
- client: sc_service::TFullClient
- block_import: sc_finality_grandpa::GrandpaBlockImport<FullBackend, Block, FullClient, FullSelectChain>
- select_chain: sc_consensus::LongestChain
- proposer_factory: sc_basic_authorship::ProposerFactory, sc_consensus::Environment, used to propose new blocks
start_slot_worker
Note in start_slot_worker() the argument ‘client’ means the SelectChain/sc_consensus::LongestChain which is create on top of Backend.
pub async fn start_slot_worker<B, C, W, T, SO, CIDP, CAW, Proof>(
slot_duration: SlotDuration<T>,
client: C,
mut worker: W,
mut sync_oracle: SO,
create_inherent_data_providers: CIDP,
can_author_with: CAW,
)
Note start_slot_worker() returns a future which will then be spawned by the task_manager.
Behavior of the future of SlotWoker: looping waiting for a slot and call on_slot() to author block.
let SlotDuration(slot_duration) = slot_duration;
let mut slots =
Slots::new(slot_duration.slot_duration(), create_inherent_data_providers, client);
loop {
let slot_info = match slots.next_slot().await {
Ok(r) => r,
Err(e) => {
warn!(target: "slots", "Error while polling for next slot: {:?}", e);
return
},
};
if sync_oracle.is_major_syncing() {
debug!(target: "slots", "Skipping proposal slot due to sync.");
continue
}
if let Err(err) =
can_author_with.can_author_with(&BlockId::Hash(slot_info.chain_head.hash()))
{
warn!(
target: "slots",
"Unable to author block in slot {},. `can_author_with` returned: {} \
Probably a node update is required!",
slot_info.slot,
err,
);
} else {
let _ = worker.on_slot(slot_info).await;
}
}
sc-consensus-slots::SlotWorker
SlotWorker describes how a slot based consensus to author new blocks.
/// A worker that should be invoked at every new slot.
///
/// The implementation should not make any assumptions of the slot being bound to the time or
/// similar. The only valid assumption is that the slot number is always increasing.
#[async_trait::async_trait]
pub trait SlotWorker<B: BlockT, Proof> {
/// Called when a new slot is triggered.
///
/// Returns a future that resolves to a [`SlotResult`] iff a block was successfully built in
/// the slot. Otherwise `None` is returned.
async fn on_slot(&mut self, slot_info: SlotInfo<B>) -> Option<SlotResult<B, Proof>>;
}
/// A skeleton implementation for `SlotWorker` which tries to claim a slot at
/// its beginning and tries to produce a block if successfully claimed, timing
/// out if block production takes too long.
#[async_trait::async_trait]
pub trait SimpleSlotWorker<B: BlockT> {
...
SlotWorker.on_slot()
It is called when a new slot is triggered, returns a future that resolves to a [SlotResult
] if a block was successfully built in the slot. Otherwise None
is returned.
proposer(sc_consensus::Environment) => proposal(proposing Block) => block_import.import_block()
Proposer: implemets the Proposer logic: spawn a future to build the block and execute transactions, and wait…
sc-basic-authorship::Proposer::propose_with()
Procedure:
- initialize BlockBuilder and inherent data
- extract the pending transactions from pool -> pending_iterator
- push transaction into block -> exectute transactions -> runtimeApi::apply_extrinsic_with_context -> Executive::apply_extrinsic -> Applyable::apply -> Call::dispacth
- remove invalid transaction from pool
- build block
async fn propose_with(
self,
inherent_data: InherentData,
inherent_digests: DigestFor<Block>,
deadline: time::Instant,
block_size_limit: Option<usize>,
) -> Result<Proposal<Block, backend::TransactionFor<B, Block>, PR::Proof>, sp_blockchain::Error>
{
/// If the block is full we will attempt to push at most
/// this number of transactions before quitting for real.
/// It allows us to increase block utilization.
const MAX_SKIPPED_TRANSACTIONS: usize = 8;
let mut block_builder =
self.client.new_block_at(&self.parent_id, inherent_digests, PR::ENABLED)?;
for inherent in block_builder.create_inherents(inherent_data)? {
match block_builder.push(inherent) {
Err(ApplyExtrinsicFailed(Validity(e))) if e.exhausted_resources() => {
warn!("⚠️ Dropping non-mandatory inherent from overweight block.")
},
Err(ApplyExtrinsicFailed(Validity(e))) if e.was_mandatory() => {
error!(
"❌️ Mandatory inherent extrinsic returned error. Block cannot be produced."
);
Err(ApplyExtrinsicFailed(Validity(e)))?
},
Err(e) => {
warn!("❗️ Inherent extrinsic returned unexpected error: {}. Dropping.", e);
},
Ok(_) => {},
}
}
// proceed with transactions
let block_timer = time::Instant::now();
let mut skipped = 0;
let mut unqueue_invalid = Vec::new();
let mut t1 = self.transaction_pool.ready_at(self.parent_number).fuse();
let mut t2 =
futures_timer::Delay::new(deadline.saturating_duration_since((self.now)()) / 8).fuse();
let pending_iterator = select! {
res = t1 => res,
_ = t2 => {
log::warn!(
"Timeout fired waiting for transaction pool at block #{}. \
Proceeding with production.",
self.parent_number,
);
self.transaction_pool.ready()
},
};
let block_size_limit = block_size_limit.unwrap_or(self.default_block_size_limit);
debug!("Attempting to push transactions from the pool.");
debug!("Pool status: {:?}", self.transaction_pool.status());
let mut transaction_pushed = false;
let mut hit_block_size_limit = false;
for pending_tx in pending_iterator {
if (self.now)() > deadline {
debug!(
"Consensus deadline reached when pushing block transactions, \
proceeding with proposing."
);
break
}
let pending_tx_data = pending_tx.data().clone();
let pending_tx_hash = pending_tx.hash().clone();
let block_size =
block_builder.estimate_block_size(self.include_proof_in_block_size_estimation);
if block_size + pending_tx_data.encoded_size() > block_size_limit {
if skipped < MAX_SKIPPED_TRANSACTIONS {
skipped += 1;
debug!(
"Transaction would overflow the block size limit, \
but will try {} more transactions before quitting.",
MAX_SKIPPED_TRANSACTIONS - skipped,
);
continue
} else {
debug!("Reached block size limit, proceeding with proposing.");
hit_block_size_limit = true;
break
}
}
trace!("[{:?}] Pushing to the block.", pending_tx_hash);
match sc_block_builder::BlockBuilder::push(&mut block_builder, pending_tx_data) {
Ok(()) => {
transaction_pushed = true;
debug!("[{:?}] Pushed to the block.", pending_tx_hash);
},
Err(ApplyExtrinsicFailed(Validity(e))) if e.exhausted_resources() => {
if skipped < MAX_SKIPPED_TRANSACTIONS {
skipped += 1;
debug!(
"Block seems full, but will try {} more transactions before quitting.",
MAX_SKIPPED_TRANSACTIONS - skipped,
);
} else {
debug!("Block is full, proceed with proposing.");
break
}
},
Err(e) if skipped > 0 => {
trace!(
"[{:?}] Ignoring invalid transaction when skipping: {}",
pending_tx_hash,
e
);
},
Err(e) => {
debug!("[{:?}] Invalid transaction: {}", pending_tx_hash, e);
unqueue_invalid.push(pending_tx_hash);
},
}
}
if hit_block_size_limit && !transaction_pushed {
warn!(
"Hit block size limit of `{}` without including any transaction!",
block_size_limit,
);
}
self.transaction_pool.remove_invalid(&unqueue_invalid);
let (block, storage_changes, proof) = block_builder.build()?.into_inner();
self.metrics.report(|metrics| {
metrics.number_of_transactions.set(block.extrinsics().len() as u64);
metrics.block_constructed.observe(block_timer.elapsed().as_secs_f64());
});
info!(
"🎁 Prepared block for proposing at {} [hash: {:?}; parent_hash: {}; extrinsics ({}): [{}]]",
block.header().number(),
<Block as BlockT>::Hash::from(block.header().hash()),
block.header().parent_hash(),
block.extrinsics().len(),
block.extrinsics()
.iter()
.map(|xt| format!("{}", BlakeTwo256::hash_of(xt)))
.collect::<Vec<_>>()
.join(", ")
);
telemetry!(
self.telemetry;
CONSENSUS_INFO;
"prepared_block_for_proposing";
"number" => ?block.header().number(),
"hash" => ?<Block as BlockT>::Hash::from(block.header().hash()),
);
if Decode::decode(&mut block.encode().as_slice()).as_ref() != Ok(&block) {
error!("Failed to verify block encoding/decoding");
}
if let Err(err) =
evaluation::evaluate_initial(&block, &self.parent_hash, self.parent_number)
{
error!("Failed to evaluate authored block: {:?}", err);
}
let proof =
PR::into_proof(proof).map_err(|e| sp_blockchain::Error::Application(Box::new(e)))?;
Ok(Proposal { block, proof, storage_changes })
}