11use crate :: get_config;
22use async_trait:: async_trait;
33use bcr_ebill_core:: {
4- PublicKey , ServiceTraitBounds ,
4+ BitcoinAddress , PublicKey , ServiceTraitBounds ,
55 bill:: { InMempoolData , PaidData , PaymentState } ,
66 sum:: Sum ,
7+ timestamp:: Timestamp ,
78} ;
89use bitcoin:: { Network , secp256k1:: Scalar } ;
910use log:: debug;
@@ -46,35 +47,35 @@ use mockall::automock;
4647#[ cfg_attr( target_arch = "wasm32" , async_trait( ?Send ) ) ]
4748#[ cfg_attr( not( target_arch = "wasm32" ) , async_trait) ]
4849pub trait BitcoinClientApi : ServiceTraitBounds {
49- async fn get_address_info ( & self , address : & str ) -> Result < AddressInfo > ;
50+ async fn get_address_info ( & self , address : & BitcoinAddress ) -> Result < AddressInfo > ;
5051
51- async fn get_transactions ( & self , address : & str ) -> Result < Transactions > ;
52+ async fn get_transactions ( & self , address : & BitcoinAddress ) -> Result < Transactions > ;
5253
5354 async fn get_last_block_height ( & self ) -> Result < u64 > ;
5455
5556 /// Checks payment by iterating over the transactions on the address in chronological order, until
5657 /// the target amount is filled, returning the respective payment status
5758 async fn check_payment_for_address (
5859 & self ,
59- address : & str ,
60+ address : & BitcoinAddress ,
6061 target_amount : u64 ,
6162 ) -> Result < PaymentState > ;
6263
6364 fn get_address_to_pay (
6465 & self ,
6566 bill_public_key : & PublicKey ,
6667 holder_public_key : & PublicKey ,
67- ) -> Result < String > ;
68+ ) -> Result < BitcoinAddress > ;
6869
69- fn generate_link_to_pay ( & self , address : & str , sum : & Sum , message : & str ) -> String ;
70+ fn generate_link_to_pay ( & self , address : & BitcoinAddress , sum : & Sum , message : & str ) -> String ;
7071
7172 fn get_combined_private_descriptor (
7273 & self ,
7374 pkey : & bitcoin:: PrivateKey ,
7475 pkey_to_combine : & bitcoin:: PrivateKey ,
7576 ) -> Result < String > ;
7677
77- fn get_mempool_link_for_address ( & self , address : & str ) -> String ;
78+ fn get_mempool_link_for_address ( & self , address : & BitcoinAddress ) -> String ;
7879}
7980
8081#[ derive( Clone ) ]
@@ -134,10 +135,11 @@ impl Default for BitcoinClient {
134135#[ cfg_attr( target_arch = "wasm32" , async_trait( ?Send ) ) ]
135136#[ cfg_attr( not( target_arch = "wasm32" ) , async_trait) ]
136137impl BitcoinClientApi for BitcoinClient {
137- async fn get_address_info ( & self , address : & str ) -> Result < AddressInfo > {
138+ async fn get_address_info ( & self , address : & BitcoinAddress ) -> Result < AddressInfo > {
139+ let addr_str = address. assume_checked_ref ( ) . to_string ( ) ;
138140 let address: AddressInfo = self
139141 . cl
140- . get ( self . request_url ( & format ! ( "/address/{address }" ) ) )
142+ . get ( self . request_url ( & format ! ( "/address/{addr_str }" ) ) )
141143 . send ( )
142144 . await
143145 . map_err ( Error :: from) ?
@@ -148,10 +150,11 @@ impl BitcoinClientApi for BitcoinClient {
148150 Ok ( address)
149151 }
150152
151- async fn get_transactions ( & self , address : & str ) -> Result < Transactions > {
153+ async fn get_transactions ( & self , address : & BitcoinAddress ) -> Result < Transactions > {
154+ let addr_str = address. assume_checked_ref ( ) . to_string ( ) ;
152155 let transactions: Transactions = self
153156 . cl
154- . get ( self . request_url ( & format ! ( "/address/{address }/txs" ) ) )
157+ . get ( self . request_url ( & format ! ( "/address/{addr_str }/txs" ) ) )
155158 . send ( )
156159 . await
157160 . map_err ( Error :: from) ?
@@ -176,10 +179,13 @@ impl BitcoinClientApi for BitcoinClient {
176179
177180 async fn check_payment_for_address (
178181 & self ,
179- address : & str ,
182+ address : & BitcoinAddress ,
180183 target_amount : u64 ,
181184 ) -> Result < PaymentState > {
182- debug ! ( "checking if btc address {address} is paid {target_amount}" ) ;
185+ debug ! (
186+ "checking if btc address {} is paid {target_amount}" ,
187+ address. assume_checked_ref( )
188+ ) ;
183189 // in parallel, get current chain height, transactions and address info for the given address
184190 let ( chain_block_height, txs) =
185191 try_join ! ( self . get_last_block_height( ) , self . get_transactions( address) , ) ?;
@@ -191,7 +197,7 @@ impl BitcoinClientApi for BitcoinClient {
191197 & self ,
192198 bill_public_key : & PublicKey ,
193199 holder_public_key : & PublicKey ,
194- ) -> Result < String > {
200+ ) -> Result < BitcoinAddress > {
195201 let public_key_bill = bitcoin:: CompressedPublicKey ( * bill_public_key) ;
196202 let public_key_bill_holder = bitcoin:: CompressedPublicKey ( * holder_public_key) ;
197203
@@ -201,12 +207,19 @@ impl BitcoinClientApi for BitcoinClient {
201207 . map_err ( Error :: from) ?;
202208 let pub_key_bill = bitcoin:: CompressedPublicKey ( public_key_bill) ;
203209
204- Ok ( bitcoin:: Address :: p2wpkh ( & pub_key_bill, get_config ( ) . bitcoin_network ( ) ) . to_string ( ) )
210+ Ok (
211+ bitcoin:: Address :: p2wpkh ( & pub_key_bill, get_config ( ) . bitcoin_network ( ) )
212+ . as_unchecked ( )
213+ . to_owned ( ) ,
214+ )
205215 }
206216
207- fn generate_link_to_pay ( & self , address : & str , sum : & Sum , message : & str ) -> String {
217+ fn generate_link_to_pay ( & self , address : & BitcoinAddress , sum : & Sum , message : & str ) -> String {
208218 let btc_sum = sum. as_btc_string ( ) ;
209- let link = format ! ( "bitcoin:{address}?amount={btc_sum}&message={message}" ) ;
219+ let link = format ! (
220+ "bitcoin:{}?amount={btc_sum}&message={message}" ,
221+ address. assume_checked_ref( )
222+ ) ;
210223 link
211224 }
212225
@@ -234,21 +247,22 @@ impl BitcoinClientApi for BitcoinClient {
234247 Ok ( desc. to_string_with_secret ( & kmap) )
235248 }
236249
237- fn get_mempool_link_for_address ( & self , address : & str ) -> String {
238- self . link_url ( & format ! ( "/address/{address}" ) )
250+ fn get_mempool_link_for_address ( & self , address : & BitcoinAddress ) -> String {
251+ self . link_url ( & format ! ( "/address/{}" , address . assume_checked_ref ( ) ) )
239252 }
240253}
241254
242255fn payment_state_from_transactions (
243256 chain_block_height : u64 ,
244257 txs : Transactions ,
245- address : & str ,
258+ address : & BitcoinAddress ,
246259 target_amount : u64 ,
247260) -> Result < PaymentState > {
248261 // no transactions - no payment
249262 if txs. is_empty ( ) {
250263 return Ok ( PaymentState :: NotFound ) ;
251264 }
265+ let addr_string = address. assume_checked_ref ( ) . to_string ( ) ;
252266
253267 let mut total = 0 ;
254268 let mut tx_filled = None ;
@@ -258,7 +272,7 @@ fn payment_state_from_transactions(
258272 for vout in tx. vout . iter ( ) {
259273 // sum up outputs towards the address to check
260274 if let Some ( ref addr) = vout. scriptpubkey_address
261- && addr == address
275+ && addr == & addr_string
262276 {
263277 total += vout. value ;
264278 }
@@ -274,7 +288,7 @@ fn payment_state_from_transactions(
274288 Some ( tx) => {
275289 // in mem pool
276290 if !tx. status . confirmed {
277- debug ! ( "payment for {address } is in mem pool {}" , tx. txid) ;
291+ debug ! ( "payment for {addr_string } is in mem pool {}" , tx. txid) ;
278292 Ok ( PaymentState :: InMempool ( InMempoolData { tx_id : tx. txid } ) )
279293 } else {
280294 match (
@@ -285,7 +299,7 @@ fn payment_state_from_transactions(
285299 ( Some ( block_height) , Some ( block_time) , Some ( block_hash) ) => {
286300 let confirmations = chain_block_height - block_height + 1 ;
287301 let paid_data = PaidData {
288- block_time,
302+ block_time : Timestamp :: new ( block_time ) . map_err ( |_| Error :: InvalidData ( format ! ( "Invalid data when checking payment for {addr_string} - invalid block time" ) ) ) ? ,
289303 block_hash,
290304 confirmations,
291305 tx_id : tx. txid ,
@@ -295,30 +309,30 @@ fn payment_state_from_transactions(
295309 {
296310 // paid and confirmed
297311 debug ! (
298- "payment for {address } is paid and confirmed with {confirmations} confirmations"
312+ "payment for {addr_string } is paid and confirmed with {confirmations} confirmations"
299313 ) ;
300314 Ok ( PaymentState :: PaidConfirmed ( paid_data) )
301315 } else {
302316 // paid but not enough confirmations yet
303317 debug ! (
304- "payment for {address } is paid and unconfirmed with {confirmations} confirmations"
318+ "payment for {addr_string } is paid and unconfirmed with {confirmations} confirmations"
305319 ) ;
306320 Ok ( PaymentState :: PaidUnconfirmed ( paid_data) )
307321 }
308322 }
309323 _ => {
310324 log:: error!(
311- "Invalid data when checking payment for {address } - confirmed tx, but no metadata"
325+ "Invalid data when checking payment for {addr_string } - confirmed tx, but no metadata"
312326 ) ;
313- Err ( Error :: InvalidData ( format ! ( "Invalid data when checking payment for {address } - confirmed tx, but no metadata" ) ) . into ( ) )
327+ Err ( Error :: InvalidData ( format ! ( "Invalid data when checking payment for {addr_string } - confirmed tx, but no metadata" ) ) . into ( ) )
314328 }
315329 }
316330 }
317331 }
318332 None => {
319333 // not enough funds to cover amount
320334 debug ! (
321- "Not enough funds to cover {target_amount} yet when checking payment for {address }: {total}"
335+ "Not enough funds to cover {target_amount} yet when checking payment for {addr_string }: {total}"
322336 ) ;
323337 Ok ( PaymentState :: NotFound )
324338 }
@@ -369,14 +383,15 @@ pub struct Status {
369383#[ cfg( test) ]
370384pub mod tests {
371385 use crate :: tests:: tests:: init_test_cfg;
386+ use std:: str:: FromStr ;
372387
373388 use super :: * ;
374389
375390 #[ test]
376391 fn test_payment_state_from_transactions ( ) {
377392 init_test_cfg ( ) ;
378393 let test_height = 4578915 ;
379- let test_addr = "n4n9CNeCkgtEs8wukKEvWC78eEqK4A3E6d" ;
394+ let test_addr = BitcoinAddress :: from_str ( "n4n9CNeCkgtEs8wukKEvWC78eEqK4A3E6d" ) . unwrap ( ) ;
380395 let test_amount = 500 ;
381396 let mut test_tx = Tx {
382397 txid : "" . into ( ) ,
@@ -390,18 +405,18 @@ pub mod tests {
390405 } ,
391406 vout : vec ! [ Vout {
392407 value: 500 ,
393- scriptpubkey_address: Some ( test_addr. to_owned ( ) ) ,
408+ scriptpubkey_address: Some ( test_addr. assume_checked_ref ( ) . to_string ( ) ) ,
394409 } ] ,
395410 } ;
396411
397412 let res_empty =
398- payment_state_from_transactions ( test_height, vec ! [ ] , test_addr, test_amount) ;
413+ payment_state_from_transactions ( test_height, vec ! [ ] , & test_addr, test_amount) ;
399414 assert ! ( matches!( res_empty, Ok ( PaymentState :: NotFound ) ) ) ;
400415
401416 let res_paid_confirmed = payment_state_from_transactions (
402417 test_height,
403418 vec ! [ test_tx. clone( ) ] ,
404- test_addr,
419+ & test_addr,
405420 test_amount,
406421 ) ;
407422 assert ! ( matches!(
@@ -413,7 +428,7 @@ pub mod tests {
413428 let res_paid_unconfirmed = payment_state_from_transactions (
414429 test_height,
415430 vec ! [ test_tx. clone( ) ] ,
416- test_addr,
431+ & test_addr,
417432 test_amount,
418433 ) ;
419434 assert ! ( matches!(
@@ -427,7 +442,7 @@ pub mod tests {
427442 let res_paid_confirmed_no_data = payment_state_from_transactions (
428443 test_height,
429444 vec ! [ test_tx. clone( ) ] ,
430- test_addr,
445+ & test_addr,
431446 test_amount,
432447 ) ;
433448 assert ! ( matches!(
@@ -441,7 +456,7 @@ pub mod tests {
441456 let res_in_mem_pool = payment_state_from_transactions (
442457 test_height,
443458 vec ! [ test_tx. clone( ) ] ,
444- test_addr,
459+ & test_addr,
445460 test_amount,
446461 ) ;
447462 assert ! ( matches!( res_in_mem_pool, Ok ( PaymentState :: InMempool ( ..) ) ) ) ;
@@ -450,7 +465,7 @@ pub mod tests {
450465 let res_not_filled = payment_state_from_transactions (
451466 test_height,
452467 vec ! [ test_tx. clone( ) ] ,
453- test_addr,
468+ & test_addr,
454469 test_amount,
455470 ) ;
456471 assert ! ( matches!( res_not_filled, Ok ( PaymentState :: NotFound ) ) ) ;
0 commit comments