1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
use std::sync::Arc;

use tendermint_light_client::{
    components::{
        self,
        io::{AtHeight, IoError},
    },
    types::{LightBlock as TMLightBlock, PeerId},
};
use tendermint_rpc::error::Error as RpcError;

use crate::{
    consensus::{
        tendermint::{decode_light_block, LightBlockMeta},
        transaction::SignedTransactionWithProof,
        HEIGHT_LATEST,
    },
    protocol::Protocol,
    types::Body,
};

use super::types::Nonce;

pub struct Io {
    protocol: Arc<Protocol>,
}

impl Io {
    pub fn new(protocol: &Arc<Protocol>) -> Self {
        Self {
            protocol: protocol.clone(),
        }
    }

    fn fetch_light_block(&self, height: u64) -> Result<LightBlockMeta, IoError> {
        let result = self
            .protocol
            .call_host(Body::HostFetchConsensusBlockRequest { height })
            .map_err(|err| IoError::rpc(RpcError::server(err.to_string())))?;

        // Extract generic light block from response.
        let block = match result {
            Body::HostFetchConsensusBlockResponse { block } => block,
            _ => return Err(IoError::rpc(RpcError::server("bad response".to_string()))),
        };

        // Decode block as a Tendermint light block.
        let block = decode_light_block(block)
            .map_err(|err| IoError::rpc(RpcError::server(err.to_string())))?;

        Ok(block)
    }

    pub fn fetch_genesis_height(&self) -> Result<u64, IoError> {
        let result = self
            .protocol
            .call_host(Body::HostFetchGenesisHeightRequest {})
            .map_err(|err| IoError::rpc(RpcError::server(err.to_string())))?;

        // Extract genesis height from response.
        let height = match result {
            Body::HostFetchGenesisHeightResponse { height } => height,
            _ => return Err(IoError::rpc(RpcError::server("bad response".to_string()))),
        };

        Ok(height)
    }

    pub fn fetch_freshness_proof(
        &self,
        nonce: &Nonce,
    ) -> Result<SignedTransactionWithProof, IoError> {
        let result = self
            .protocol
            .call_host(Body::HostProveFreshnessRequest {
                blob: nonce.to_vec(),
            })
            .map_err(|err| IoError::rpc(RpcError::server(err.to_string())))?;

        // Extract proof from response.
        let (signed_tx, proof) = match result {
            Body::HostProveFreshnessResponse { signed_tx, proof } => (signed_tx, proof),
            _ => return Err(IoError::rpc(RpcError::server("bad response".to_string()))),
        };

        Ok(SignedTransactionWithProof { signed_tx, proof })
    }

    pub fn fetch_block_metadata(&self, height: u64) -> Result<SignedTransactionWithProof, IoError> {
        let result = self
            .protocol
            .call_host(Body::HostFetchBlockMetadataTxRequest { height })
            .map_err(|err| IoError::rpc(RpcError::server(err.to_string())))?;

        // Extract proof from response.
        let (signed_tx, proof) = match result {
            Body::HostFetchBlockMetadataTxResponse { signed_tx, proof } => (signed_tx, proof),
            _ => return Err(IoError::rpc(RpcError::server("bad response".to_string()))),
        };

        Ok(SignedTransactionWithProof { signed_tx, proof })
    }
}

impl components::io::Io for Io {
    fn fetch_light_block(&self, height: AtHeight) -> Result<TMLightBlock, IoError> {
        let height = match height {
            AtHeight::At(height) => height.into(),
            AtHeight::Highest => HEIGHT_LATEST,
        };

        // Fetch light block at height and height+1.
        let block = Io::fetch_light_block(self, height)?;
        let height: u64 = block
            .signed_header
            .as_ref()
            .ok_or_else(|| IoError::rpc(RpcError::server("missing signed header".to_string())))?
            .header()
            .height
            .into();
        // NOTE: It seems that the requirement to fetch the next validator set is redundant and it
        //       should be handled at a higher layer of the light client.
        let next_block = Io::fetch_light_block(self, height + 1)?;

        Ok(TMLightBlock {
            signed_header: block.signed_header.unwrap(), // Checked above.
            validators: block.validators,
            next_validators: next_block.validators,
            provider: PeerId::new([0; 20]),
        })
    }
}