ipc_hooks.rs
1 // Copyright (c) 2025-2026 ACDC Network 2 // This file is part of the alphaos library. 3 // 4 // Alpha Chain | Delta Chain Protocol 5 // International Monetary Graphite. 6 // 7 // Derived from Aleo (https://aleo.org) and ProvableHQ (https://provable.com). 8 // They built world-class ZK infrastructure. We installed the EASY button. 9 // Their cryptography: elegant. Our modifications: bureaucracy-compatible. 10 // Original brilliance: theirs. Robert's Rules: ours. Bugs: definitely ours. 11 // 12 // Original Aleo/ProvableHQ code subject to Apache 2.0 https://www.apache.org/licenses/LICENSE-2.0 13 // All modifications and new work: CC0 1.0 Universal Public Domain Dedication. 14 // No rights reserved. No permission required. No warranty. No refunds. 15 // 16 // https://creativecommons.org/publicdomain/zero/1.0/ 17 // SPDX-License-Identifier: CC0-1.0 18 19 //! IPC hooks for cross-chain communication with DELTA chain. 20 //! 21 //! This module provides the infrastructure for notifying the adnet runtime 22 //! about block finalization and cross-chain transactions. 23 24 use alphavm::prelude::*; 25 use tokio::sync::mpsc; 26 use tracing::{debug, error, info}; 27 28 /// Events emitted by consensus for IPC processing. 29 #[derive(Debug, Clone)] 30 pub enum IpcEvent<N: Network> { 31 /// A new block has been finalized. 32 BlockFinalized { 33 /// Block height. 34 height: u64, 35 /// State root hash (32 bytes). 36 state_root: [u8; 32], 37 /// Block hash for reference (Field element). 38 block_hash: Field<N>, 39 }, 40 41 /// A lock_for_sax transaction was included in a finalized block. 42 LockTransaction { 43 /// Unique lock identifier (transaction ID hash). 44 lock_id: [u8; 32], 45 /// User address who initiated the lock. 46 user: [u8; 32], 47 /// Amount locked in microcredits. 48 amount: u128, 49 /// Block height where lock was finalized. 50 block_height: u64, 51 /// Transaction index in block (for merkle proof). 52 tx_index: usize, 53 /// Transaction hash (merkle leaf). 54 tx_hash: [u8; 32], 55 /// All transaction hashes in the block (for merkle proof generation). 56 block_tx_hashes: Vec<[u8; 32]>, 57 }, 58 } 59 60 /// Trait for receiving IPC events from consensus. 61 /// 62 /// Implement this trait in adnet-runtime to receive notifications 63 /// about block finalization and cross-chain transactions. 64 #[allow(clippy::too_many_arguments)] 65 pub trait IpcEventHandler<N: Network>: Send + Sync { 66 /// Called when a new block is finalized. 67 fn on_block_finalized(&self, height: u64, state_root: [u8; 32], block_hash: Field<N>); 68 69 /// Called when a lock_for_sax transaction is detected. 70 /// Includes transaction hash and all block tx hashes for merkle proof generation. 71 fn on_lock_transaction( 72 &self, 73 lock_id: [u8; 32], 74 user: [u8; 32], 75 amount: u128, 76 block_height: u64, 77 tx_index: usize, 78 tx_hash: [u8; 32], 79 block_tx_hashes: Vec<[u8; 32]>, 80 ); 81 } 82 83 /// Channel-based IPC event sender. 84 /// 85 /// This implementation sends events through an async channel, 86 /// allowing the adnet runtime to process them asynchronously. 87 pub struct IpcEventSender<N: Network> { 88 tx: mpsc::UnboundedSender<IpcEvent<N>>, 89 } 90 91 impl<N: Network> IpcEventSender<N> { 92 /// Create a new sender/receiver pair. 93 pub fn new() -> (Self, IpcEventReceiver<N>) { 94 let (tx, rx) = mpsc::unbounded_channel(); 95 (Self { tx }, IpcEventReceiver { rx }) 96 } 97 98 /// Send an IPC event. 99 pub fn send(&self, event: IpcEvent<N>) { 100 if let Err(e) = self.tx.send(event) { 101 error!("Failed to send IPC event: {}", e); 102 } 103 } 104 } 105 106 impl<N: Network> Clone for IpcEventSender<N> { 107 fn clone(&self) -> Self { 108 Self { tx: self.tx.clone() } 109 } 110 } 111 112 impl<N: Network> IpcEventHandler<N> for IpcEventSender<N> { 113 fn on_block_finalized(&self, height: u64, state_root: [u8; 32], block_hash: Field<N>) { 114 debug!("IPC: Block finalized at height {}", height); 115 self.send(IpcEvent::BlockFinalized { height, state_root, block_hash }); 116 } 117 118 fn on_lock_transaction( 119 &self, 120 lock_id: [u8; 32], 121 user: [u8; 32], 122 amount: u128, 123 block_height: u64, 124 tx_index: usize, 125 tx_hash: [u8; 32], 126 block_tx_hashes: Vec<[u8; 32]>, 127 ) { 128 info!("IPC: Lock transaction detected at block {} (amount: {})", block_height, amount as f64 / 10000.0); 129 self.send(IpcEvent::LockTransaction { 130 lock_id, 131 user, 132 amount, 133 block_height, 134 tx_index, 135 tx_hash, 136 block_tx_hashes, 137 }); 138 } 139 } 140 141 /// Receiver for IPC events. 142 pub struct IpcEventReceiver<N: Network> { 143 rx: mpsc::UnboundedReceiver<IpcEvent<N>>, 144 } 145 146 impl<N: Network> IpcEventReceiver<N> { 147 /// Receive the next event, blocking until one is available. 148 pub async fn recv(&mut self) -> Option<IpcEvent<N>> { 149 self.rx.recv().await 150 } 151 152 /// Try to receive an event without blocking. 153 pub fn try_recv(&mut self) -> Option<IpcEvent<N>> { 154 self.rx.try_recv().ok() 155 } 156 } 157 158 /// No-op IPC handler for standalone operation (no adnet). 159 pub struct NoOpIpcHandler; 160 161 impl<N: Network> IpcEventHandler<N> for NoOpIpcHandler { 162 fn on_block_finalized(&self, _height: u64, _state_root: [u8; 32], _block_hash: Field<N>) { 163 // No-op 164 } 165 166 fn on_lock_transaction( 167 &self, 168 _lock_id: [u8; 32], 169 _user: [u8; 32], 170 _amount: u128, 171 _block_height: u64, 172 _tx_index: usize, 173 _tx_hash: [u8; 32], 174 _block_tx_hashes: Vec<[u8; 32]>, 175 ) { 176 // No-op 177 } 178 } 179 180 /// Helper to extract state root from a block. 181 pub fn extract_state_root<N: Network>(block: &alphavm::ledger::Block<N>) -> [u8; 32] { 182 // The state root is derived from the block's transactions root 183 // In production, this should be the actual state commitment 184 let header = block.header(); 185 let transactions_root = header.transactions_root(); 186 187 // Convert to bytes - using the transactions root as a proxy for state root 188 // In a full implementation, this would be the actual state trie root 189 let mut root = [0u8; 32]; 190 let root_bytes = transactions_root.to_bytes_le().unwrap_or_default(); 191 let len = root_bytes.len().min(32); 192 root[..len].copy_from_slice(&root_bytes[..len]); 193 root 194 } 195 196 /// Compute transaction hash for merkle tree. 197 fn compute_tx_hash<N: Network>(tx: &alphavm::ledger::block::ConfirmedTransaction<N>) -> [u8; 32] { 198 let tx_id_bytes = tx.id().to_bytes_le().unwrap_or_default(); 199 let mut hash = [0u8; 32]; 200 let len = tx_id_bytes.len().min(32); 201 hash[..len].copy_from_slice(&tx_id_bytes[..len]); 202 hash 203 } 204 205 /// Scan a block for lock_for_sax transactions. 206 /// 207 /// This function examines all transactions in a finalized block 208 /// and identifies those that lock AX for sAX minting on DELTA. 209 /// It provides transaction hashes for merkle proof generation. 210 pub fn scan_for_lock_transactions<N: Network>(block: &alphavm::ledger::Block<N>, handler: &dyn IpcEventHandler<N>) { 211 let block_height = block.height() as u64; 212 213 // Pre-compute all transaction hashes for merkle proof generation 214 let block_tx_hashes: Vec<[u8; 32]> = block.transactions().iter().map(compute_tx_hash::<N>).collect(); 215 216 for (tx_index, tx) in block.transactions().iter().enumerate() { 217 // Check if this is a lock_for_sax execution 218 if let Some((_lock_id, user, amount)) = is_lock_for_sax_transaction::<N>(tx) { 219 // Convert transaction ID to lock_id bytes 220 let tx_hash = compute_tx_hash::<N>(tx); 221 let lock_id_bytes = tx_hash; // Use tx hash as lock ID 222 223 handler.on_lock_transaction( 224 lock_id_bytes, 225 user, 226 amount, 227 block_height, 228 tx_index, 229 tx_hash, 230 block_tx_hashes.clone(), 231 ); 232 } 233 } 234 } 235 236 /// Check if a transaction is a lock_for_sax execution. 237 /// 238 /// Returns (lock_id, user_address, amount) if it is a lock transaction. 239 fn is_lock_for_sax_transaction<N: Network>( 240 tx: &alphavm::ledger::block::ConfirmedTransaction<N>, 241 ) -> Option<([u8; 32], [u8; 32], u128)> { 242 use alphavm::ledger::block::Transaction; 243 244 let transaction = tx.transaction(); 245 246 // Check if this is an execution transaction 247 if let Transaction::Execute(_, _, execution, _) = transaction { 248 // Check if it's calling the credits.alpha program 249 for transition in execution.transitions() { 250 let program_id = transition.program_id(); 251 let function_name = transition.function_name(); 252 253 // Check for lock_for_sax function call 254 // Program: credits.alpha 255 // Function: lock_for_sax 256 if program_id.to_string() == "credits.alpha" && function_name.to_string() == "lock_for_sax" { 257 // Extract inputs: (user: address, amount: u64) 258 // For now, return placeholder values - actual extraction 259 // requires parsing the transition inputs 260 if let Some((user, amount)) = extract_lock_inputs::<N>(transition) { 261 let mut lock_id = [0u8; 32]; 262 // Use transition ID as lock ID 263 let transition_id_bytes = transition.id().to_bytes_le().unwrap_or_default(); 264 let len = transition_id_bytes.len().min(32); 265 lock_id[..len].copy_from_slice(&transition_id_bytes[..len]); 266 267 return Some((lock_id, user, amount)); 268 } 269 } 270 } 271 } 272 273 None 274 } 275 276 /// Extract lock inputs from a transition. 277 /// 278 /// For lock_for_sax, the function expects: 279 /// - Public input 0: user address 280 /// - Public input 1: amount (u64) 281 fn extract_lock_inputs<N: Network>(transition: &alphavm::ledger::block::Transition<N>) -> Option<([u8; 32], u128)> { 282 use alphavm::{ 283 ledger::block::transition::Input, 284 prelude::{Literal, Plaintext}, 285 }; 286 287 let mut user_address: Option<[u8; 32]> = None; 288 let mut lock_amount: Option<u128> = None; 289 290 // Helper to extract address bytes 291 let extract_address = |addr: &alphavm::prelude::Address<N>| { 292 let addr_bytes = addr.to_bytes_le().unwrap_or_default(); 293 let mut user = [0u8; 32]; 294 let len = addr_bytes.len().min(32); 295 user[..len].copy_from_slice(&addr_bytes[..len]); 296 user 297 }; 298 299 // Helper to extract from literal 300 let process_literal = |literal: &Literal<N>, user: &mut Option<[u8; 32]>, amount: &mut Option<u128>| match literal { 301 Literal::Address(addr) if user.is_none() => *user = Some(extract_address(addr)), 302 Literal::U64(val) if amount.is_none() => *amount = Some(**val as u128), 303 Literal::U128(val) if amount.is_none() => *amount = Some(**val), 304 _ => {} 305 }; 306 307 // First, try to extract from public inputs 308 for input in transition.inputs() { 309 if let Input::Public(_, Some(Plaintext::Literal(literal, _))) = input { 310 process_literal(literal, &mut user_address, &mut lock_amount); 311 } 312 } 313 314 // If not found in inputs, check outputs (Future may contain finalize arguments) 315 if user_address.is_none() || lock_amount.is_none() { 316 for output in transition.outputs() { 317 if let alphavm::ledger::block::transition::Output::Future(_, Some(future)) = output { 318 for arg in future.arguments() { 319 if let alphavm::prelude::Argument::Plaintext(Plaintext::Literal(literal, _)) = arg { 320 process_literal(literal, &mut user_address, &mut lock_amount); 321 } 322 } 323 } 324 } 325 } 326 327 // Return if we have both values, otherwise return with defaults for partial data 328 match (user_address, lock_amount) { 329 (Some(user), Some(amount)) => Some((user, amount)), 330 (Some(user), None) => { 331 debug!("IPC: lock_for_sax found user but no amount"); 332 Some((user, 0)) 333 } 334 (None, Some(amount)) => { 335 debug!("IPC: lock_for_sax found amount but no user"); 336 Some(([0u8; 32], amount)) 337 } 338 (None, None) => { 339 debug!("IPC: lock_for_sax could not extract user or amount"); 340 None 341 } 342 } 343 } 344 345 #[cfg(test)] 346 mod tests { 347 use super::*; 348 use alphavm::prelude::MainnetV0; 349 350 #[test] 351 fn test_no_op_handler() { 352 let handler = NoOpIpcHandler; 353 // Should not panic - call via trait with explicit type 354 <NoOpIpcHandler as IpcEventHandler<MainnetV0>>::on_block_finalized(&handler, 0, [0u8; 32], Default::default()); 355 } 356 }