1use bitcoin::{
6 address::NetworkUnchecked,
7 block,
8 consensus::{deserialize, serialize, Decodable, Encodable},
9 hashes::Hash,
10 hex::DisplayHex,
11 secp256k1::{schnorr, Message, PublicKey},
12 Address, OutPoint, ScriptBuf, TxOut, Txid, XOnlyPublicKey,
13};
14use clementine_primitives::EVMAddress;
15use eyre::eyre;
16use prost::Message as _;
17use risc0_zkvm::Receipt;
18use secp256k1::musig;
19use serde::{Deserialize, Serialize};
20use sqlx::{
21 error::BoxDynError,
22 postgres::{PgArgumentBuffer, PgValueRef},
23 Decode, Encode, Postgres,
24};
25use std::str::FromStr;
26
27macro_rules! impl_text_wrapper_base {
32 ($wrapper:ident, $inner:ty, $encode:expr, $decode:expr) => {
33 impl sqlx::Type<sqlx::Postgres> for $wrapper {
34 fn type_info() -> sqlx::postgres::PgTypeInfo {
35 sqlx::postgres::PgTypeInfo::with_name("TEXT")
36 }
37 }
38
39 impl Encode<'_, Postgres> for $wrapper {
40 fn encode_by_ref(
41 &self,
42 buf: &mut PgArgumentBuffer,
43 ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
44 let s = $encode(&self.0);
45 <&str as Encode<Postgres>>::encode_by_ref(&s.as_str(), buf)
46 }
47 }
48
49 impl<'r> Decode<'r, Postgres> for $wrapper {
50 fn decode(value: PgValueRef<'r>) -> Result<Self, sqlx::error::BoxDynError> {
51 let s = <&str as Decode<Postgres>>::decode(value)?;
52 Ok(Self($decode(s)?))
53 }
54 }
55 };
56}
57
58macro_rules! impl_text_wrapper_custom {
71 ($wrapper:ident, $inner:ty, $encode:expr, $decode:expr) => {
73 impl_text_wrapper_custom!($wrapper, $inner, $encode, $decode, true);
74 };
75
76 ($wrapper:ident, $inner:ty, $encode:expr, $decode:expr, true) => {
78 #[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, PartialEq)]
79 pub struct $wrapper(pub $inner);
80
81 impl_text_wrapper_base!($wrapper, $inner, $encode, $decode);
82 };
83
84 ($wrapper:ident, $inner:ty, $encode:expr, $decode:expr, false) => {
86 #[derive(sqlx::FromRow, Debug, Clone, PartialEq)]
87 pub struct $wrapper(pub $inner);
88
89 impl_text_wrapper_base!($wrapper, $inner, $encode, $decode);
90 };
91}
92
93macro_rules! impl_bytea_wrapper_custom {
106 ($wrapper:ident, $inner:ty, $encode:expr, $decode:expr) => {
107 #[derive(sqlx::FromRow, Debug, Clone, PartialEq)]
108 pub struct $wrapper(pub $inner);
109
110 impl sqlx::Type<sqlx::Postgres> for $wrapper {
111 fn type_info() -> sqlx::postgres::PgTypeInfo {
112 sqlx::postgres::PgTypeInfo::with_name("BYTEA")
113 }
114 }
115
116 impl Encode<'_, Postgres> for $wrapper {
117 fn encode_by_ref(
118 &self,
119 buf: &mut PgArgumentBuffer,
120 ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
121 let bytes = $encode(&self.0);
122 <&[u8] as Encode<Postgres>>::encode(bytes.as_ref(), buf)
123 }
124 }
125
126 impl<'r> Decode<'r, Postgres> for $wrapper {
127 fn decode(value: PgValueRef<'r>) -> Result<Self, sqlx::error::BoxDynError> {
128 let bytes = <Vec<u8> as Decode<Postgres>>::decode(value)?;
129 Ok(Self($decode(&bytes)?))
130 }
131 }
132 };
133}
134
135macro_rules! impl_bytea_wrapper_custom_with_error {
137 ($wrapper:ident, $inner:ty, $encode:expr, $decode:expr) => {
138 #[derive(sqlx::FromRow, Debug, Clone)]
139 pub struct $wrapper(pub $inner);
140
141 impl sqlx::Type<sqlx::Postgres> for $wrapper {
142 fn type_info() -> sqlx::postgres::PgTypeInfo {
143 sqlx::postgres::PgTypeInfo::with_name("BYTEA")
144 }
145 }
146
147 impl Encode<'_, Postgres> for $wrapper {
148 fn encode_by_ref(
149 &self,
150 buf: &mut PgArgumentBuffer,
151 ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
152 let bytes = $encode(&self.0)?;
153 <&[u8] as Encode<Postgres>>::encode(bytes.as_ref(), buf)
154 }
155 }
156
157 impl<'r> Decode<'r, Postgres> for $wrapper {
158 fn decode(value: PgValueRef<'r>) -> Result<Self, sqlx::error::BoxDynError> {
159 let bytes = <Vec<u8> as Decode<Postgres>>::decode(value)?;
160 Ok(Self($decode(&bytes)?))
161 }
162 }
163 };
164}
165
166macro_rules! impl_bytea_wrapper_default {
180 ($wrapper:ident, $inner:ty) => {
181 impl_bytea_wrapper_custom!(
182 $wrapper,
183 $inner,
184 |x: &$inner| x.serialize(),
185 |x: &[u8]| -> Result<$inner, BoxDynError> {
186 <$inner>::from_slice(x).map_err(|e| Box::new(e) as sqlx::error::BoxDynError)
187 }
188 );
189 };
190}
191
192macro_rules! impl_text_wrapper_default {
206 ($wrapper:ident, $inner:ty) => {
207 impl_text_wrapper_custom!(
208 $wrapper,
209 $inner,
210 <$inner as ToString>::to_string,
211 <$inner as FromStr>::from_str
212 );
213 };
214}
215
216impl_text_wrapper_default!(OutPointDB, OutPoint);
217impl_text_wrapper_default!(BlockHashDB, block::BlockHash);
218impl_text_wrapper_default!(PublicKeyDB, PublicKey);
219impl_text_wrapper_default!(XOnlyPublicKeyDB, XOnlyPublicKey);
220
221impl_bytea_wrapper_default!(SignatureDB, schnorr::Signature);
222
223impl_bytea_wrapper_custom!(
224 MusigPubNonceDB,
225 musig::PublicNonce,
226 |pub_nonce: &musig::PublicNonce| pub_nonce.serialize(),
227 |x: &[u8]| -> Result<musig::PublicNonce, BoxDynError> {
228 let arr: &[u8; 66] = x
229 .try_into()
230 .map_err(|_| eyre!("Expected 66 bytes for PublicNonce"))?;
231 Ok(musig::PublicNonce::from_byte_array(arr)?)
232 }
233);
234
235impl_bytea_wrapper_custom!(
236 MusigAggNonceDB,
237 musig::AggregatedNonce,
238 |pub_nonce: &musig::AggregatedNonce| pub_nonce.serialize(),
239 |x: &[u8]| -> Result<musig::AggregatedNonce, BoxDynError> {
240 let arr: &[u8; 66] = x
241 .try_into()
242 .map_err(|_| eyre!("Expected 66 bytes for AggregatedNonce"))?;
243 Ok(musig::AggregatedNonce::from_byte_array(arr)?)
244 }
245);
246
247impl_bytea_wrapper_custom_with_error!(
248 ReceiptDB,
249 Receipt,
250 |lcp: &Receipt| -> Result<Vec<u8>, BoxDynError> { borsh::to_vec(lcp).map_err(Into::into) },
251 |x: &[u8]| -> Result<Receipt, BoxDynError> { borsh::from_slice(x).map_err(Into::into) }
252);
253
254impl_text_wrapper_custom!(
255 AddressDB,
256 Address<NetworkUnchecked>,
257 |addr: &Address<NetworkUnchecked>| addr.clone().assume_checked().to_string(),
258 |s: &str| Address::from_str(s)
259);
260
261impl_text_wrapper_custom!(
262 EVMAddressDB,
263 EVMAddress,
264 |addr: &EVMAddress| hex::encode(addr.0),
265 |s: &str| -> Result<EVMAddress, BoxDynError> {
266 let bytes = hex::decode(s).map_err(Box::new)?;
267
268 Ok(EVMAddress(bytes.try_into().map_err(|arr: Vec<u8>| {
269 eyre!("Failed to deserialize EVMAddress from {:?}", arr)
270 })?))
271 }
272);
273
274impl_bytea_wrapper_custom!(
275 TxidDB,
276 Txid,
277 |txid: &Txid| *txid, |x: &[u8]| -> Result<Txid, BoxDynError> { Ok(Txid::from_slice(x)?) }
279);
280
281impl sqlx::postgres::PgHasArrayType for TxidDB {
283 fn array_type_info() -> sqlx::postgres::PgTypeInfo {
284 sqlx::postgres::PgTypeInfo::with_name("_bytea") }
286}
287
288impl_bytea_wrapper_custom!(
289 MessageDB,
290 Message,
291 |msg: &Message| *msg, |x: &[u8]| -> Result<Message, BoxDynError> { Ok(Message::from_digest(x.try_into()?)) }
293);
294
295use crate::rpc::clementine::DepositSignatures;
296impl_bytea_wrapper_custom!(
297 SignaturesDB,
298 DepositSignatures,
299 |signatures: &DepositSignatures| { signatures.encode_to_vec() },
300 |x: &[u8]| -> Result<DepositSignatures, BoxDynError> {
301 DepositSignatures::decode(x).map_err(Into::into)
302 }
303);
304
305use crate::rpc::clementine::DepositParams;
306impl_bytea_wrapper_custom!(
307 DepositParamsDB,
308 DepositParams,
309 |deposit_params: &DepositParams| { deposit_params.encode_to_vec() },
310 |x: &[u8]| -> Result<DepositParams, BoxDynError> {
311 DepositParams::decode(x).map_err(Into::into)
312 }
313);
314
315impl_bytea_wrapper_custom!(
316 ScriptBufDB,
317 ScriptBuf,
318 |script: &ScriptBuf| serialize(script),
319 |x: &[u8]| -> Result<ScriptBuf, BoxDynError> { deserialize(x).map_err(Into::into) }
320);
321
322impl_text_wrapper_custom!(
323 BlockHeaderDB,
324 block::Header,
325 |header: &block::Header| {
326 let mut bytes = Vec::new();
327 header
328 .consensus_encode(&mut bytes)
329 .expect("exceeded max Vec size or ran out of memory");
330 bytes.to_hex_string(bitcoin::hex::Case::Lower)
331 },
332 |s: &str| -> Result<block::Header, BoxDynError> {
333 let bytes = hex::decode(s)?;
334 block::Header::consensus_decode(&mut bytes.as_slice()).map_err(Into::into)
335 }
336);
337
338#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, sqlx::FromRow)]
339pub struct UtxoDB {
340 pub outpoint_db: OutPointDB,
341 pub txout_db: TxOutDB,
342}
343
344impl_text_wrapper_custom!(
345 TxOutDB,
346 TxOut,
347 |txout: &TxOut| bitcoin::consensus::encode::serialize_hex(&txout),
348 |s: &str| -> Result<TxOut, BoxDynError> {
349 bitcoin::consensus::encode::deserialize_hex(s)
350 .map_err(|e| Box::new(e) as sqlx::error::BoxDynError)
351 }
352);
353
354#[cfg(test)]
355mod tests {
356 use super::*;
357 use crate::{
358 bitvm_client::{self, SECP},
359 database::Database,
360 musig2,
361 rpc::clementine::TaggedSignature,
362 test::common::*,
363 };
364 use bitcoin::{
365 block::{self, Version},
366 hashes::Hash,
367 key::Keypair,
368 secp256k1::{schnorr::Signature, SecretKey},
369 Amount, BlockHash, CompactTarget, OutPoint, ScriptBuf, TxMerkleNode, TxOut, Txid,
370 };
371 use clementine_primitives::EVMAddress;
372 use secp256k1::{musig::AggregatedNonce, SECP256K1};
373 use sqlx::{Executor, Type};
374
375 macro_rules! test_encode_decode_invariant {
376 ($db_type:ty, $inner:ty, $db_wrapper:expr, $table_name:expr, $column_type:expr) => {
377 let db_wrapper = $db_wrapper;
378
379 let config = create_test_config_with_thread_name().await;
380 let database = Database::new(&config).await.unwrap();
381
382 database
384 .connection
385 .execute(sqlx::query(&format!(
386 "CREATE TABLE IF NOT EXISTS {} ({} {} PRIMARY KEY)",
387 $table_name, $table_name, $column_type
388 )))
389 .await
390 .unwrap();
391
392 database
394 .connection
395 .execute(
396 sqlx::query(&format!(
397 "INSERT INTO {} ({}) VALUES ($1)",
398 $table_name, $table_name
399 ))
400 .bind(db_wrapper.clone()),
401 )
402 .await
403 .unwrap();
404
405 let retrieved: $db_type = sqlx::query_scalar(&format!(
407 "SELECT {} FROM {} WHERE {} = $1",
408 $table_name, $table_name, $table_name
409 ))
410 .bind(db_wrapper.clone())
411 .fetch_one(&database.connection)
412 .await
413 .unwrap();
414
415 assert_eq!(retrieved, db_wrapper);
417
418 database
420 .connection
421 .execute(sqlx::query(&format!("DROP TABLE {}", $table_name)))
422 .await
423 .unwrap();
424 };
425 }
426 #[tokio::test]
427 async fn outpoint_encode_decode_invariant() {
428 assert_eq!(
429 OutPointDB::type_info(),
430 sqlx::postgres::PgTypeInfo::with_name("TEXT")
431 );
432
433 test_encode_decode_invariant!(
434 OutPointDB,
435 OutPoint,
436 OutPointDB(OutPoint {
437 txid: Txid::all_zeros(),
438 vout: 0x45
439 }),
440 "outpoint",
441 "TEXT"
442 );
443 }
444
445 #[tokio::test]
446 async fn txoutdb_encode_decode_invariant() {
447 assert_eq!(
448 TxOutDB::type_info(),
449 sqlx::postgres::PgTypeInfo::with_name("TEXT")
450 );
451
452 test_encode_decode_invariant!(
453 TxOutDB,
454 TxOut,
455 TxOutDB(TxOut {
456 value: Amount::from_sat(0x45),
457 script_pubkey: ScriptBuf::new(),
458 }),
459 "txout",
460 "TEXT"
461 );
462 }
463
464 #[tokio::test]
465 async fn addressdb_encode_decode_invariant() {
466 assert_eq!(
467 AddressDB::type_info(),
468 sqlx::postgres::PgTypeInfo::with_name("TEXT")
469 );
470
471 let address = bitcoin::Address::p2tr(
472 &SECP,
473 *bitvm_client::UNSPENDABLE_XONLY_PUBKEY,
474 None,
475 bitcoin::Network::Regtest,
476 );
477 let address = AddressDB(address.as_unchecked().clone());
478
479 test_encode_decode_invariant!(
480 AddressDB,
481 Address<NetworkUnchecked>,
482 address,
483 "address",
484 "TEXT"
485 );
486 }
487
488 #[tokio::test]
489 async fn evmaddressdb_encode_decode_invariant() {
490 assert_eq!(
491 EVMAddressDB::type_info(),
492 sqlx::postgres::PgTypeInfo::with_name("TEXT")
493 );
494
495 let evmaddress = EVMAddressDB(EVMAddress([0x45u8; 20]));
496 test_encode_decode_invariant!(EVMAddressDB, EVMAddress, evmaddress, "evmaddress", "TEXT");
497 }
498
499 #[tokio::test]
500 async fn txiddb_encode_decode_invariant() {
501 assert_eq!(
502 TxidDB::type_info(),
503 sqlx::postgres::PgTypeInfo::with_name("BYTEA")
504 );
505
506 let txid = TxidDB(Txid::all_zeros());
507 test_encode_decode_invariant!(TxidDB, Txid, txid, "txid", "BYTEA");
508 }
509
510 #[tokio::test]
511 async fn signaturedb_encode_decode_invariant() {
512 assert_eq!(
513 SignatureDB::type_info(),
514 sqlx::postgres::PgTypeInfo::with_name("BYTEA")
515 );
516
517 let signature = SignatureDB(Signature::from_slice(&[0u8; 64]).unwrap());
518 test_encode_decode_invariant!(SignatureDB, Signature, signature, "signature", "BYTEA");
519 }
520
521 #[tokio::test]
522 async fn signaturesdb_encode_decode_invariant() {
523 assert_eq!(
524 SignaturesDB::type_info(),
525 sqlx::postgres::PgTypeInfo::with_name("BYTEA")
526 );
527
528 use crate::rpc::clementine::{
529 DepositSignatures, NormalSignatureKind, NumberedSignatureKind,
530 };
531 let signatures = DepositSignatures {
532 signatures: vec![
533 TaggedSignature {
534 signature: vec![0x1Fu8; 64],
535 signature_id: Some(NormalSignatureKind::NotStored.into()),
536 },
537 TaggedSignature {
538 signature: vec![0x45u8; 64],
539 signature_id: Some((NumberedSignatureKind::NumberedNotStored, 1).into()),
540 },
541 ],
542 };
543 test_encode_decode_invariant!(
544 SignaturesDB,
545 DepositSignatures,
546 SignaturesDB(signatures),
547 "signatures",
548 "BYTEA"
549 );
550 }
551
552 #[tokio::test]
553 async fn utxodb_json_encode_decode_invariant() {
554 use sqlx::types::Json;
555
556 assert_eq!(
557 Json::<UtxoDB>::type_info(),
558 sqlx::postgres::PgTypeInfo::with_name("JSONB")
559 );
560
561 let utxodb = UtxoDB {
562 outpoint_db: OutPointDB(OutPoint {
563 txid: Txid::all_zeros(),
564 vout: 0x45,
565 }),
566 txout_db: TxOutDB(TxOut {
567 value: Amount::from_sat(0x45),
568 script_pubkey: ScriptBuf::new(),
569 }),
570 };
571
572 test_encode_decode_invariant!(Json<UtxoDB>, Utxodb, Json(utxodb), "utxodb", "JSONB");
573 }
574
575 #[tokio::test]
576 async fn blockhashdb_encode_decode_invariant() {
577 assert_eq!(
578 OutPointDB::type_info(),
579 sqlx::postgres::PgTypeInfo::with_name("TEXT")
580 );
581
582 let blockhash = BlockHashDB(BlockHash::all_zeros());
583 test_encode_decode_invariant!(BlockHashDB, BlockHash, blockhash, "blockhash", "TEXT");
584 }
585
586 #[tokio::test]
587 async fn blockheaderdb_encode_decode_invariant() {
588 assert_eq!(
589 OutPointDB::type_info(),
590 sqlx::postgres::PgTypeInfo::with_name("TEXT")
591 );
592
593 let blockheader = BlockHeaderDB(block::Header {
594 version: Version::TWO,
595 prev_blockhash: BlockHash::all_zeros(),
596 merkle_root: TxMerkleNode::all_zeros(),
597 time: 0,
598 bits: CompactTarget::default(),
599 nonce: 0,
600 });
601 test_encode_decode_invariant!(
602 BlockHeaderDB,
603 block::Header,
604 blockheader,
605 "blockheader",
606 "TEXT"
607 );
608 }
609
610 #[tokio::test]
611 async fn musigpubnoncedb_encode_decode_invariant() {
612 assert_eq!(
613 MusigPubNonceDB::type_info(),
614 sqlx::postgres::PgTypeInfo::with_name("BYTEA")
615 );
616
617 let kp = Keypair::from_secret_key(&SECP, &SecretKey::from_slice(&[1u8; 32]).unwrap());
618 let (_sec_nonce, pub_nonce) = musig2::nonce_pair(&kp).unwrap();
619 let public_nonce = MusigPubNonceDB(pub_nonce);
620 test_encode_decode_invariant!(
621 MusigPubNonceDB,
622 PublicNonce,
623 public_nonce,
624 "public_nonce",
625 "BYTEA"
626 );
627 }
628
629 #[tokio::test]
630 async fn musigaggnoncedb_encode_decode_invariant() {
631 assert_eq!(
632 MusigAggNonceDB::type_info(),
633 sqlx::postgres::PgTypeInfo::with_name("BYTEA")
634 );
635
636 let kp = Keypair::from_secret_key(&SECP, &SecretKey::from_slice(&[1u8; 32]).unwrap());
637 let (_sec_nonce, pub_nonce) = musig2::nonce_pair(&kp).unwrap();
638 let aggregated_nonce = MusigAggNonceDB(AggregatedNonce::new(SECP256K1, &[&pub_nonce]));
639 test_encode_decode_invariant!(
640 MusigAggNonceDB,
641 AggregatedNonce,
642 aggregated_nonce,
643 "aggregated_nonce",
644 "BYTEA"
645 );
646 }
647}