I had a bit of a dig to see how this might be done, and it turns out that Polkadot.js does a lot of work under the hood here. I hope somebody might be able to refine this, but I had a go at transcribing the PJS code into Subxt pseudocode to give a general idea of what it appears to look like:
// fetch current block details we need:
let current_block = api.blocks().at_latest().await?;
let digest_logs = ¤t_block.header().digest.logs;
// fetch list of validators for current block:
let validators = api.storage()
.at_block(current_hash)
.fetch(runtime::storage().session().validators());
.await?;
// Find first PreRuntime, Consensus and Seal digest item in logs:
let pre_runtime = digest_logs.iter().find(|item| matches!(item, DigestItem::PreRuntime(..)));
let consensus = digest_logs.iter().find(|item| matches!(item, DigestItem::Consensus(..)));
let seal = digest_logs.iter().find(|item| matches!(item, DigestItem::Seal(..)));
// In this order, try to extract author from the logs:
if let Some(DigestItem::PreRuntime(engine, data)) = consensus {
extract_author(engine, data, &validators)
} else if let Some(DigestItem::Consensus(engine, data)) = consensus {
extract_author(engine, data, &validators)
} else if let Some(DigestItem::Seal(engine, data)) = consensus {
extract_author(engine, data, &validators)
}
// Extracting author looks something like this (I'm just trying to transcribe the PJS code here):
fn extract_author(engine: ConsensusEngineId, data: Vec<u8>, validators) -> Option<subxt::utils::MultiAddress> {
if !validators.is_empty() {
if is_aura(engine) {
let d = sp_consensus::aura::digest::PreDigest::decode(&mut &*data).unwrap();
let n: u64 = *d.slot();
subxt::utils::MultiAddress::Address32(validators[n % validators.len()].try_into().unwrap())
} else if is_babe(engine) {
// Not sure; for PreRuntime looks like you can decode a u64 directly to
// access validator in list. Maybe that's all that matters?
let d = u64::decode(&mut &*data).unwrap();
subxt::utils::MultiAddress::Address32(validators[d].try_into().unwrap())
}
}
// For pow & Moonbeam, the bytes are the actual author
if is_pow(engine) || data.len() == 20 {
subxt::utils::MultiAddress::Address20(data.try_into().unwrap())
}
None
}
fn is_aura(engine: ConsensusEngineId) -> bool {
engine == [b'a', b'u', b'r', b'a']
}
fn is_babe(engine: ConsensusEngineId) -> bool {
engine == [b'B', b'A', b'B', b'E']
}
fn is_pow(engine: ConsensusEngineId) -> bool {
engine == [b'p', b'o', b'w', b'_']
}
I'd have to properly check and test the above, no guarantees on its accuracy, but hopefully it gives a starting point.
As you can see, it varies depending on the consensus mechanism and chain. Even the block header itself is generic in Subxt and can be changed entirely.
Search for function createHeaderExtended
in the https://github.com/polkadot-js/api repo (I checked the code out locally for this) to follow the logic that PJS uses :)