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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
//! `ContentBlacklist` contract ABI bindings (ADR 011 § Content Takedown).
//!
//! Generated by the `alloy::sol!` macro from the Solidity surface in
//! `contracts/src/ContentBlacklist.sol`. The node's blacklist compliance
//! watcher ([`crate`] consumer `decdn-node::blacklist_watcher`) consumes the
//! read path (`isHashBlacklistedForOperator`) plus the `HashBlacklisted` /
//! `HashRemoved` membership events: each event is a *signal to re-read* the
//! authoritative in-scope predicate for the hashes the node holds, then evict
//! any that are blacklisted for this operator (global ∪ current-region ∪
//! ripening-prev-region — the region logic lives entirely inside the contract
//! view, so the watcher never repacks a region).
//!
//! The write + `getHashEntry` surface is mirrored for the cross-layer e2e
//! harness (`decdn-e2e`), which drives governance blacklist adds and reads
//! `addedAt` to time the compliance / slashability windows.
// The `sol!`-generated bindings include macro-emitted code that uses patterns
// workspace clippy denies (raw indexing into ABI fixed-size byte arrays,
// `unwrap` on infallible conversions). These allows scope the relaxation to
// this module only.
#[allow(
clippy::expect_used,
clippy::indexing_slicing,
clippy::panic,
clippy::unwrap_used,
clippy::pub_underscore_fields,
clippy::missing_docs_in_private_items,
clippy::too_many_arguments,
missing_debug_implementations,
missing_docs,
non_snake_case,
non_camel_case_types
)]
mod sol_types {
alloy::sol! {
#[sol(rpc)]
contract ContentBlacklist {
/// Per-(region, hash) entry. `addedAt == 0` means "not blacklisted".
///
/// MUST match `ContentBlacklist.HashEntry` field-for-field and in
/// order — a static struct return is ABI-identical to the flattened
/// tuple, so a missing field silently mis-decodes rather than
/// failing loudly.
///
/// `effectiveAt` (`addedAt + complianceWindow`, ADR 011 § Compliance
/// Window) is the slashability boundary, NOT `addedAt`: an entry is
/// unenforceable during the grace period a node needs to learn of it.
/// `emergency` + `category` carry the auto-expiry term for entries
/// added by the emergency multisig.
struct HashEntry {
uint64 addedAt;
uint64 effectiveAt;
bool emergency;
uint8 category;
}
/// Region-scoped regional-body record (ADR 011 § Regional Governance
/// Bodies). `body == address(0)` means no body is registered.
struct RegionalBody {
address body;
uint64 suspendedAt;
bool suspensionRatified;
}
// -----------------------------------------------------------------
// View functions (consumed by the node runtime)
// -----------------------------------------------------------------
/// True iff `hash` is an enforceable blacklist entry in scope for
/// `operator`: global ∪ current-region ∪ (ripening-window)
/// previous-region, per ADR 030. The single source of truth for the
/// watcher's scope decision — the region packing / ripening math
/// stays on-chain.
function isHashBlacklistedForOperator(bytes32 hash, address operator)
external
view
returns (bool);
/// Raw entry for `(region, hash)`. Used by the e2e harness to time
/// windows; the node relies on the operator-scope predicate above
/// instead.
function getHashEntry(bytes32 region, bytes32 hash)
external
view
returns (HashEntry memory);
/// True while `origin` is denied at the origin level, honouring
/// emergency auto-expiry (a raw mapping read would not).
///
/// The per-address boot reconcile relies on this predicate, which is usable
/// because the deny-set is enumerable and has something to iterate over.
///
/// This alone is NOT the address-liveness predicate: it reads only
/// `_isOriginBlacklisted` and returns `false` for an OPERATOR-only
/// entry (`addOperator` sets `isOperatorBlacklisted` and never touches
/// the origin mapping). A boot reconcile of the `blacklistedAddresses`
/// union MUST OR this with `isOperatorBlacklisted` — the same
/// disjunction `ContentBlacklist._syncAddr` / `OriginAssignment` use —
/// or it drops every voted-out operator (#1499).
function isOriginBlacklisted(address origin) external view returns (bool);
/// The operator half of the address deny-set (`addOperator` /
/// `removeOperator`). Public auto-getter for the `mapping(address =>
/// bool)`; there is no emergency auto-expiry on this leg, so — unlike
/// [`isOriginBlacklisted`] — a raw read IS the live value. The boot
/// reconcile ORs the two: `isOriginBlacklisted(a) ||
/// isOperatorBlacklisted(a)`.
function isOperatorBlacklisted(address operator) external view returns (bool);
/// Every region key whose entries are in scope for `operator` right
/// now: `GLOBAL` (always first), the declared region, and — while the
/// ADR 030 ripening window is open — the previous one. One to three
/// keys. Resolved on-chain through the same internal the slash-
/// eligibility predicate uses, so the region packing and ripening
/// math cannot drift between the two.
function getScopeRegions(address operator) external view returns (bytes32[] memory);
/// Number of entries indexed under `region`. Companion to
/// `blacklistedHashes`; RAW membership, matching that view.
function blacklistedHashCount(bytes32 region) external view returns (uint256);
/// A page of `region`'s entry set. A page shorter than `limit` is the
/// end of the set.
///
/// Returns RAW membership — an emergency entry past its deadline is
/// still listed, because filtering would put holes in a paginated
/// view and dropping entries is the fail-open direction. Apply
/// `isHashBlacklistedForOperator` (or `getHashEntry`) for liveness.
///
/// Order is NOT stable across mutations (swap-and-pop removal), so
/// every page — and the count it is checked against — MUST be read at
/// one pinned block height, or a concurrent removal can move an
/// unread element into an already-read slot and skip it.
function blacklistedHashes(bytes32 region, uint256 offset, uint256 limit)
external
view
returns (bytes32[] memory);
/// Number of addresses denied at the origin or operator level.
function blacklistedAddressCount() external view returns (uint256);
/// A page of the UNION of the origin and operator deny lists — the
/// same disjunction `OriginAssignment` evaluates per address, and the
/// only readable source for that set. Same raw-membership and
/// pinned-block caveats as `blacklistedHashes`.
function blacklistedAddresses(uint256 offset, uint256 limit)
external
view
returns (address[] memory);
// -----------------------------------------------------------------
// Write functions (governance — driven by the e2e harness)
// -----------------------------------------------------------------
/// Add `hash` to the global blacklist (`GOVERNANCE_ROLE`). `reason`
/// is the free-form on-chain audit-trail note (DMCA/DSA notice id).
function addHashGlobal(bytes32 hash, string reason) external;
/// Add `hash` to `region`'s blacklist (`REGIONAL_BODY_ROLE`). `reason`
/// is the free-form on-chain audit-trail note (DMCA/DSA notice id).
function addHashRegional(bytes32 region, bytes32 hash, string reason) external;
/// Remove `hash` from the global blacklist (`GOVERNANCE_ROLE`).
function removeHashGlobal(bytes32 hash) external;
/// Bind `body` to `region` and grant it `REGIONAL_BODY_ROLE`
/// (`GOVERNANCE_ROLE`). `emergencyMultisig` is the
/// `EMERGENCY_MULTISIG_ROLE` holder to check signer-disjointness
/// against; it is verified to hold the role.
function registerRegionalBody(bytes32 region, address body, address emergencyMultisig)
external;
/// The body registered for `region`, plus its suspension state.
function getRegionalBody(bytes32 region) external view returns (RegionalBody memory);
/// Grace (seconds) between a standard/regional add and the moment the
/// entry becomes slashable (ADR 011 § Compliance Window).
function complianceWindow() external view returns (uint64);
// -----------------------------------------------------------------
// Membership events (watcher re-read signals)
// -----------------------------------------------------------------
/// A `(region, hash)` entry was added (or re-added). `region` is
/// `bytes32("GLOBAL")` for the global scope.
///
/// These declarations MUST match `ContentBlacklist.sol` field-for-field,
/// including parameters this watcher never reads. `topic0` is the keccak of
/// the event signature, and the non-indexed params are the ABI-encoded data
/// payload — so a drift in either breaks the watcher twice over: it matches
/// no logs at all, and would mis-decode any it did match. It fails SILENTLY,
/// as an empty deny-set, which reads exactly like "nothing is blacklisted"
/// while the node serves content it is slashable for serving.
///
/// `reason` (the audit-trail note) is unused here for that reason alone:
/// it is part of the signature, so it is part of this declaration.
event HashBlacklisted(bytes32 indexed region, bytes32 indexed hash, string reason);
/// A `(region, hash)` entry was removed.
event HashRemoved(bytes32 indexed region, bytes32 indexed hash);
/// An origin/operator address entered or left the origin blacklist
/// (ADR 011 § Hash Evasion and Origin Blacklisting).
///
/// Like the hash events, it carries no version counter: a consumer
/// detects a missed one by re-enumerating, not from the event.
///
/// The set is enumerable via [`blacklistedAddresses`], so a consumer holds
/// the deny-set by enumeration and treats this event as a low-latency signal
/// to re-read rather than the source of truth; the boot reconcile draws on
/// the same enumeration.
event OriginBlacklistUpdated(address indexed origin, bool blacklisted);
/// Governance blacklisted an OPERATOR address (`addOperator`), which
/// also ejects it from `CapacityBond`. This is a SECOND, independent
/// origin-level list: `addOperator` writes `isOperatorBlacklisted`
/// and never touches `_isOriginBlacklisted`, so a consumer that
/// watches only `OriginBlacklistUpdated` misses the primary
/// governance path entirely. `OriginAssignment` treats the two as a
/// union (`isOriginBlacklisted(op) || isOperatorBlacklisted(op)`);
/// so must the node.
event OperatorBlacklisted(address indexed operator);
/// `removeOperator` — the inverse of `OperatorBlacklisted`.
event OperatorBlacklistCleared(address indexed operator);
}
}
}
pub use sol_types::ContentBlacklist;