hyperstack_interpreter/
resolvers.rs

1use std::collections::HashMap;
2
3/// Context provided to primary key resolver functions
4pub struct ResolveContext<'a> {
5    #[allow(dead_code)]
6    pub(crate) state_id: u32,
7    pub(crate) slot: u64,
8    pub(crate) signature: String,
9    pub(crate) reverse_lookups: &'a mut std::collections::HashMap<String, crate::vm::PdaReverseLookup>,
10}
11
12impl<'a> ResolveContext<'a> {
13    /// Create a new ResolveContext (primarily for use by generated code)
14    pub fn new(
15        state_id: u32,
16        slot: u64,
17        signature: String,
18        reverse_lookups: &'a mut std::collections::HashMap<String, crate::vm::PdaReverseLookup>,
19    ) -> Self {
20        Self {
21            state_id,
22            slot,
23            signature,
24            reverse_lookups,
25        }
26    }
27    
28    /// Try to reverse lookup a PDA address to find the seed value
29    /// This is typically used to find the primary key from a PDA account address
30    pub fn pda_reverse_lookup(&mut self, pda_address: &str) -> Option<String> {
31        // Default lookup name - could be made configurable
32        let lookup_name = "default_pda_lookup";
33        
34        if let Some(lookup_table) = self.reverse_lookups.get_mut(lookup_name) {
35            let result = lookup_table.lookup(pda_address);
36            if result.is_some() {
37                tracing::debug!("✓ PDA reverse lookup hit: {} -> {:?}", pda_address, result);
38            } else {
39                tracing::debug!("✗ PDA reverse lookup miss: {}", pda_address);
40            }
41            result
42        } else {
43            tracing::debug!("✗ PDA reverse lookup table '{}' not found", lookup_name);
44            None
45        }
46    }
47    
48    pub fn slot(&self) -> u64 {
49        self.slot
50    }
51    
52    pub fn signature(&self) -> &str {
53        &self.signature
54    }
55}
56
57/// Result of attempting to resolve a primary key
58pub enum KeyResolution {
59    /// Primary key successfully resolved
60    Found(String),
61    
62    /// Queue this update until we see one of these instruction discriminators
63    /// The discriminators identify which instructions can populate the reverse lookup
64    QueueUntil(&'static [u8]),
65    
66    /// Skip this update entirely (don't queue)
67    Skip,
68}
69
70/// Context provided to instruction hook functions
71pub struct InstructionContext<'a> {
72    pub(crate) accounts: HashMap<String, String>,
73    #[allow(dead_code)]
74    pub(crate) state_id: u32,
75    pub(crate) reverse_lookup_tx: &'a mut dyn ReverseLookupUpdater,
76    pub(crate) pending_updates: Vec<crate::vm::PendingAccountUpdate>,
77    // Fields for metrics context integration
78    pub(crate) registers: Option<&'a mut Vec<crate::vm::RegisterValue>>,
79    pub(crate) state_reg: Option<crate::vm::Register>,
80    #[allow(dead_code)]
81    pub(crate) compiled_paths: Option<&'a HashMap<String, crate::metrics_context::CompiledPath>>,
82    pub(crate) instruction_data: Option<&'a serde_json::Value>,
83    pub(crate) slot: Option<u64>,
84    pub(crate) signature: Option<String>,
85    pub(crate) timestamp: Option<i64>,
86    // Track which fields were modified by imperative hooks
87    pub(crate) dirty_fields: std::collections::HashSet<String>,
88}
89
90pub trait ReverseLookupUpdater {
91    fn update(&mut self, pda_address: String, seed_value: String) -> Vec<crate::vm::PendingAccountUpdate>;
92    fn flush_pending(&mut self, pda_address: &str) -> Vec<crate::vm::PendingAccountUpdate>;
93}
94
95impl<'a> InstructionContext<'a> {
96    /// Create a new InstructionContext (primarily for use by generated code)
97    pub fn new(
98        accounts: HashMap<String, String>,
99        state_id: u32,
100        reverse_lookup_tx: &'a mut dyn ReverseLookupUpdater,
101    ) -> Self {
102        Self {
103            accounts,
104            state_id,
105            reverse_lookup_tx,
106            pending_updates: Vec::new(),
107            registers: None,
108            state_reg: None,
109            compiled_paths: None,
110            instruction_data: None,
111            slot: None,
112            signature: None,
113            timestamp: None,
114            dirty_fields: std::collections::HashSet::new(),
115        }
116    }
117    
118    /// Create InstructionContext with metrics support
119    #[allow(clippy::too_many_arguments)]
120    pub fn with_metrics(
121        accounts: HashMap<String, String>,
122        state_id: u32,
123        reverse_lookup_tx: &'a mut dyn ReverseLookupUpdater,
124        registers: &'a mut Vec<crate::vm::RegisterValue>,
125        state_reg: crate::vm::Register,
126        compiled_paths: &'a HashMap<String, crate::metrics_context::CompiledPath>,
127        instruction_data: &'a serde_json::Value,
128        slot: Option<u64>,
129        signature: Option<String>,
130        timestamp: i64,
131    ) -> Self {
132        Self {
133            accounts,
134            state_id,
135            reverse_lookup_tx,
136            pending_updates: Vec::new(),
137            registers: Some(registers),
138            state_reg: Some(state_reg),
139            compiled_paths: Some(compiled_paths),
140            instruction_data: Some(instruction_data),
141            slot,
142            signature,
143            timestamp: Some(timestamp),
144            dirty_fields: std::collections::HashSet::new(),
145        }
146    }
147    
148    /// Get an account address by its name from the instruction
149    pub fn account(&self, name: &str) -> Option<String> {
150        self.accounts.get(name).cloned()
151    }
152    
153    /// Register a reverse lookup: PDA address -> seed value
154    /// This also flushes any pending account updates for this PDA
155    /// 
156    /// The pending account updates are accumulated internally and can be retrieved
157    /// via `take_pending_updates()` after all hooks have executed.
158    pub fn register_pda_reverse_lookup(&mut self, pda_address: &str, seed_value: &str) {
159        tracing::info!("📝 Registering PDA reverse lookup: {} -> {}", pda_address, seed_value);
160        let pending = self.reverse_lookup_tx.update(pda_address.to_string(), seed_value.to_string());
161        if !pending.is_empty() {
162            tracing::info!("   🔄 Flushed {} pending account update(s) for this PDA", pending.len());
163        }
164        self.pending_updates.extend(pending);
165    }
166    
167    /// Take all accumulated pending updates
168    /// 
169    /// This should be called after all instruction hooks have executed to retrieve
170    /// any pending account updates that need to be reprocessed.
171    pub fn take_pending_updates(&mut self) -> Vec<crate::vm::PendingAccountUpdate> {
172        std::mem::take(&mut self.pending_updates)
173    }
174
175    /// Get the fields modified by imperative hooks
176    pub fn dirty_fields(&self) -> &std::collections::HashSet<String> {
177        &self.dirty_fields
178    }
179
180    /// Get the current state register value (for generating mutations)
181    pub fn state_value(&self) -> Option<&serde_json::Value> {
182        if let (Some(registers), Some(state_reg)) = (self.registers.as_ref(), self.state_reg) {
183            Some(&registers[state_reg])
184        } else {
185            None
186        }
187    }
188    
189    /// Get a field value from the entity state
190    /// This allows reading aggregated values or other entity fields
191    pub fn get<T: serde::de::DeserializeOwned>(&self, field_path: &str) -> Option<T> {
192        if let (Some(registers), Some(state_reg)) = (self.registers.as_ref(), self.state_reg) {
193            let state = &registers[state_reg];
194            self.get_nested_value(state, field_path)
195                .and_then(|v| serde_json::from_value(v.clone()).ok())
196        } else {
197            None
198        }
199    }
200
201    /// Set a field value in the entity state
202    pub fn set<T: serde::Serialize>(&mut self, field_path: &str, value: T) {
203        if let (Some(registers), Some(state_reg)) = (self.registers.as_mut(), self.state_reg) {
204            let serialized = serde_json::to_value(value).ok();
205            if let Some(val) = serialized {
206                Self::set_nested_value_static(&mut registers[state_reg], field_path, val);
207                // Track this field as dirty so it gets included in mutations
208                self.dirty_fields.insert(field_path.to_string());
209                println!("      ✓ Set field '{}' and marked as dirty", field_path);
210            }
211        } else {
212            println!("      ⚠️  Cannot set field '{}': metrics not configured (registers={}, state_reg={:?})", 
213                field_path, self.registers.is_some(), self.state_reg);
214        }
215    }
216
217    /// Increment a numeric field (creates it as 0 if it doesn't exist)
218    pub fn increment(&mut self, field_path: &str, amount: i64) {
219        let current = self.get::<i64>(field_path).unwrap_or(0);
220        self.set(field_path, current + amount);
221    }
222
223    /// Helper to get nested value from JSON
224    fn get_nested_value<'b>(&self, value: &'b serde_json::Value, path: &str) -> Option<&'b serde_json::Value> {
225        let mut current = value;
226        for segment in path.split('.') {
227            current = current.get(segment)?;
228        }
229        Some(current)
230    }
231
232    /// Helper to set nested value in JSON
233    fn set_nested_value_static(value: &mut serde_json::Value, path: &str, new_value: serde_json::Value) {
234        let segments: Vec<&str> = path.split('.').collect();
235        if segments.is_empty() {
236            return;
237        }
238
239        // Navigate to parent
240        let mut current = value;
241        for segment in &segments[..segments.len() - 1] {
242            if !current.is_object() {
243                *current = serde_json::json!({});
244            }
245            let obj = current.as_object_mut().unwrap();
246            current = obj.entry(segment.to_string()).or_insert(serde_json::json!({}));
247        }
248
249        // Set final value
250        if !current.is_object() {
251            *current = serde_json::json!({});
252        }
253        if let Some(obj) = current.as_object_mut() {
254            obj.insert(segments[segments.len() - 1].to_string(), new_value);
255        }
256    }
257    
258    /// Access instruction data field
259    pub fn data<T: serde::de::DeserializeOwned>(&self, field: &str) -> Option<T> {
260        self.instruction_data
261            .and_then(|data| data.get(field))
262            .and_then(|v| serde_json::from_value(v.clone()).ok())
263    }
264    
265    /// Get the current timestamp
266    pub fn timestamp(&self) -> i64 {
267        self.timestamp.unwrap_or(0)
268    }
269    
270    /// Get the current slot
271    pub fn slot(&self) -> Option<u64> {
272        self.slot
273    }
274    
275    /// Get the current signature
276    pub fn signature(&self) -> Option<&str> {
277        self.signature.as_deref()
278    }
279}