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()
}
}