exonum / exonum

An extensible open-source framework for creating private/permissioned blockchain applications

Home Page:https://exonum.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Hash of the proof is not equal to the trusted hash of the index after committing new block with at least 50% probability

vimmerru opened this issue · comments

Hi,

I am exposing API that returns proof based on ProofMapIndex. The problem that client receives malformed proof in about 50% of cases. After new block is committed the situation changes every time.

To exclude probability of client/node miscommunication i added verification of the proof in API handler and got the same results as on client side.

Looks like when new block is committed with 50% of probability entity proof doesn't match to index proof.

My code just mirrors the code from your example:

pub async fn get_permission_set(
        state: ServiceApiState,
        query: EntityQuery,
    ) -> api::Result<EntityInfo<PermissionSet>> {
        let IndexProof {
            block_proof,
            index_proof,
            ..
        } = state
            .data()
            .proof_for_service_index(name_of!(permission_sets in IotlSchema<&dyn Snapshot>))
            .unwrap();

        let schema = IotlSchema::new(state.service_data());
        let entity_proof = schema.permission_sets.get_proof(query.id);

        Ok(EntityInfo::new(block_proof, index_proof, entity_proof))
    }

I use exonum 1.0.0, rustc 1.41.1 on MacOS. Please see short attached video how it works.

Hello.
A couple of questions.

  1. What client do you use to validate proofs ?
  2. Could you post the code of the verification ?

i added verification of the proof in API handler

Hi @aleksuss

What client do you use to validate proofs ?

I use code from exonum::blockchain on client side and node side.

Could you post the code of the verification ?

Here is the code. I put it to API handler right after proof generation to avoid any client-node communication issues:

 /// Endpoint for getting permission set.
    pub async fn get_permission_set(
        state: ServiceApiState,
        query: EntityQuery,
    ) -> api::Result<EntityInfo<PermissionSet>> {
        let IndexProof {
            block_proof,
            index_proof,
            ..
        } = state
            .data()
            .proof_for_service_index(name_of!(permission_sets in IotlSchema<&dyn Snapshot>))
            .unwrap();

        let entity_id = query.id.clone();
        let schema = IotlSchema::new(state.service_data());
        let entity_proof = schema.permission_sets.get_proof(query.id);

        let block_hash = block_proof.block.state_hash;
        let index_name = PermissionSet::index_name();

        let table_hash = index_proof
            .check_against_hash(block_hash)
            .map_err(|e| api::Error::internal(e))?
            .entries()
            .find_map(|(k, v)| {
                if k == &index_name {
                    Some(v.to_owned())
                } else {
                    None
                }
            })
            .ok_or_else(|| api::Error::internal("Index not found in proof"))?;

        let _entity = entity_proof
            .check_against_hash(table_hash)
            .map_err(|e| api::Error::internal(e))?
            .all_entries()
            .find_map(|(k, v)| {
                if k == &entity_id {
                    Some(v.map(|v| v.to_owned()))
                } else {
                    None
                }
            })
            .ok_or_else(|| api::Error::internal("Entity not found in proof"))?;

        Ok(EntityInfo::new(block_proof, index_proof, entity_proof))
    }

It fails on .check_against_hash(table_hash)

Does index_name include the service name ? I mean it should be like iotl.permission_sets.

Does index_name include the service name ? I mean it should be like iotl.permission_sets.

Sure, it is like this. And proof is valid sometimes. The problem is that when block changes it may contain index hash incompatible with entity proof. I don't understand why it can be because i have no any mutations in this index.

And one more thing. What is a timeout between blocks commitment in your configuration ? Maybe you make a get request before a block (which contains the requested data) was committed ? I've just checked this example in cryptocurrency-advanced example and everything works fine.

What is a timeout between blocks commitment in your configuration ?

I use development node. There is no timeout configuration, but i looks very small. Less than a second.

Maybe you make a get request before a block (which contains the requested data) was committed ?

I am checking tx status in my post function before sending get. But it doesn't matter as i can request entity after few minutes and it sometimes give valid proof and sometimes invalid. Seems there is some race condition and ServiceApiState sometimes can be incompatible with latest block available though .data().

It would be cool if you add a unit test or test with a testkit which demonstrates such behaviour.

@aleksuss FYI seems i found the issue, but i am not sure for 100%. Seems my entity has unstable serialization and probably hashing. I will spend time tomorrow to make sure and update you. Thanks for your support.

@aleksuss FYI as i said yesterday the issue is related to unstable serialization of my entity. Refactoring of entity type solved my problem and the issue can be closed as invalid.

It's great. Glad to see this.