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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
use lifeline::{Lifeline, Service, Task};
use support_common::error::BridgerError;
use web3::transports::Http;
use web3::types::{BlockId, BlockNumber, U256, U64};
use web3::Web3;

use support_common::config::{Config, Names};
use support_lifeline::service::BridgeService;

use crate::bridge::{PangoroChapelBus, PangoroChapelConfig};
use crate::pangoro_client::client::PangoroClient;
use crate::pangoro_client::types::BSCHeader;

#[derive(Debug)]
pub struct HeaderRelayService {
    _chapel2pangoro: Lifeline,
}

impl BridgeService for HeaderRelayService {}

impl Service for HeaderRelayService {
    type Bus = PangoroChapelBus;
    type Lifeline = color_eyre::Result<Self>;

    fn spawn(_bus: &Self::Bus) -> Self::Lifeline {
        tracing::trace!("Spawn service HeaderRelayService");
        let config: PangoroChapelConfig = Config::restore(Names::BridgePangoroChapel)?;

        let _chapel2pangoro = Self::try_task("header-relay-service", async move {
            tracing::trace!("Start to relay chapel headers to pangoro ");
            start_relay(config).await;
            Ok(())
        });
        Ok(Self { _chapel2pangoro })
    }
}

pub async fn get_bsc_header(
    client: &Web3<Http>,
    block_number: u64,
) -> color_eyre::Result<BSCHeader> {
    let block_number = BlockId::Number(BlockNumber::Number(U64::from(block_number)));
    let block = client
        .eth()
        .block(block_number)
        .await?
        .ok_or_else(|| BridgerError::Custom("Failed to get block".into()))?;

    let log_bloom = web3::types::Bytes(
        block
            .logs_bloom
            .ok_or_else(|| BridgerError::Custom("Failed to get log_bloom from block".into()))?
            .0
            .into(),
    );
    let number = U256::from(
        block
            .number
            .ok_or_else(|| BridgerError::Custom("Failed to get number from block".into()))?
            .as_u64(),
    );
    let nonce = block
        .nonce
        .ok_or_else(|| BridgerError::Custom("Failed to get nonce from block".into()))?
        .0;
    let mix_digest = block
        .mix_hash
        .ok_or_else(|| BridgerError::Custom("Failed to get mix_digest from block".into()))?;

    Ok(BSCHeader {
        parent_hash: block.parent_hash,
        uncle_hash: block.uncles_hash,
        coinbase: block.author,
        state_root: block.state_root,
        transactions_root: block.transactions_root,
        receipts_root: block.receipts_root,
        difficulty: block.difficulty,
        gas_limit: block.gas_limit,
        gas_used: block.gas_used,
        timestamp: block.timestamp,
        extra_data: block.extra_data,
        mix_digest,
        number,
        log_bloom,
        nonce,
    })
}

pub async fn start_relay(config: PangoroChapelConfig) {
    while let Err(e) = relay_chapel_headers(config.clone()).await {
        tracing::error!(target: "pangoro-chapel", "Relaying failed {:?}. Restarting...", e);
        tokio::time::sleep(std::time::Duration::from_secs(5)).await;
    }
}

/// Relay headers to pangoro
/// 1. get finalized checkpoint
/// 2. calculate next bsc header number range which is required by relay interface
/// 3. get current bsc block number.
/// 4. If the current block number is bigger than last required block number,
///    relay the headers. If not, sleep a few seconds, and jump to 3.
pub async fn relay_chapel_headers(config: PangoroChapelConfig) -> color_eyre::Result<()> {
    tracing::trace!(target: "pangoro-chapel", "Initialize Pangoro subxt client");
    let pangoro = PangoroClient::new(
        &config.pangoro.endpoint,
        &config.pangoro.bsc_address,
        Some(&config.pangoro.private_key),
    )?;
    let transport = web3::transports::Http::new(&config.chapel.endpoint)?;
    tracing::trace!(target: "pangoro-chapel", "Initialize Chapel web3 client");
    let chapel = web3::Web3::new(transport);

    loop {
        let authority_set_length = pangoro.get_authority_set_length().await?;
        let checkpoint = pangoro.get_finalized_checkpoint().await?;
        let checkpoint_number = checkpoint.4.as_u64();
        tracing::trace!(target: "pangoro-chapel", "Current checkpoint on Pangoro: {:?}", checkpoint_number);
        tracing::trace!(target: "pangoro-chapel", "Current finalized authority set length on Pangoro: {:?}", authority_set_length);

        let next_block_range = 0..(authority_set_length.as_u64() / 2 + 1);
        let chapel_current_block_number = chapel.eth().block_number().await?.as_u64();
        tracing::trace!(target: "pangoro-chapel", "Current block number on Chapel: {:?}", chapel_current_block_number);

        let mut headers: Vec<BSCHeader> = Vec::new();
        if chapel_current_block_number >= checkpoint_number + next_block_range.end {
            for offset in next_block_range.clone() {
                let block_number = checkpoint_number + offset + 200;
                let header = get_bsc_header(&chapel, block_number).await?;
                headers.push(header);
            }
            tracing::trace!(
                target: "pangoro-chapel",
                "Relaying headers[{:?}:{:?}] to Pangoro",
                checkpoint_number + 200,
                checkpoint_number + 200 + next_block_range.end - 1
            );
            let tx = pangoro.import_finalized_epoch_header(headers).await?;
            tracing::info!(
                target: "pangoro-chapel",
                "Sending tx: {:?}",
                tx
            );
        }
        tokio::time::sleep(std::time::Duration::from_secs(6)).await;
    }
}