1use crate::EVMAddress;
6use bitcoin::{
7 address::NetworkUnchecked,
8 block,
9 consensus::{deserialize, serialize, Decodable, Encodable},
10 hashes::Hash,
11 hex::DisplayHex,
12 secp256k1::{schnorr, Message, PublicKey},
13 Address, OutPoint, ScriptBuf, TxOut, Txid, XOnlyPublicKey,
14};
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_bytea_wrapper_custom!(
282 MessageDB,
283 Message,
284 |msg: &Message| *msg, |x: &[u8]| -> Result<Message, BoxDynError> { Ok(Message::from_digest(x.try_into()?)) }
286);
287
288use crate::rpc::clementine::DepositSignatures;
289impl_bytea_wrapper_custom!(
290 SignaturesDB,
291 DepositSignatures,
292 |signatures: &DepositSignatures| { signatures.encode_to_vec() },
293 |x: &[u8]| -> Result<DepositSignatures, BoxDynError> {
294 DepositSignatures::decode(x).map_err(Into::into)
295 }
296);
297
298use crate::rpc::clementine::DepositParams;
299impl_bytea_wrapper_custom!(
300 DepositParamsDB,
301 DepositParams,
302 |deposit_params: &DepositParams| { deposit_params.encode_to_vec() },
303 |x: &[u8]| -> Result<DepositParams, BoxDynError> {
304 DepositParams::decode(x).map_err(Into::into)
305 }
306);
307
308impl_bytea_wrapper_custom!(
309 ScriptBufDB,
310 ScriptBuf,
311 |script: &ScriptBuf| serialize(script),
312 |x: &[u8]| -> Result<ScriptBuf, BoxDynError> { deserialize(x).map_err(Into::into) }
313);
314
315impl_text_wrapper_custom!(
316 BlockHeaderDB,
317 block::Header,
318 |header: &block::Header| {
319 let mut bytes = Vec::new();
320 header
321 .consensus_encode(&mut bytes)
322 .expect("exceeded max Vec size or ran out of memory");
323 bytes.to_hex_string(bitcoin::hex::Case::Lower)
324 },
325 |s: &str| -> Result<block::Header, BoxDynError> {
326 let bytes = hex::decode(s)?;
327 block::Header::consensus_decode(&mut bytes.as_slice()).map_err(Into::into)
328 }
329);
330
331#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, sqlx::FromRow)]
332pub struct UtxoDB {
333 pub outpoint_db: OutPointDB,
334 pub txout_db: TxOutDB,
335}
336
337impl_text_wrapper_custom!(
338 TxOutDB,
339 TxOut,
340 |txout: &TxOut| bitcoin::consensus::encode::serialize_hex(&txout),
341 |s: &str| -> Result<TxOut, BoxDynError> {
342 bitcoin::consensus::encode::deserialize_hex(s)
343 .map_err(|e| Box::new(e) as sqlx::error::BoxDynError)
344 }
345);
346
347#[cfg(test)]
348mod tests {
349 use super::*;
350 use crate::{
351 bitvm_client::{self, SECP},
352 database::Database,
353 musig2,
354 rpc::clementine::TaggedSignature,
355 test::common::*,
356 EVMAddress,
357 };
358 use bitcoin::{
359 block::{self, Version},
360 hashes::Hash,
361 key::Keypair,
362 secp256k1::{schnorr::Signature, SecretKey},
363 Amount, BlockHash, CompactTarget, OutPoint, ScriptBuf, TxMerkleNode, TxOut, Txid,
364 };
365 use secp256k1::{musig::AggregatedNonce, SECP256K1};
366 use sqlx::{Executor, Type};
367
368 macro_rules! test_encode_decode_invariant {
369 ($db_type:ty, $inner:ty, $db_wrapper:expr, $table_name:expr, $column_type:expr) => {
370 let db_wrapper = $db_wrapper;
371
372 let config = create_test_config_with_thread_name().await;
373 let database = Database::new(&config).await.unwrap();
374
375 database
377 .connection
378 .execute(sqlx::query(&format!(
379 "CREATE TABLE IF NOT EXISTS {} ({} {} PRIMARY KEY)",
380 $table_name, $table_name, $column_type
381 )))
382 .await
383 .unwrap();
384
385 database
387 .connection
388 .execute(
389 sqlx::query(&format!(
390 "INSERT INTO {} ({}) VALUES ($1)",
391 $table_name, $table_name
392 ))
393 .bind(db_wrapper.clone()),
394 )
395 .await
396 .unwrap();
397
398 let retrieved: $db_type = sqlx::query_scalar(&format!(
400 "SELECT {} FROM {} WHERE {} = $1",
401 $table_name, $table_name, $table_name
402 ))
403 .bind(db_wrapper.clone())
404 .fetch_one(&database.connection)
405 .await
406 .unwrap();
407
408 assert_eq!(retrieved, db_wrapper);
410
411 database
413 .connection
414 .execute(sqlx::query(&format!("DROP TABLE {}", $table_name)))
415 .await
416 .unwrap();
417 };
418 }
419 #[tokio::test]
420 async fn outpoint_encode_decode_invariant() {
421 assert_eq!(
422 OutPointDB::type_info(),
423 sqlx::postgres::PgTypeInfo::with_name("TEXT")
424 );
425
426 test_encode_decode_invariant!(
427 OutPointDB,
428 OutPoint,
429 OutPointDB(OutPoint {
430 txid: Txid::all_zeros(),
431 vout: 0x45
432 }),
433 "outpoint",
434 "TEXT"
435 );
436 }
437
438 #[tokio::test]
439 async fn txoutdb_encode_decode_invariant() {
440 assert_eq!(
441 TxOutDB::type_info(),
442 sqlx::postgres::PgTypeInfo::with_name("TEXT")
443 );
444
445 test_encode_decode_invariant!(
446 TxOutDB,
447 TxOut,
448 TxOutDB(TxOut {
449 value: Amount::from_sat(0x45),
450 script_pubkey: ScriptBuf::new(),
451 }),
452 "txout",
453 "TEXT"
454 );
455 }
456
457 #[tokio::test]
458 async fn addressdb_encode_decode_invariant() {
459 assert_eq!(
460 AddressDB::type_info(),
461 sqlx::postgres::PgTypeInfo::with_name("TEXT")
462 );
463
464 let address = bitcoin::Address::p2tr(
465 &SECP,
466 *bitvm_client::UNSPENDABLE_XONLY_PUBKEY,
467 None,
468 bitcoin::Network::Regtest,
469 );
470 let address = AddressDB(address.as_unchecked().clone());
471
472 test_encode_decode_invariant!(
473 AddressDB,
474 Address<NetworkUnchecked>,
475 address,
476 "address",
477 "TEXT"
478 );
479 }
480
481 #[tokio::test]
482 async fn evmaddressdb_encode_decode_invariant() {
483 assert_eq!(
484 EVMAddressDB::type_info(),
485 sqlx::postgres::PgTypeInfo::with_name("TEXT")
486 );
487
488 let evmaddress = EVMAddressDB(EVMAddress([0x45u8; 20]));
489 test_encode_decode_invariant!(EVMAddressDB, EVMAddress, evmaddress, "evmaddress", "TEXT");
490 }
491
492 #[tokio::test]
493 async fn txiddb_encode_decode_invariant() {
494 assert_eq!(
495 TxidDB::type_info(),
496 sqlx::postgres::PgTypeInfo::with_name("BYTEA")
497 );
498
499 let txid = TxidDB(Txid::all_zeros());
500 test_encode_decode_invariant!(TxidDB, Txid, txid, "txid", "BYTEA");
501 }
502
503 #[tokio::test]
504 async fn signaturedb_encode_decode_invariant() {
505 assert_eq!(
506 SignatureDB::type_info(),
507 sqlx::postgres::PgTypeInfo::with_name("BYTEA")
508 );
509
510 let signature = SignatureDB(Signature::from_slice(&[0u8; 64]).unwrap());
511 test_encode_decode_invariant!(SignatureDB, Signature, signature, "signature", "BYTEA");
512 }
513
514 #[tokio::test]
515 async fn signaturesdb_encode_decode_invariant() {
516 assert_eq!(
517 SignaturesDB::type_info(),
518 sqlx::postgres::PgTypeInfo::with_name("BYTEA")
519 );
520
521 use crate::rpc::clementine::{
522 DepositSignatures, NormalSignatureKind, NumberedSignatureKind,
523 };
524 let signatures = DepositSignatures {
525 signatures: vec![
526 TaggedSignature {
527 signature: vec![0x1Fu8; 64],
528 signature_id: Some(NormalSignatureKind::NotStored.into()),
529 },
530 TaggedSignature {
531 signature: vec![0x45u8; 64],
532 signature_id: Some((NumberedSignatureKind::NumberedNotStored, 1).into()),
533 },
534 ],
535 };
536 test_encode_decode_invariant!(
537 SignaturesDB,
538 DepositSignatures,
539 SignaturesDB(signatures),
540 "signatures",
541 "BYTEA"
542 );
543 }
544
545 #[tokio::test]
546 async fn utxodb_json_encode_decode_invariant() {
547 use sqlx::types::Json;
548
549 assert_eq!(
550 Json::<UtxoDB>::type_info(),
551 sqlx::postgres::PgTypeInfo::with_name("JSONB")
552 );
553
554 let utxodb = UtxoDB {
555 outpoint_db: OutPointDB(OutPoint {
556 txid: Txid::all_zeros(),
557 vout: 0x45,
558 }),
559 txout_db: TxOutDB(TxOut {
560 value: Amount::from_sat(0x45),
561 script_pubkey: ScriptBuf::new(),
562 }),
563 };
564
565 test_encode_decode_invariant!(Json<UtxoDB>, Utxodb, Json(utxodb), "utxodb", "JSONB");
566 }
567
568 #[tokio::test]
569 async fn blockhashdb_encode_decode_invariant() {
570 assert_eq!(
571 OutPointDB::type_info(),
572 sqlx::postgres::PgTypeInfo::with_name("TEXT")
573 );
574
575 let blockhash = BlockHashDB(BlockHash::all_zeros());
576 test_encode_decode_invariant!(BlockHashDB, BlockHash, blockhash, "blockhash", "TEXT");
577 }
578
579 #[tokio::test]
580 async fn blockheaderdb_encode_decode_invariant() {
581 assert_eq!(
582 OutPointDB::type_info(),
583 sqlx::postgres::PgTypeInfo::with_name("TEXT")
584 );
585
586 let blockheader = BlockHeaderDB(block::Header {
587 version: Version::TWO,
588 prev_blockhash: BlockHash::all_zeros(),
589 merkle_root: TxMerkleNode::all_zeros(),
590 time: 0,
591 bits: CompactTarget::default(),
592 nonce: 0,
593 });
594 test_encode_decode_invariant!(
595 BlockHeaderDB,
596 block::Header,
597 blockheader,
598 "blockheader",
599 "TEXT"
600 );
601 }
602
603 #[tokio::test]
604 async fn musigpubnoncedb_encode_decode_invariant() {
605 assert_eq!(
606 MusigPubNonceDB::type_info(),
607 sqlx::postgres::PgTypeInfo::with_name("BYTEA")
608 );
609
610 let kp = Keypair::from_secret_key(&SECP, &SecretKey::from_slice(&[1u8; 32]).unwrap());
611 let (_sec_nonce, pub_nonce) = musig2::nonce_pair(&kp).unwrap();
612 let public_nonce = MusigPubNonceDB(pub_nonce);
613 test_encode_decode_invariant!(
614 MusigPubNonceDB,
615 PublicNonce,
616 public_nonce,
617 "public_nonce",
618 "BYTEA"
619 );
620 }
621
622 #[tokio::test]
623 async fn musigaggnoncedb_encode_decode_invariant() {
624 assert_eq!(
625 MusigAggNonceDB::type_info(),
626 sqlx::postgres::PgTypeInfo::with_name("BYTEA")
627 );
628
629 let kp = Keypair::from_secret_key(&SECP, &SecretKey::from_slice(&[1u8; 32]).unwrap());
630 let (_sec_nonce, pub_nonce) = musig2::nonce_pair(&kp).unwrap();
631 let aggregated_nonce = MusigAggNonceDB(AggregatedNonce::new(SECP256K1, &[&pub_nonce]));
632 test_encode_decode_invariant!(
633 MusigAggNonceDB,
634 AggregatedNonce,
635 aggregated_nonce,
636 "aggregated_nonce",
637 "BYTEA"
638 );
639 }
640}