@@ -76,9 +76,13 @@ impl Store {
7676 outpoint : & OutPoint ,
7777 txout : & TxOut ,
7878 blinder_key : Option < [ u8 ; 32 ] > ,
79- ) -> Result < ( AssetId , u64 , bool ) , StoreError > {
79+ ) -> Result < ( AssetId , i64 , bool ) , StoreError > {
8080 if let ( Some ( asset) , Some ( value) ) = ( txout. asset . explicit ( ) , txout. value . explicit ( ) ) {
81- return Ok ( ( asset, value, false ) ) ;
81+ return Ok ( (
82+ asset,
83+ i64:: try_from ( value) . expect ( "UTXO values never exceed i64 max (9.2e18 vs max BTC supply ~2.1e15 sats)" ) ,
84+ false ,
85+ ) ) ;
8286 }
8387
8488 let Some ( key) = blinder_key else {
@@ -87,7 +91,12 @@ impl Store {
8791
8892 let secret_key = SecretKey :: from_slice ( & key) ?;
8993 let secrets = txout. unblind ( secp256k1:: SECP256K1 , secret_key) ?;
90- Ok ( ( secrets. asset , secrets. value , true ) )
94+ Ok ( (
95+ secrets. asset ,
96+ i64:: try_from ( secrets. value )
97+ . expect ( "UTXO values never exceed i64 max (9.2e18 vs max BTC supply ~2.1e15 sats)" ) ,
98+ true ,
99+ ) )
91100 }
92101
93102 async fn internal_insert (
@@ -101,23 +110,18 @@ impl Store {
101110
102111 let txid: & [ u8 ] = outpoint. txid . as_ref ( ) ;
103112 let vout = i64:: from ( outpoint. vout ) ;
104- let script_pubkey = txout. script_pubkey . as_bytes ( ) ;
105- let asset_bytes = asset_id. into_inner ( ) . 0 . to_vec ( ) ;
106- let value_i64 = value as i64 ;
107- let serialized = encode:: serialize ( & txout) ;
108- let is_conf_i64 = i64:: from ( is_confidential) ;
109113
110114 sqlx:: query (
111115 "INSERT INTO utxos (txid, vout, script_pubkey, asset_id, value, serialized, is_confidential)
112116 VALUES (?, ?, ?, ?, ?, ?, ?)" ,
113117 )
114118 . bind ( txid)
115119 . bind ( vout)
116- . bind ( script_pubkey)
117- . bind ( asset_bytes . as_slice ( ) )
118- . bind ( value_i64 )
119- . bind ( & serialized )
120- . bind ( is_conf_i64 )
120+ . bind ( txout . script_pubkey . as_bytes ( ) )
121+ . bind ( asset_id . into_inner ( ) . 0 . as_slice ( ) )
122+ . bind ( value )
123+ . bind ( encode :: serialize ( & txout ) )
124+ . bind ( i64 :: from ( is_confidential ) )
121125 . execute ( & mut * tx)
122126 . await ?;
123127
@@ -206,8 +210,12 @@ impl Store {
206210 /// Fetches UTXOs in batches until the required value is met (early termination).
207211 /// This avoids loading all matching UTXOs when only a subset is needed.
208212 async fn query_until_sufficient ( & self , filter : & Filter ) -> Result < QueryResult , StoreError > {
209- let required = filter. required_value . unwrap ( ) ;
213+ let Some ( required) = filter. required_value else {
214+ return Ok ( QueryResult :: Empty ) ;
215+ } ;
216+
210217 let mut entries = Vec :: new ( ) ;
218+
211219 let mut total_value: u64 = 0 ;
212220 let mut offset: i64 = 0 ;
213221
@@ -219,9 +227,9 @@ impl Store {
219227 }
220228
221229 for row in rows {
222- total_value = total_value. saturating_add ( row. value as u64 ) ;
223- let entry = row . into_entry ( ) ? ;
224- entries. push ( entry ) ;
230+ total_value = total_value. checked_add ( row. value ) . ok_or ( StoreError :: ValueOverflow ) ? ;
231+
232+ entries. push ( row . into_entry ( ) ? ) ;
225233
226234 // Early termination: we have enough value
227235 if total_value >= required {
@@ -285,8 +293,7 @@ impl Store {
285293
286294 /// Fetches all matching UTXOs (used when no `required_value` optimization applies).
287295 async fn query_all ( & self , filter : & Filter ) -> Result < QueryResult , StoreError > {
288- let limit = filter. limit . map ( |l| l as i64 ) ;
289- let rows = self . fetch_rows ( filter, limit, None ) . await ?;
296+ let rows = self . fetch_rows ( filter, filter. limit , None ) . await ?;
290297
291298 if rows. is_empty ( ) {
292299 return Ok ( QueryResult :: Empty ) ;
@@ -296,7 +303,7 @@ impl Store {
296303 let mut total_value: u64 = 0 ;
297304
298305 for row in rows {
299- total_value = total_value. saturating_add ( row. value as u64 ) ;
306+ total_value = total_value. saturating_add ( row. value ) ;
300307
301308 entries. push ( row. into_entry ( ) ?) ;
302309 }
@@ -319,10 +326,10 @@ impl Store {
319326#[ derive( sqlx:: FromRow ) ]
320327struct UtxoRow {
321328 txid : Vec < u8 > ,
322- vout : i64 ,
329+ vout : u32 ,
323330 serialized : Vec < u8 > ,
324331 is_confidential : i64 ,
325- value : i64 ,
332+ value : u64 ,
326333 blinding_key : Option < Vec < u8 > > ,
327334}
328335
@@ -334,7 +341,7 @@ impl UtxoRow {
334341 . map_err ( |_| sqlx:: Error :: Decode ( "Invalid txid length" . into ( ) ) ) ?;
335342
336343 let txid = Txid :: from_byte_array ( txid_array) ;
337- let outpoint = OutPoint :: new ( txid, self . vout as u32 ) ;
344+ let outpoint = OutPoint :: new ( txid, self . vout ) ;
338345
339346 let txout: TxOut = encode:: deserialize ( & self . serialized ) ?;
340347
@@ -556,7 +563,7 @@ mod tests {
556563 . unwrap ( ) ;
557564
558565 let filter = Filter :: new ( ) . asset_id ( asset) ;
559- let results = store. query ( & [ filter. clone ( ) ] ) . await . unwrap ( ) ;
566+ let results = store. query ( std :: slice :: from_ref ( & filter) ) . await . unwrap ( ) ;
560567 assert ! ( matches!( & results[ 0 ] , QueryResult :: Found ( e) if e. len( ) == 1 ) ) ;
561568
562569 store
0 commit comments