6

I am fetching specific block by hash using subxt:

    let block_hash = subxt::utils::H256::from_str(
        "0xfb6f67233cc212a40522351c88d86d9ac83f68a6b6d6be14c0cb69e83a5b8407"
    ).unwrap();
    let block = api.rpc()
        .block(Some(block_hash))
        .await.unwrap().unwrap();

Now I want to find the Account ID of the validator that produced this block. I have found this in regards to finding the block author with JS API https://polkadot.js.org/docs/api/start/api.rpc#detour-into-derives but I am not able to find anything similar with subxt.

I am also curious how the JS API is deriving block producer information since I do not see anything in ChainBlock where this information could be included, or is it perhaps determined from leader schedule?

I would appreciate any pointers.

2 Answers 2

4

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 = &current_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 :)

4

I have similar requirement and I followed the suggestion in the previous answer given by @jsdw.

Following is my code, I am able to receive the author AccountId32 from the block.

fn extract_author(digest_logs: &Vec<DigestItem>, validators: Vec<AccountId32> ) -> Result<Option<AccountId32>>{
    let pre_runtime = digest_logs.iter().find(|item| matches!(item, DigestItem::PreRuntime(..)));
    if let Some(DigestItem::PreRuntime(engine, data)) = pre_runtime {
        if is_babe(engine.clone()) {
            let mut data: &[u8] = &data;
            let pre_digest = PreDigest::decode(&mut data)?;
            let authority_index = match pre_digest {
                PreDigest::Primary(primary) => primary.authority_index,
                PreDigest::SecondaryPlain(secondary) => secondary.authority_index,
                PreDigest::SecondaryVRF(secondary) => secondary.authority_index,
            };

            return Ok(Some(validators[authority_index as usize].clone()));
        }else {
            return Ok(None);
        }
    } else {
        return Ok(None);
    }
}

calling code:-

let validators = block.storage().fetch(&polkadot::storage().session().validators()).await?.unwrap();
let digest_logs = &block.header().digest.logs;
let has_author = extract_author(digest_logs, validators)?;

let validator_public_key = if let Some(validator) = has_author {
    validator.to_string()
} else { "".to_string() };

Next step is to get MultiAddress using AccountId32 and get the address string.

Edit: To find the address string follow the answer at MultiAddress to Address String

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.