15
15
// along with the Provable SDK library. If not, see <https://www.gnu.org/licenses/>.
16
16
17
17
use crate :: types:: native:: CurrentNetwork ;
18
- use crate :: types:: native:: { ProgramIDNative , IdentifierNative , RecordPlaintextNative } ;
18
+ use crate :: types:: native:: { ProgramIDNative , IdentifierNative , RecordPlaintextNative , ViewKeyNative } ;
19
19
use anyhow:: { anyhow, bail, Result } ;
20
20
use futures:: future:: join_all;
21
21
use indexmap:: IndexMap ;
@@ -29,8 +29,7 @@ use snarkvm_console::{
29
29
use snarkvm_ledger_query:: QueryTrait ;
30
30
use std:: str:: FromStr ;
31
31
32
- /// A snapshot-based query object used to pin the block height, state root,
33
- /// and state paths to a single ledger view during online execution.
32
+ /// A snapshot-based query object used to pin the block height, state root, and state paths to a single ledger view during online execution.
34
33
#[ derive( Clone , Debug , Deserialize , Eq , PartialEq , Serialize ) ]
35
34
pub struct SnapshotQuery {
36
35
block_height : u32 ,
@@ -75,47 +74,82 @@ impl SnapshotQuery {
75
74
node_url : & str ,
76
75
program_id : & ProgramID < CurrentNetwork > ,
77
76
record_name : & Identifier < CurrentNetwork > ,
78
- view_key : Field < CurrentNetwork > ,
77
+ view_key : & ViewKeyNative ,
79
78
js_inputs : & [ JsValue ] ,
80
79
) -> Result < Self > {
81
80
// 1) Extract commitments from inputs.
82
- let commitments = collect_commitments_from_inputs ( program_id, record_name, view_key, js_inputs) ?;
81
+ let commitments = Self :: collect_commitments_from_inputs ( program_id, record_name, view_key, js_inputs) ?;
83
82
84
- // Fast path: if there are no record inputs, still pin a snapshot (height + root) for consistency .
85
- let ( snap_root, snap_height) = snapshot_head ( node_url) . await ?;
83
+ // 2) Take snapshot and build base query .
84
+ let ( snap_root, snap_height) = Self :: snapshot_head ( node_url) . await ?;
86
85
let mut query = SnapshotQuery :: new ( snap_height, & snap_root) ?;
87
86
88
-
89
87
if commitments. is_empty ( ) {
90
88
return Ok ( query) ;
91
89
}
92
90
93
- // 2) Fetch state paths concurrently (anchored to the chosen snapshot).
94
- // If your node cannot fetch-at-root, you can fetch plain paths, parse their embedded roots,
95
- // and ensure they're consistent; otherwise re-snapshot and retry.
96
- // Precompute owned strings to avoid borrowing temporaries into async futures.
97
- let cm_strings: Vec < String > = commitments. iter ( ) . map ( |c| c. to_string ( ) ) . collect ( ) ;
91
+ // 3) Fetch state paths concurrently at that root.
92
+ let commitment_strings: Vec < String > = commitments. iter ( ) . map ( |c| c. to_string ( ) ) . collect ( ) ;
98
93
let root_str = snap_root. clone ( ) ;
99
- let futs = cm_strings
100
- . iter ( )
101
- . map ( |cm_s| fetch_state_path_at_root ( node_url , cm_s . as_str ( ) , root_str . as_str ( ) ) ) ;
94
+ let futs = commitment_strings . iter ( ) . map ( |commitment_s| {
95
+ Self :: fetch_state_path_at_root ( node_url , commitment_s . as_str ( ) , root_str . as_str ( ) )
96
+ } ) ;
102
97
let results = join_all ( futs) . await ;
103
98
104
- // 3 ) Insert all paths; verify consistency against the pinned root .
105
- for ( cm , res) in commitments. iter ( ) . zip ( results. into_iter ( ) ) {
106
- let sp_str = res?;
107
- // Optional safety: parse and ensure the path's root matches the pinned root.
108
- let sp = StatePath :: < CurrentNetwork > :: from_str ( & sp_str ) . map_err ( |e| anyhow ! ( e. to_string( ) ) ) ?;
109
- let path_root = sp . global_state_root ( ) . to_string ( ) ;
99
+ // 4 ) Insert paths and sanity check .
100
+ for ( commitment , res) in commitments. iter ( ) . zip ( results. into_iter ( ) ) {
101
+ let state_path_str = res?;
102
+ let state_path = StatePath :: < CurrentNetwork > :: from_str ( & state_path_str )
103
+ . map_err ( |e| anyhow ! ( e. to_string( ) ) ) ?;
104
+ let path_root = state_path . global_state_root ( ) . to_string ( ) ;
110
105
if path_root != snap_root {
111
- // Strategy: bail and let caller retry; or resnapshot + refetch here.
112
106
bail ! ( "State path root mismatch: expected {}, got {}" , snap_root, path_root) ;
113
107
}
114
- query. add_state_path ( & cm . to_string ( ) , & sp_str ) ?;
108
+ query. add_state_path ( & commitment . to_string ( ) , & state_path_str ) ?;
115
109
}
116
110
117
111
Ok ( query)
118
112
}
113
+
114
+ /// Detect plaintext records in `js_inputs` and compute their commitments.
115
+ fn collect_commitments_from_inputs (
116
+ program_id : & ProgramIDNative ,
117
+ record_name : & IdentifierNative ,
118
+ view_key : & ViewKeyNative ,
119
+ js_inputs : & [ wasm_bindgen:: JsValue ] ,
120
+ ) -> anyhow:: Result < Vec < Field < CurrentNetwork > > > {
121
+ let mut out = Vec :: new ( ) ;
122
+
123
+ for js in js_inputs {
124
+ if let Some ( s) = js. as_string ( ) {
125
+ // Heuristic: plaintext record strings contain `_nonce`.
126
+ if !s. contains ( "_nonce" ) { continue ; }
127
+
128
+ if let Ok ( rec) = RecordPlaintextNative :: from_str ( & s) {
129
+ let record_view_key: Field < CurrentNetwork > =
130
+ ( * rec. nonce ( ) * & * * view_key) . to_x_coordinate ( ) ;
131
+
132
+ let commitment = rec
133
+ . to_commitment ( program_id, record_name, & record_view_key) ?;
134
+ out. push ( commitment) ;
135
+ }
136
+ }
137
+ }
138
+ Ok ( out)
139
+ }
140
+
141
+ async fn snapshot_head ( _node_url : & str ) -> Result < ( String , u32 ) > {
142
+ Err ( anyhow ! ( "snapshot_head() not implemented" ) )
143
+ }
144
+
145
+ async fn fetch_state_path_at_root (
146
+ _node_url : & str ,
147
+ _commitment : & str ,
148
+ _state_root : & str ,
149
+ ) -> anyhow:: Result < String > {
150
+ Err ( anyhow ! ( "fetch_state_path_at_root() not implemented" ) )
151
+ }
152
+
119
153
}
120
154
121
155
#[ async_trait:: async_trait( ?Send ) ]
@@ -156,47 +190,3 @@ impl QueryTrait<CurrentNetwork> for SnapshotQuery {
156
190
Ok ( self . block_height )
157
191
}
158
192
}
159
-
160
- /* --------------------------- internal helpers --------------------------- */
161
-
162
- /// Heuristic: detect plaintext records from JS inputs and compute commitments.
163
- fn collect_commitments_from_inputs (
164
- program_id : & ProgramIDNative ,
165
- record_name : & IdentifierNative ,
166
- view_key : Field < CurrentNetwork > ,
167
- js_inputs : & [ JsValue ] ,
168
- ) -> anyhow:: Result < Vec < Field < CurrentNetwork > > > {
169
- let mut out = Vec :: new ( ) ;
170
-
171
- for js in js_inputs {
172
- if let Some ( s) = js. as_string ( ) {
173
- // Quick filter: record plaintexts include a `_nonce` field.
174
- if !s. contains ( "_nonce" ) {
175
- continue ;
176
- }
177
- // Try parse as a plaintext record. Skip if not a record.
178
- if let Ok ( rec) = RecordPlaintextNative :: from_str ( & s) {
179
- let cm = rec. to_commitment ( program_id, record_name, & view_key) ;
180
- out. push ( cm?) ;
181
- }
182
- }
183
- }
184
- Ok ( out)
185
- }
186
-
187
- /// Return a single `(state_root_string, block_height)` snapshot.
188
- /// TODO: replace with real node client calls.
189
- async fn snapshot_head ( _node_url : & str ) -> Result < ( String , u32 ) > {
190
- // Example (pseudo):
191
- // let head = client.latest_head(_node_url).await?;
192
- // Ok((head.state_root, head.block_height))
193
- Err ( anyhow ! ( "snapshot_head() not implemented" ) )
194
- }
195
-
196
- /// Fetch a `StatePath` (as string) for `commitment` anchored to `state_root`
197
- /// TODO: replace with real node client call and ensure it returns a path at the requested root
198
- async fn fetch_state_path_at_root ( _node_url : & str , _commitment : & str , _state_root : & str ) -> anyhow:: Result < String > {
199
- // Example (pseudo):
200
- // client.state_path_at_root(_node_url, _commitment, _state_root).await
201
- Err ( anyhow ! ( "fetch_state_path_at_root() not implemented" ) )
202
- }
0 commit comments