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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
//! The `sov-sequencer-registry` module is responsible for sequencer
//! registration, slashing, and rewards. At the moment, only a centralized
//! sequencer is supported. The sequencer's address and bond are registered
//! during the rollup deployment.
//!
//! The module implements the [`sov_modules_api::hooks::ApplyBlobHooks`] trait.

#![deny(missing_docs)]

mod call;
mod genesis;
mod hooks;
#[cfg(feature = "native")]
pub mod query;

pub use call::CallMessage;
#[cfg(feature = "native")]
pub use query::{
    SequencerAddressResponse, SequencerRegistryRpcClient, SequencerRegistryRpcImpl,
    SequencerRegistryRpcServer,
};
use sov_modules_api::{CallResponse, Error, ModuleInfo, Spec};
use sov_state::{StateMap, StateValue, WorkingSet};

/// A type alias for DA addresses.
///
/// TODO: All usages of this type ought to be replaced with a DA address generic,
/// once <https://github.com/Sovereign-Labs/sovereign-sdk/issues/493> is fixed.
type DaAddress = Vec<u8>;

/// Genesis configuration for the [`SequencerRegistry`] module.
///
/// This `struct` must be passed as an argument to
/// [`Module::genesis`](sov_modules_api::Module::genesis).
///
// TODO: Should we allow multiple sequencers in genesis?
pub struct SequencerConfig<C: sov_modules_api::Context> {
    /// The rollup address of the sequencer.
    pub seq_rollup_address: C::Address,
    /// The Data Availability (DA) address of the sequencer.
    pub seq_da_address: DaAddress,
    /// Coins that will be slashed if the sequencer is malicious.
    ///
    /// The coins will be transferred from
    /// [`SequencerConfig::seq_rollup_address`] to this module's address
    /// ([`ModuleInfo::address`]) and locked away until the sequencer
    /// decides to exit (unregister).
    ///
    /// Only sequencers that are [`SequencerRegistry::is_sender_allowed`] list are
    /// allowed to exit.
    pub coins_to_lock: sov_bank::Coins<C>,
    /// Determines whether this sequencer is *regular* or *preferred*.
    ///
    /// Batches from the preferred sequencer are always processed first in
    /// block, which means the preferred sequencer can guarantee soft
    /// confirmation time for transactions.
    pub is_preferred_sequencer: bool,
}

/// The `sov-sequencer-registry` module `struct`.
#[cfg_attr(feature = "native", derive(sov_modules_api::ModuleCallJsonSchema))]
#[derive(Clone, ModuleInfo)]
pub struct SequencerRegistry<C: sov_modules_api::Context> {
    /// The address of the `sov_sequencer_registry` module.
    /// Note: this is address is generated by the module framework and the
    /// corresponding private key is unknown.
    #[address]
    pub(crate) address: C::Address,

    /// Reference to the Bank module.
    #[module]
    pub(crate) bank: sov_bank::Bank<C>,

    /// Only batches from sequencers from this list are going to be processed.
    #[state]
    pub(crate) allowed_sequencers: StateMap<DaAddress, C::Address>,

    /// Optional preferred sequencer.
    /// If set, batches from this sequencer will be processed first in block,
    /// So this sequencer can guarantee soft confirmation time for transactions
    #[state]
    pub(crate) preferred_sequencer: StateValue<DaAddress>,

    /// Coin's that will be slashed if the sequencer is malicious.
    /// The coins will be transferred from
    /// [`SequencerConfig::seq_rollup_address`] to
    /// [`SequencerRegistry::address`] and locked forever, until sequencer
    /// decides to exit (unregister).
    ///
    /// Only sequencers in the [`SequencerRegistry::allowed_sequencers`] list are
    /// allowed to exit.
    #[state]
    pub(crate) coins_to_lock: StateValue<sov_bank::Coins<C>>,
}

/// Result of applying a blob, from sequencer's point of view.
pub enum SequencerOutcome {
    /// The blob was applied successfully and the operation is concluded.
    Completed,
    /// The blob was *not* applied successfully. The sequencer has been slashed
    /// as a result of the invalid blob.
    Slashed {
        /// The address of the sequencer that was slashed.
        sequencer: DaAddress,
    },
}

impl<C: sov_modules_api::Context> sov_modules_api::Module for SequencerRegistry<C> {
    type Context = C;

    type Config = SequencerConfig<C>;

    type CallMessage = CallMessage;

    fn genesis(
        &self,
        config: &Self::Config,
        working_set: &mut WorkingSet<C::Storage>,
    ) -> Result<(), Error> {
        Ok(self.init_module(config, working_set)?)
    }

    fn call(
        &self,
        message: Self::CallMessage,
        context: &Self::Context,
        working_set: &mut WorkingSet<<Self::Context as Spec>::Storage>,
    ) -> Result<CallResponse, Error> {
        Ok(match message {
            CallMessage::Register { da_address } => {
                self.register(da_address, context, working_set)?
            }
            CallMessage::Exit { da_address } => self.exit(da_address, context, working_set)?,
        })
    }
}

impl<C: sov_modules_api::Context> SequencerRegistry<C> {
    /// Returns the configured amount of [`Coins`](sov_bank::Coins) to lock.
    pub fn get_coins_to_lock(
        &self,
        working_set: &mut WorkingSet<C::Storage>,
    ) -> Option<sov_bank::Coins<C>> {
        self.coins_to_lock.get(working_set)
    }

    pub(crate) fn register_sequencer(
        &self,
        da_address: DaAddress,
        rollup_address: &C::Address,
        working_set: &mut WorkingSet<C::Storage>,
    ) -> anyhow::Result<()> {
        if self
            .allowed_sequencers
            .get(&da_address, working_set)
            .is_some()
        {
            anyhow::bail!("sequencer {} already registered", rollup_address)
        }
        let locker = &self.address;
        let coins = self.coins_to_lock.get_or_err(working_set)?;
        self.bank
            .transfer_from(rollup_address, locker, coins, working_set)?;

        self.allowed_sequencers
            .set(&da_address, rollup_address, working_set);

        Ok(())
    }

    /// Returns the preferred sequencer, or [`None`] it wasn't set.
    ///
    /// Read about [`SequencerConfig::is_preferred_sequencer`] to learn about
    /// preferred sequencers.
    pub fn get_preferred_sequencer(
        &self,
        working_set: &mut WorkingSet<C::Storage>,
    ) -> Option<DaAddress> {
        self.preferred_sequencer.get(working_set)
    }

    /// Checks whether `sender` is a registered sequencer.
    pub fn is_sender_allowed<T: sov_modules_api::BasicAddress>(
        &self,
        sender: &T,
        working_set: &mut WorkingSet<C::Storage>,
    ) -> bool {
        // Clone to satisfy StateMap API
        // TODO: can be fixed after https://github.com/Sovereign-Labs/sovereign-sdk/issues/427
        self.allowed_sequencers
            .get(&sender.as_ref().to_vec(), working_set)
            .is_some()
    }
}