/ node / consensus / src / ipc_hooks.rs
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  }