Relayer whitelist (#570)
* Update ABIs, compiles. Removed Inbox indexer and cached checkpoints from the Inbox * Rm checkpoint indexing test * Update InboxValidatorManager, just need to connect the pieces now * Add channels * So close * Fix InboxValidatorManager deploy * Make message processor slightly more readable * Create loop-control crate * Try to bring some sanity to message processor * Fix bug where prover_sync wasn't in line with latest_signed_checkpoint * Rm immediate message processing, clean up * rm abacus-cli * more cleanup * rename / rm some settings * TS renames / rms * Lower some processing failure logs to info * Checkpoint fetcher doesn't need the CommittedMessages * Hardcode kathy dispatching * Move to watch channel * I'm sorry clippy * nits * rm some nonce related stuff * more refactoring * cleanup * Add whitelist settings * Whitelist filtering * fix docker build * use relayed terminology * Change whitelist tuple order * naming change * move whitelist check to before fetching proof * Update span event * make messages singular * Update error * minor cleanup * Update configs in scripts * Whitelist testing and parsing fixes Co-authored-by: Trevor Porter <trkporter@ucdavis.edu>pull/586/head
parent
bcf638ff4f
commit
4d9603c868
@ -0,0 +1,311 @@ |
||||
use std::fmt; |
||||
use std::fmt::{Display, Formatter}; |
||||
use std::marker::PhantomData; |
||||
use std::num::ParseIntError; |
||||
|
||||
use ethers::prelude::*; |
||||
use serde::de::{Error, SeqAccess, Visitor}; |
||||
use serde::{Deserialize, Deserializer}; |
||||
|
||||
use abacus_core::AbacusMessage; |
||||
|
||||
/// Whitelist defining which messages should be relayed. If no wishlist is provided ALL
|
||||
/// messages will be relayed.
|
||||
///
|
||||
/// Valid options for each of the tuple elements are
|
||||
/// - wildcard "*"
|
||||
/// - single value in decimal or hex (must start with `0x`) format
|
||||
/// - list of values in decimal or hex format
|
||||
/// - defaults to wildcards
|
||||
#[derive(Debug, Deserialize, Default, Clone)] |
||||
#[serde(transparent)] |
||||
pub struct Whitelist(Option<Vec<WhitelistElement>>); |
||||
|
||||
#[derive(Debug, Clone, PartialEq)] |
||||
enum Filter<T> { |
||||
Wildcard, |
||||
Enumerated(Vec<T>), |
||||
} |
||||
|
||||
impl<T> Default for Filter<T> { |
||||
fn default() -> Self { |
||||
Self::Wildcard |
||||
} |
||||
} |
||||
|
||||
impl<T: PartialEq> Filter<T> { |
||||
fn matches(&self, v: &T) -> bool { |
||||
match self { |
||||
Filter::Wildcard => true, |
||||
Filter::Enumerated(list) => list.iter().any(|i| i == v), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl<T: Display> Display for Filter<T> { |
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
||||
match self { |
||||
Self::Wildcard => write!(f, "*"), |
||||
Self::Enumerated(l) if l.len() == 1 => write!(f, "{}", l[0]), |
||||
Self::Enumerated(l) => { |
||||
write!(f, "[")?; |
||||
for i in l { |
||||
write!(f, "{i},")?; |
||||
} |
||||
write!(f, "]") |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
#[derive(Deserialize)] |
||||
#[serde(untagged)] |
||||
enum StrOrInt<'a> { |
||||
Str(&'a str), |
||||
Int(u32), |
||||
} |
||||
|
||||
impl TryFrom<StrOrInt<'_>> for u32 { |
||||
type Error = ParseIntError; |
||||
|
||||
fn try_from(v: StrOrInt) -> Result<Self, Self::Error> { |
||||
match v { |
||||
StrOrInt::Str(s) => s.parse(), |
||||
StrOrInt::Int(i) => Ok(i), |
||||
} |
||||
} |
||||
} |
||||
|
||||
struct FilterVisitor<T>(PhantomData<T>); |
||||
impl<'de> Visitor<'de> for FilterVisitor<u32> { |
||||
type Value = Filter<u32>; |
||||
|
||||
fn expecting(&self, fmt: &mut Formatter) -> fmt::Result { |
||||
write!(fmt, "Expecting either a wildcard \"*\", decimal/hex value string, or list of decimal/hex value strings") |
||||
} |
||||
|
||||
fn visit_u32<E>(self, v: u32) -> Result<Self::Value, E> |
||||
where |
||||
E: Error, |
||||
{ |
||||
Ok(Self::Value::Enumerated(vec![v])) |
||||
} |
||||
|
||||
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E> |
||||
where |
||||
E: Error, |
||||
{ |
||||
if v <= u32::MAX as u64 { |
||||
Ok(Self::Value::Enumerated(vec![v as u32])) |
||||
} else { |
||||
Err(E::custom("Domain Id must fit within a u32 value")) |
||||
} |
||||
} |
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> |
||||
where |
||||
E: Error, |
||||
{ |
||||
Ok(if v == "*" { |
||||
Self::Value::Wildcard |
||||
} else { |
||||
Self::Value::Enumerated(vec![v.parse::<u32>().map_err(to_serde_err)?]) |
||||
}) |
||||
} |
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> |
||||
where |
||||
A: SeqAccess<'de>, |
||||
{ |
||||
let mut values = Vec::new(); |
||||
while let Some(i) = seq.next_element::<StrOrInt>()? { |
||||
values.push(i.try_into().map_err(to_serde_err)?); |
||||
} |
||||
Ok(Self::Value::Enumerated(values)) |
||||
} |
||||
} |
||||
|
||||
impl<'de> Visitor<'de> for FilterVisitor<H256> { |
||||
type Value = Filter<H256>; |
||||
|
||||
fn expecting(&self, fmt: &mut Formatter) -> fmt::Result { |
||||
write!( |
||||
fmt, |
||||
"Expecting either a wildcard \"*\", hex address string, or list of hex address strings" |
||||
) |
||||
} |
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> |
||||
where |
||||
E: Error, |
||||
{ |
||||
Ok(if v == "*" { |
||||
Self::Value::Wildcard |
||||
} else { |
||||
Self::Value::Enumerated(vec![parse_addr(v)?]) |
||||
}) |
||||
} |
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> |
||||
where |
||||
A: SeqAccess<'de>, |
||||
{ |
||||
let mut values = Vec::new(); |
||||
while let Some(i) = seq.next_element::<&str>()? { |
||||
values.push(parse_addr(i)?) |
||||
} |
||||
Ok(Self::Value::Enumerated(values)) |
||||
} |
||||
} |
||||
|
||||
impl<'de> Deserialize<'de> for Filter<u32> { |
||||
fn deserialize<D>(d: D) -> Result<Self, D::Error> |
||||
where |
||||
D: Deserializer<'de>, |
||||
{ |
||||
d.deserialize_any(FilterVisitor::<u32>(Default::default())) |
||||
} |
||||
} |
||||
|
||||
impl<'de> Deserialize<'de> for Filter<H256> { |
||||
fn deserialize<D>(d: D) -> Result<Self, D::Error> |
||||
where |
||||
D: Deserializer<'de>, |
||||
{ |
||||
d.deserialize_any(FilterVisitor::<H256>(Default::default())) |
||||
} |
||||
} |
||||
|
||||
#[derive(Debug, Deserialize, Clone)] |
||||
#[serde(tag = "type", rename_all = "camelCase")] |
||||
struct WhitelistElement { |
||||
#[serde(default)] |
||||
source_domain: Filter<u32>, |
||||
#[serde(default)] |
||||
source_address: Filter<H256>, |
||||
#[serde(default)] |
||||
destination_domain: Filter<u32>, |
||||
#[serde(default)] |
||||
destination_address: Filter<H256>, |
||||
} |
||||
|
||||
impl Display for WhitelistElement { |
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
||||
write!(f, "{{sourceDomain: {}, sourceAddress: {}, destinationDomain: {}, destinationAddress: {}}}", self.source_domain, self.source_address, self.destination_domain, self.destination_address) |
||||
} |
||||
} |
||||
|
||||
impl Whitelist { |
||||
pub fn msg_matches(&self, msg: &AbacusMessage) -> bool { |
||||
self.matches(msg.origin, &msg.sender, msg.destination, &msg.recipient) |
||||
} |
||||
|
||||
pub fn matches( |
||||
&self, |
||||
src_domain: u32, |
||||
src_addr: &H256, |
||||
dst_domain: u32, |
||||
dst_addr: &H256, |
||||
) -> bool { |
||||
if let Some(rules) = &self.0 { |
||||
rules.iter().any(|rule| { |
||||
rule.source_domain.matches(&src_domain) |
||||
&& rule.source_address.matches(src_addr) |
||||
&& rule.destination_domain.matches(&dst_domain) |
||||
&& rule.destination_address.matches(dst_addr) |
||||
}) |
||||
} else { |
||||
// by default if there is no whitelist, allow everything
|
||||
true |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl Display for Whitelist { |
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
||||
if let Some(wl) = &self.0 { |
||||
write!(f, "[")?; |
||||
for i in wl { |
||||
write!(f, "{i},")?; |
||||
} |
||||
write!(f, "]") |
||||
} else { |
||||
write!(f, "null") |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn to_serde_err<IE: ToString, OE: Error>(e: IE) -> OE { |
||||
OE::custom(e.to_string()) |
||||
} |
||||
|
||||
fn parse_addr<E: Error>(addr_str: &str) -> Result<H256, E> { |
||||
if addr_str.len() <= 42 { |
||||
addr_str.parse::<H160>().map(H256::from) |
||||
} else { |
||||
addr_str.parse::<H256>() |
||||
} |
||||
.map_err(to_serde_err) |
||||
} |
||||
|
||||
#[cfg(test)] |
||||
mod test { |
||||
use ethers::prelude::*; |
||||
|
||||
use super::{Filter::*, Whitelist}; |
||||
|
||||
#[test] |
||||
fn basic_config() { |
||||
let whitelist: Whitelist = serde_json::from_str(r#"[{"sourceDomain": "*", "sourceAddress": "*", "destinationDomain": "*", "destinationAddress": "*"}, {}]"#).unwrap(); |
||||
assert!(whitelist.0.is_some()); |
||||
assert_eq!(whitelist.0.as_ref().unwrap().len(), 2); |
||||
let elem = &whitelist.0.as_ref().unwrap()[0]; |
||||
assert_eq!(elem.destination_domain, Wildcard); |
||||
assert_eq!(elem.destination_address, Wildcard); |
||||
assert_eq!(elem.source_domain, Wildcard); |
||||
assert_eq!(elem.source_address, Wildcard); |
||||
|
||||
let elem = &whitelist.0.as_ref().unwrap()[1]; |
||||
assert_eq!(elem.destination_domain, Wildcard); |
||||
assert_eq!(elem.destination_address, Wildcard); |
||||
assert_eq!(elem.source_domain, Wildcard); |
||||
assert_eq!(elem.source_address, Wildcard); |
||||
} |
||||
|
||||
#[test] |
||||
fn config_with_address() { |
||||
let whitelist: Whitelist = serde_json::from_str(r#"[{"sourceAddress": "0x9d4454B023096f34B160D6B654540c56A1F81688", "destinationAddress": "9d4454B023096f34B160D6B654540c56A1F81688"}]"#).unwrap(); |
||||
assert!(whitelist.0.is_some()); |
||||
assert_eq!(whitelist.0.as_ref().unwrap().len(), 1); |
||||
let elem = &whitelist.0.as_ref().unwrap()[0]; |
||||
assert_eq!(elem.destination_domain, Wildcard); |
||||
assert_eq!( |
||||
elem.destination_address, |
||||
Enumerated(vec!["0x9d4454B023096f34B160D6B654540c56A1F81688" |
||||
.parse::<H160>() |
||||
.unwrap() |
||||
.into()]) |
||||
); |
||||
assert_eq!(elem.source_domain, Wildcard); |
||||
assert_eq!( |
||||
elem.source_address, |
||||
Enumerated(vec!["0x9d4454B023096f34B160D6B654540c56A1F81688" |
||||
.parse::<H160>() |
||||
.unwrap() |
||||
.into()]) |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn config_with_multiple_domains() { |
||||
let whitelist: Whitelist = |
||||
serde_json::from_str(r#"[{"destinationDomain": ["13372", "13373"]}]"#).unwrap(); |
||||
assert!(whitelist.0.is_some()); |
||||
assert_eq!(whitelist.0.as_ref().unwrap().len(), 1); |
||||
let elem = &whitelist.0.as_ref().unwrap()[0]; |
||||
assert_eq!(elem.destination_domain, Enumerated(vec![13372, 13373])); |
||||
assert_eq!(elem.destination_address, Wildcard); |
||||
assert_eq!(elem.source_domain, Wildcard); |
||||
assert_eq!(elem.source_address, Wildcard); |
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
[package] |
||||
name = "loop-control" |
||||
version = "0.1.0" |
||||
edition = "2021" |
||||
description = "A simple library which provides an easy way to breakup logic within a loop without losing the ability to pass loop control logic back." |
||||
|
||||
[dependencies] |
@ -0,0 +1,33 @@ |
||||
/// A loop control operation.
|
||||
#[must_use] |
||||
#[derive(Debug, Copy, Clone)] |
||||
pub enum LoopControl { |
||||
/// No op, just flow through the rest of the loop normally
|
||||
Flow, |
||||
/// Inject `continue` and run next loop iteration
|
||||
Continue, |
||||
/// Inject `break` and end the loop
|
||||
Break, |
||||
} |
||||
|
||||
impl Default for LoopControl { |
||||
fn default() -> Self { |
||||
LoopControl::Flow |
||||
} |
||||
} |
||||
|
||||
/// Handle a loop control operation. This must be called directly within a loop.
|
||||
#[macro_export] |
||||
macro_rules! loop_ctrl { |
||||
($ctrl:expr) => { |
||||
match $ctrl { |
||||
::loop_control::LoopControl::Flow => {} |
||||
::loop_control::LoopControl::Continue => { |
||||
continue; |
||||
} |
||||
::loop_control::LoopControl::Break => { |
||||
break; |
||||
} |
||||
} |
||||
}; |
||||
} |
@ -0,0 +1,34 @@ |
||||
use loop_control::{loop_ctrl, LoopControl::*}; |
||||
|
||||
#[test] |
||||
fn flows_loop() { |
||||
let mut i = 0; |
||||
for _ in 0..5 { |
||||
i += 1; |
||||
loop_ctrl!(Flow); |
||||
i += 1; |
||||
} |
||||
assert_eq!(i, 10); |
||||
} |
||||
|
||||
#[test] |
||||
fn continues_loop() { |
||||
let mut i = 0; |
||||
for _ in 0..5 { |
||||
i += 1; |
||||
loop_ctrl!(Continue); |
||||
i += 1; |
||||
} |
||||
assert_eq!(i, 5); |
||||
} |
||||
|
||||
#[test] |
||||
fn breaks_loop() { |
||||
let mut i = 0; |
||||
for _ in 0..5 { |
||||
i += 1; |
||||
loop_ctrl!(Break); |
||||
i += 1; |
||||
} |
||||
assert_eq!(i, 1); |
||||
} |
Loading…
Reference in new issue