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
//! Ports used by the relayer to access the outside world

use async_trait::async_trait;
use fuel_core_storage::{
    not_found,
    tables::Messages,
    transactional::Transactional,
    Error as StorageError,
    Mappable,
    Result as StorageResult,
    StorageAsMut,
    StorageAsRef,
    StorageMutate,
};
use fuel_core_types::{
    blockchain::primitives::DaBlockHeight,
    entities::message::CheckedMessage,
};

#[cfg(test)]
mod tests;

/// Manages state related to supported external chains.
#[async_trait]
pub trait RelayerDb: Send + Sync {
    /// Add bridge messages to database. Messages are not revertible.
    /// Must only set a new da height if it is greater than the current.
    fn insert_messages(
        &mut self,
        da_height: &DaBlockHeight,
        messages: &[CheckedMessage],
    ) -> StorageResult<()>;

    /// Set finalized da height that represent last block from da layer that got finalized.
    /// This will only set the value if it is greater than the current.
    fn set_finalized_da_height_to_at_least(
        &mut self,
        block: &DaBlockHeight,
    ) -> StorageResult<()>;

    /// Get finalized da height that represent last block from da layer that got finalized.
    /// Panics if height is not set as of initialization of database.
    fn get_finalized_da_height(&self) -> StorageResult<DaBlockHeight>;
}

impl<T, Storage> RelayerDb for T
where
    T: Send + Sync,
    T: Transactional<Storage = Storage>,
    T: StorageMutate<RelayerMetadata, Error = StorageError>,
    Storage: StorageMutate<Messages, Error = StorageError>
        + StorageMutate<RelayerMetadata, Error = StorageError>,
{
    fn insert_messages(
        &mut self,
        da_height: &DaBlockHeight,
        messages: &[CheckedMessage],
    ) -> StorageResult<()> {
        // A transaction is required to ensure that the height is
        // set atomically with the insertion based on the current
        // height. Also so that the messages are inserted atomically
        // with the height.
        let mut db_tx = self.transaction();
        let db = db_tx.as_mut();

        let mut max_height = None;
        for message in messages {
            db.storage::<Messages>()
                .insert(message.id(), message.message())?;
            let max = max_height.get_or_insert(0u64);
            *max = (*max).max(message.message().da_height.0);
        }
        if let Some(height) = max_height {
            if **da_height < height {
                return Err(anyhow::anyhow!("Invalid da height").into())
            }
        }
        grow_monotonically(db, da_height)?;
        db_tx.commit()?;
        Ok(())
    }

    fn set_finalized_da_height_to_at_least(
        &mut self,
        height: &DaBlockHeight,
    ) -> StorageResult<()> {
        // A transaction is required to ensure that the height is
        // set atomically with the insertion based on the current
        // height.
        let mut db_tx = self.transaction();
        let db = db_tx.as_mut();
        grow_monotonically(db, height)?;
        db_tx.commit()?;
        Ok(())
    }

    fn get_finalized_da_height(&self) -> StorageResult<DaBlockHeight> {
        Ok(*StorageAsRef::storage::<RelayerMetadata>(&self)
            .get(&METADATA_KEY)?
            .ok_or(not_found!("DaBlockHeight missing for relayer"))?)
    }
}

fn grow_monotonically<Storage>(
    s: &mut Storage,
    height: &DaBlockHeight,
) -> StorageResult<()>
where
    Storage: StorageMutate<RelayerMetadata, Error = StorageError>,
{
    let current = (&s)
        .storage::<RelayerMetadata>()
        .get(&METADATA_KEY)?
        .map(|cow| cow.as_u64());
    match current {
        Some(current) => {
            if **height > current {
                s.storage::<RelayerMetadata>()
                    .insert(&METADATA_KEY, height)?;
            }
        }
        None => {
            s.storage::<RelayerMetadata>()
                .insert(&METADATA_KEY, height)?;
        }
    }
    Ok(())
}

/// Metadata for relayer.
pub struct RelayerMetadata;
impl Mappable for RelayerMetadata {
    type Key = Self::OwnedKey;
    type OwnedKey = ();
    type Value = Self::OwnedValue;
    type OwnedValue = DaBlockHeight;
}

/// Key for da height.
/// If the relayer metadata ever contains more than one key, this should be
/// changed from a unit value.
const METADATA_KEY: () = ();