1use std::collections::{BTreeMap, BTreeSet};
2
3use agglayer_interop_types::{ImportedBridgeExit, TokenInfo};
4use agglayer_primitives::{ruint::UintTryFrom, FromBool, Hashable};
5use agglayer_tries::{roots::LocalExitRoot, smt::Smt};
6use pessimistic_proof::{
7 core::commitment::{PessimisticRootCommitmentValues, PessimisticRootCommitmentVersion},
8 local_balance_tree::{LocalBalancePath, LocalBalanceTree, LOCAL_BALANCE_TREE_DEPTH},
9 local_state::StateCommitment,
10 multi_batch_header::MultiBatchHeader,
11 nullifier_tree::{NullifierKey, NullifierPath, NullifierTree, NULLIFIER_TREE_DEPTH},
12 LocalNetworkState,
13};
14use unified_bridge::LocalExitTree;
15
16use crate::{
17 aggchain_data::{CertificateAggchainDataCtx, CertificateAggchainDataWithCtx},
18 Certificate, Digest, Error, U256, U512,
19};
20
21#[derive(Clone, Debug, Default)]
24pub struct LocalNetworkStateData {
25 pub exit_tree: LocalExitTree,
27 pub balance_tree: Smt<LOCAL_BALANCE_TREE_DEPTH>,
29 pub nullifier_tree: Smt<NULLIFIER_TREE_DEPTH>,
31}
32
33impl From<LocalNetworkStateData> for LocalNetworkState {
34 fn from(state: LocalNetworkStateData) -> Self {
35 LocalNetworkState {
36 exit_tree: state.exit_tree,
37 balance_tree: LocalBalanceTree::new_with_root(state.balance_tree.root),
38 nullifier_tree: NullifierTree::new_with_root(state.nullifier_tree.root),
39 }
40 }
41}
42
43impl From<LocalNetworkStateData> for pessimistic_proof::NetworkState {
44 fn from(state: LocalNetworkStateData) -> Self {
45 LocalNetworkState::from(state).into()
46 }
47}
48
49#[derive(Debug, Clone)]
52pub enum PessimisticRootInput {
53 Computed(PessimisticRootCommitmentVersion),
55 Fetched(Digest),
57}
58
59#[derive(Debug, Clone)]
61pub struct L1WitnessCtx {
62 pub l1_info_root: Digest,
63 pub prev_pessimistic_root: PessimisticRootInput,
64 pub aggchain_data_ctx: CertificateAggchainDataCtx,
65}
66
67impl LocalNetworkStateData {
68 pub fn prune_stale_nodes(&mut self) -> Result<(), Error> {
70 self.balance_tree.traverse_and_prune()?;
71 self.nullifier_tree.traverse_and_prune()?;
72
73 Ok(())
74 }
75
76 pub fn apply_certificate(
79 &mut self,
80 certificate: &Certificate,
81 ctx_from_l1: L1WitnessCtx,
82 ) -> Result<MultiBatchHeader, Error> {
83 let L1WitnessCtx {
84 prev_pessimistic_root,
85 l1_info_root,
86 aggchain_data_ctx,
87 } = ctx_from_l1;
88
89 let gers_are_consistent = certificate
90 .imported_bridge_exits
91 .iter()
92 .all(|ib| ib.valid_claim());
93
94 if !gers_are_consistent {
95 return Err(Error::InconsistentGlobalExitRoot);
96 }
97
98 let prev_pessimistic_root = match prev_pessimistic_root {
100 PessimisticRootInput::Fetched(settled_from_l1) => settled_from_l1,
101 PessimisticRootInput::Computed(version) => PessimisticRootCommitmentValues {
102 balance_root: self.balance_tree.root.into(),
103 nullifier_root: self.nullifier_tree.root.into(),
104 ler_leaf_count: self.exit_tree.leaf_count(),
105 height: certificate.height.as_u64(),
106 origin_network: certificate.network_id,
107 }
108 .compute_pp_root(version),
109 };
110
111 let prev_local_exit_root = self.exit_tree.get_root().into();
112 if certificate.prev_local_exit_root != prev_local_exit_root {
113 return Err(Error::MismatchPrevLocalExitRoot {
114 computed: prev_local_exit_root,
115 declared: certificate.prev_local_exit_root,
116 });
117 }
118
119 for e in certificate.bridge_exits.iter() {
120 self.exit_tree.add_leaf(e.hash())?;
121 }
122
123 let balances_proofs: BTreeMap<TokenInfo, (U256, LocalBalancePath)> = {
124 let imported_bridge_exits = certificate.imported_bridge_exits.iter().filter(|b| {
126 b.bridge_exit.amount_token_info().origin_network != certificate.network_id
127 });
128
129 let bridge_exits = certificate
131 .bridge_exits
132 .iter()
133 .filter(|b| b.amount_token_info().origin_network != certificate.network_id);
134
135 let mutated_tokens: BTreeSet<TokenInfo> = {
137 let imported_tokens = imported_bridge_exits
138 .clone()
139 .map(|exit| exit.bridge_exit.amount_token_info());
140 let exported_tokens = bridge_exits.clone().map(|exit| exit.amount_token_info());
141 imported_tokens.chain(exported_tokens).collect()
142 };
143
144 let initial_balances: BTreeMap<_, _> = mutated_tokens
145 .iter()
146 .map(|&token| {
147 let balance =
148 U256::from_be_bytes(*self.balance_tree.get(token).unwrap_or_default());
149 (token, balance)
150 })
151 .collect();
152
153 let mut new_balances: BTreeMap<_, _> = initial_balances
154 .iter()
155 .map(|(&token, &balance)| (token, U512::from(balance)))
156 .collect();
157
158 for imported_bridge_exit in imported_bridge_exits {
159 let token = imported_bridge_exit.bridge_exit.amount_token_info();
160 new_balances.insert(
161 token,
162 new_balances[&token]
163 .checked_add(U512::from(imported_bridge_exit.bridge_exit.amount))
164 .ok_or(Error::BalanceOverflow(token))?,
165 );
166 }
167
168 for bridge_exit in bridge_exits {
169 let token = bridge_exit.amount_token_info();
170 new_balances.insert(
171 token,
172 new_balances[&token]
173 .checked_sub(U512::from(bridge_exit.amount))
174 .ok_or(Error::BalanceUnderflow(token))?,
175 );
176 }
177
178 mutated_tokens
180 .into_iter()
181 .map(|token| {
182 let initial_balance = initial_balances[&token];
183
184 let new_balance = U256::uint_try_from(new_balances[&token])
185 .map_err(|_| Error::BalanceOverflow(token))?;
186
187 let balance_proof_error =
188 |source| Error::BalanceProofGenerationFailed { source, token };
189
190 let path = if initial_balance.is_zero() {
191 self.balance_tree
192 .get_inclusion_proof_zero(token)
193 .map_err(balance_proof_error)?
194 } else {
195 self.balance_tree
196 .get_inclusion_proof(token)
197 .map_err(balance_proof_error)?
198 };
199
200 self.balance_tree
201 .update(token, new_balance.to_be_bytes().into())
202 .map_err(balance_proof_error)?;
203
204 Ok((token, (initial_balance, path)))
205 })
206 .collect::<Result<BTreeMap<_, _>, Error>>()?
207 };
208
209 let imported_bridge_exits: Vec<(ImportedBridgeExit, NullifierPath)> = certificate
210 .imported_bridge_exits
211 .iter()
212 .map(|exit| {
213 let nullifier_key: NullifierKey = exit.global_index.into();
214 let nullifier_error = |source| Error::NullifierPathGenerationFailed {
215 source,
216 global_index: exit.global_index,
217 };
218 let nullifier_path = self
219 .nullifier_tree
220 .get_non_inclusion_proof(nullifier_key)
221 .map_err(nullifier_error)?;
222 self.nullifier_tree
223 .insert(nullifier_key, Digest::from_bool(true))
224 .map_err(nullifier_error)?;
225 Ok((exit.clone(), nullifier_path))
226 })
227 .collect::<Result<Vec<_>, Error>>()?;
228
229 let computed = LocalExitRoot::from(self.exit_tree.get_root());
231 if computed != certificate.new_local_exit_root {
232 return Err(Error::MismatchNewLocalExitRoot {
233 declared: certificate.new_local_exit_root,
234 computed,
235 });
236 }
237
238 let chain_payload = certificate
239 .aggchain_data
240 .clone()
241 .try_into()
242 .map_err(Error::InvalidChainData)?;
243
244 let aggchain_data = CertificateAggchainDataWithCtx(chain_payload, aggchain_data_ctx)
245 .try_into()
246 .map_err(Error::InvalidChainData)?;
247
248 Ok(MultiBatchHeader {
249 origin_network: certificate.network_id,
250 bridge_exits: certificate.bridge_exits.clone(),
251 imported_bridge_exits,
252 balances_proofs,
253 l1_info_root,
254 height: certificate.height.as_u64(),
255 prev_pessimistic_root,
256 aggchain_data,
257 certificate_id: certificate.hash().into(),
258 })
259 }
260
261 pub fn make_multi_batch_header(
264 &self,
265 certificate: &Certificate,
266 witness_fetched_from_l1: L1WitnessCtx,
267 ) -> Result<MultiBatchHeader, Error> {
268 self.clone()
269 .apply_certificate(certificate, witness_fetched_from_l1)
270 }
271
272 pub fn get_roots(&self) -> StateCommitment {
273 StateCommitment {
274 exit_root: self.exit_tree.get_root(),
275 ler_leaf_count: self.exit_tree.leaf_count(),
276 balance_root: self.balance_tree.root,
277 nullifier_root: self.nullifier_tree.root,
278 }
279 }
280}