Skip to content

Commit 3d5bbd6

Browse files
committed
refactor: update deny list check for v2
Signed-off-by: Gustavo Inacio <[email protected]>
1 parent 7d912af commit 3d5bbd6

File tree

4 files changed

+135
-24
lines changed

4 files changed

+135
-24
lines changed

.sqlx/query-f1d8dcf24c9677ef789469d37733f354c5d246db5bb1506839cd7b07cdcc7065.json

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/service/src/tap/checks/deny_list_check.rs

Lines changed: 89 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,14 @@ use crate::{
1616
tap::{CheckingReceipt, TapReceipt},
1717
};
1818

19+
enum DenyListVersion {
20+
V1,
21+
V2,
22+
}
23+
1924
pub struct DenyListCheck {
20-
sender_denylist: Arc<RwLock<HashSet<Address>>>,
21-
_sender_denylist_watcher_handle: Arc<tokio::task::JoinHandle<()>>,
25+
sender_denylist_v1: Arc<RwLock<HashSet<Address>>>,
26+
sender_denylist_v2: Arc<RwLock<HashSet<Address>>>,
2227
sender_denylist_watcher_cancel_token: tokio_util::sync::CancellationToken,
2328

2429
#[cfg(test)]
@@ -29,43 +34,65 @@ impl DenyListCheck {
2934
pub async fn new(pgpool: PgPool) -> Self {
3035
// Listen to pg_notify events. We start it before updating the sender_denylist so that we
3136
// don't miss any updates. PG will buffer the notifications until we start consuming them.
32-
let mut pglistener = PgListener::connect_with(&pgpool.clone()).await.unwrap();
33-
pglistener
37+
let mut pglistener_v1 = PgListener::connect_with(&pgpool.clone()).await.unwrap();
38+
let mut pglistener_v2 = PgListener::connect_with(&pgpool.clone()).await.unwrap();
39+
pglistener_v1
3440
.listen("scalar_tap_deny_notification")
3541
.await
3642
.expect(
3743
"should be able to subscribe to Postgres Notify events on the channel \
3844
'scalar_tap_deny_notification'",
3945
);
4046

47+
pglistener_v2
48+
.listen("tap_horizon_deny_notification")
49+
.await
50+
.expect(
51+
"should be able to subscribe to Postgres Notify events on the channel \
52+
'tap_horizon_deny_notification'",
53+
);
54+
4155
// Fetch the denylist from the DB
42-
let sender_denylist = Arc::new(RwLock::new(HashSet::new()));
43-
Self::sender_denylist_reload(pgpool.clone(), sender_denylist.clone())
56+
let sender_denylist_v1 = Arc::new(RwLock::new(HashSet::new()));
57+
let sender_denylist_v2 = Arc::new(RwLock::new(HashSet::new()));
58+
Self::sender_denylist_reload_v1(pgpool.clone(), sender_denylist_v1.clone())
4459
.await
4560
.expect("should be able to fetch the sender_denylist from the DB on startup");
4661

4762
#[cfg(test)]
4863
let notify = std::sync::Arc::new(tokio::sync::Notify::new());
4964

5065
let sender_denylist_watcher_cancel_token = tokio_util::sync::CancellationToken::new();
51-
let sender_denylist_watcher_handle = Arc::new(tokio::spawn(Self::sender_denylist_watcher(
66+
tokio::spawn(Self::sender_denylist_watcher(
5267
pgpool.clone(),
53-
pglistener,
54-
sender_denylist.clone(),
68+
pglistener_v1,
69+
sender_denylist_v1.clone(),
5570
sender_denylist_watcher_cancel_token.clone(),
71+
DenyListVersion::V1,
5672
#[cfg(test)]
5773
notify.clone(),
58-
)));
74+
));
75+
76+
tokio::spawn(Self::sender_denylist_watcher(
77+
pgpool.clone(),
78+
pglistener_v2,
79+
sender_denylist_v1.clone(),
80+
sender_denylist_watcher_cancel_token.clone(),
81+
DenyListVersion::V2,
82+
#[cfg(test)]
83+
notify.clone(),
84+
));
85+
5986
Self {
60-
sender_denylist,
61-
_sender_denylist_watcher_handle: sender_denylist_watcher_handle,
87+
sender_denylist_v1,
88+
sender_denylist_v2,
6289
sender_denylist_watcher_cancel_token,
6390
#[cfg(test)]
6491
notify,
6592
}
6693
}
6794

68-
async fn sender_denylist_reload(
95+
async fn sender_denylist_reload_v1(
6996
pgpool: PgPool,
7097
denylist_rwlock: Arc<RwLock<HashSet<Address>>>,
7198
) -> anyhow::Result<()> {
@@ -86,11 +113,33 @@ impl DenyListCheck {
86113
Ok(())
87114
}
88115

116+
async fn sender_denylist_reload_v2(
117+
pgpool: PgPool,
118+
denylist_rwlock: Arc<RwLock<HashSet<Address>>>,
119+
) -> anyhow::Result<()> {
120+
// Fetch the denylist from the DB
121+
let sender_denylist = sqlx::query!(
122+
r#"
123+
SELECT sender_address FROM tap_horizon_denylist
124+
"#
125+
)
126+
.fetch_all(&pgpool)
127+
.await?
128+
.iter()
129+
.map(|row| Address::from_str(&row.sender_address))
130+
.collect::<Result<HashSet<_>, _>>()?;
131+
132+
*(denylist_rwlock.write().unwrap()) = sender_denylist;
133+
134+
Ok(())
135+
}
136+
89137
async fn sender_denylist_watcher(
90138
pgpool: PgPool,
91139
mut pglistener: PgListener,
92140
denylist: Arc<RwLock<HashSet<Address>>>,
93141
cancel_token: tokio_util::sync::CancellationToken,
142+
version: DenyListVersion,
94143
#[cfg(test)] notify: std::sync::Arc<tokio::sync::Notify>,
95144
) {
96145
#[derive(serde::Deserialize)]
@@ -137,10 +186,14 @@ impl DenyListCheck {
137186
denylist.",
138187
denylist_notification.tg_op
139188
);
140-
141-
Self::sender_denylist_reload(pgpool.clone(), denylist.clone())
142-
.await
143-
.expect("should be able to reload the sender denylist")
189+
match version {
190+
DenyListVersion::V1 => Self::sender_denylist_reload_v1(pgpool.clone(), denylist.clone())
191+
.await
192+
.expect("should be able to reload the sender denylist"),
193+
DenyListVersion::V2 => Self::sender_denylist_reload_v2(pgpool.clone(), denylist.clone())
194+
.await
195+
.expect("should be able to reload the sender denylist"),
196+
}
144197
}
145198
}
146199
#[cfg(test)]
@@ -153,18 +206,30 @@ impl DenyListCheck {
153206

154207
#[async_trait::async_trait]
155208
impl Check<TapReceipt> for DenyListCheck {
156-
async fn check(&self, ctx: &tap_core::receipt::Context, _: &CheckingReceipt) -> CheckResult {
209+
async fn check(
210+
&self,
211+
ctx: &tap_core::receipt::Context,
212+
receipt: &CheckingReceipt,
213+
) -> CheckResult {
157214
let Sender(receipt_sender) = ctx
158215
.get::<Sender>()
159216
.ok_or(CheckError::Failed(anyhow::anyhow!("Could not find sender")))?;
160217

218+
let denied = match receipt.signed_receipt() {
219+
TapReceipt::V1(_) => self
220+
.sender_denylist_v1
221+
.read()
222+
.unwrap()
223+
.contains(receipt_sender),
224+
TapReceipt::V2(_) => self
225+
.sender_denylist_v2
226+
.read()
227+
.unwrap()
228+
.contains(receipt_sender),
229+
};
230+
161231
// Check that the sender is not denylisted
162-
if self
163-
.sender_denylist
164-
.read()
165-
.unwrap()
166-
.contains(receipt_sender)
167-
{
232+
if denied {
168233
return Err(CheckError::Failed(anyhow::anyhow!(
169234
"Received a receipt from a denylisted sender: {}",
170235
receipt_sender
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
-- Add down migration script here
2+
DROP TRIGGER IF EXISTS deny_update ON tap_horizon_deny CASCADE;
3+
4+
DROP FUNCTION IF EXISTS tap_horizon_deny_notify() CASCADE;
5+
26
DROP TABLE IF EXISTS tap_horizon_denylist CASCADE;

migrations/20250212211337_tap_horizon_sender_denylist.up.sql

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,25 @@
22
CREATE TABLE IF NOT EXISTS tap_horizon_denylist (
33
sender_address CHAR(40) PRIMARY KEY
44
);
5+
6+
7+
CREATE FUNCTION tap_horizon_deny_notify()
8+
RETURNS trigger AS
9+
$$
10+
BEGIN
11+
IF TG_OP = 'DELETE' THEN
12+
PERFORM pg_notify('tap_horizon_deny_notification', format('{"tg_op": "DELETE", "sender_address": "%s"}', OLD.sender_address));
13+
RETURN OLD;
14+
ELSIF TG_OP = 'INSERT' THEN
15+
PERFORM pg_notify('tap_horizon_deny_notification', format('{"tg_op": "INSERT", "sender_address": "%s"}', NEW.sender_address));
16+
RETURN NEW;
17+
ELSE -- UPDATE OR TRUNCATE, should never happen
18+
PERFORM pg_notify('tap_horizon_deny_notification', format('{"tg_op": "%s", "sender_address": null}', TG_OP, NEW.sender_address));
19+
RETURN NEW;
20+
END IF;
21+
END;
22+
$$ LANGUAGE 'plpgsql';
23+
24+
CREATE TRIGGER deny_update AFTER INSERT OR UPDATE OR DELETE
25+
ON tap_horizon_denylist
26+
FOR EACH ROW EXECUTE PROCEDURE tap_horizon_deny_notify();

0 commit comments

Comments
 (0)