1use crate::commitment::{BlockTimestampCommitment, IONCommitment};
3use crate::config::ion_config;
4use crate::resolver::HTTPTrustchainResolver;
5use crate::sidetree::{ChunkFile, ChunkFileUri, CoreIndexFile, ProvisionalIndexFile};
6use crate::utils::{
7 block_header, decode_ipfs_content, locate_transaction, query_ipfs, transaction,
8 tx_to_op_return_cid,
9};
10use crate::{FullClient, LightClient, URL};
11use async_trait::async_trait;
12use bitcoin::blockdata::transaction::Transaction;
13use bitcoin::hash_types::BlockHash;
14use bitcoincore_rpc::RpcApi;
15use did_ion::sidetree::Delta;
16use futures::TryFutureExt;
17use ipfs_api_backend_hyper::IpfsClient;
18use mongodb::bson::doc;
19use serde::{Deserialize, Serialize};
20use serde_json::Value;
21use ssi::did::Document;
22use ssi::did_resolve::{DIDResolver, DocumentMetadata};
23use std::collections::HashMap;
24use std::marker::PhantomData;
25use std::str::FromStr;
26use std::sync::{Arc, Mutex};
27use trustchain_core::commitment::{
28 CommitmentChain, CommitmentError, DIDCommitment, TimestampCommitment,
29};
30use trustchain_core::resolver::{ResolverError, TrustchainResolver};
31use trustchain_core::verifier::{Timestamp, VerifiableTimestamp, Verifier, VerifierError};
32
33#[derive(Serialize, Deserialize, Clone, Debug)]
35pub struct VerificationBundle {
36 did_doc: Document,
38 did_doc_meta: DocumentMetadata,
40 chunk_file: Vec<u8>,
42 provisional_index_file: Vec<u8>,
44 core_index_file: Vec<u8>,
46 transaction: Vec<u8>,
48 merkle_block: Vec<u8>,
50 block_header: Vec<u8>,
52}
53
54impl VerificationBundle {
55 pub fn new(
56 did_doc: Document,
57 did_doc_meta: DocumentMetadata,
58 chunk_file: Vec<u8>,
59 provisional_index_file: Vec<u8>,
60 core_index_file: Vec<u8>,
61 transaction: Vec<u8>,
62 merkle_block: Vec<u8>,
63 block_header: Vec<u8>,
64 ) -> Self {
65 Self {
66 did_doc,
67 did_doc_meta,
68 chunk_file,
69 provisional_index_file,
70 core_index_file,
71 transaction,
72 merkle_block,
73 block_header,
74 }
75 }
76}
77
78pub struct TrustchainVerifier<T, U = FullClient>
80where
81 T: Sync + Send + DIDResolver,
82{
83 resolver: HTTPTrustchainResolver<T, U>,
85 rpc_client: Option<bitcoincore_rpc::Client>,
86 ipfs_client: Option<IpfsClient>,
87 bundles: Mutex<HashMap<String, Arc<VerificationBundle>>>,
88 endpoint: Option<URL>,
89 _marker: PhantomData<U>,
90}
91
92impl<T> TrustchainVerifier<T, FullClient>
93where
94 T: Send + Sync + DIDResolver,
95{
96 pub fn new(resolver: HTTPTrustchainResolver<T>) -> Self {
99 let rpc_client = bitcoincore_rpc::Client::new(
101 &ion_config().bitcoin_connection_string,
102 bitcoincore_rpc::Auth::UserPass(
103 ion_config().bitcoin_rpc_username.clone(),
104 ion_config().bitcoin_rpc_password.clone(),
105 ),
106 )
107 .unwrap();
109
110 let ipfs_client = IpfsClient::default();
116 let bundles = Mutex::new(HashMap::new());
117 Self {
118 resolver,
119 rpc_client: Some(rpc_client),
120 ipfs_client: Some(ipfs_client),
121 bundles,
122 endpoint: None,
123 _marker: PhantomData,
124 }
125 }
126
127 fn rpc_client(&self) -> &bitcoincore_rpc::Client {
129 self.rpc_client.as_ref().unwrap()
130 }
131
132 fn ipfs_client(&self) -> &IpfsClient {
134 self.ipfs_client.as_ref().unwrap()
135 }
136
137 pub async fn fetch_bundle(&self, did: &str) -> Result<(), VerifierError> {
141 let (did_doc, did_doc_meta) = self.resolve_did(did).await?;
142 let (block_hash, tx_index) = locate_transaction(did, self.rpc_client()).await?;
143 let tx = self.fetch_transaction(&block_hash, tx_index)?;
144 let transaction = bitcoin::util::psbt::serialize::Serialize::serialize(&tx);
145 let cid = self.op_return_cid(&tx)?;
146 let core_index_file = self.fetch_core_index_file(&cid).await?;
147 let provisional_index_file = self.fetch_prov_index_file(&core_index_file).await?;
148 let chunk_file = self.fetch_chunk_file(&provisional_index_file).await?;
149 let merkle_block = self.fetch_merkle_block(&block_hash, &tx)?;
150 let block_header = self.fetch_block_header(&block_hash)?;
151 let bundle = VerificationBundle::new(
153 did_doc,
154 did_doc_meta,
155 chunk_file,
156 provisional_index_file,
157 core_index_file,
158 transaction,
159 merkle_block,
160 block_header,
161 );
162 self.bundles
164 .lock()
165 .unwrap()
166 .insert(did.to_string(), Arc::new(bundle));
167 Ok(())
168 }
169
170 fn fetch_transaction(
171 &self,
172 block_hash: &BlockHash,
173 tx_index: u32,
174 ) -> Result<Transaction, VerifierError> {
175 transaction(block_hash, tx_index, Some(self.rpc_client())).map_err(|e| {
176 VerifierError::ErrorFetchingVerificationMaterial(
177 "Failed to fetch transaction.".to_string(),
178 e.into(),
179 )
180 })
181 }
182
183 async fn fetch_core_index_file(&self, cid: &str) -> Result<Vec<u8>, VerifierError> {
184 query_ipfs(cid, self.ipfs_client())
185 .map_err(|e| {
186 VerifierError::ErrorFetchingVerificationMaterial(
187 "Failed to fetch core index file".to_string(),
188 e.into(),
189 )
190 })
191 .await
192 }
193
194 async fn fetch_prov_index_file(
195 &self,
196 core_index_file: &[u8],
197 ) -> Result<Vec<u8>, VerifierError> {
198 let content = decode_ipfs_content(core_index_file, true).map_err(|e| {
199 VerifierError::FailureToFetchVerificationMaterial(format!(
200 "Failed to decode ION core index file: {}",
201 e
202 ))
203 })?;
204 let provisional_index_file_uri = serde_json::from_value::<CoreIndexFile>(content.clone())?
205 .provisional_index_file_uri
206 .ok_or(VerifierError::FailureToFetchVerificationMaterial(format!(
207 "Missing provisional index file URI in core index file: {content}."
208 )))?;
209 query_ipfs(&provisional_index_file_uri, self.ipfs_client())
210 .map_err(|e| {
211 VerifierError::ErrorFetchingVerificationMaterial(
212 "Failed to fetch ION provisional index file.".to_string(),
213 e.into(),
214 )
215 })
216 .await
217 }
218
219 async fn fetch_chunk_file(&self, prov_index_file: &[u8]) -> Result<Vec<u8>, VerifierError> {
220 let content = decode_ipfs_content(prov_index_file, true).map_err(|err| {
221 VerifierError::ErrorFetchingVerificationMaterial(
222 "Failed to decode ION provisional index file".to_string(),
223 err.into(),
224 )
225 })?;
226
227 let prov_index_file: ProvisionalIndexFile =
229 serde_json::from_value(content).map_err(|err| {
230 VerifierError::ErrorFetchingVerificationMaterial(
231 "Failed to parse ION provisional index file.".to_string(),
232 err.into(),
233 )
234 })?;
235
236 let chunk_file_uri = match prov_index_file.chunks.as_deref() {
241 Some([ChunkFileUri { chunk_file_uri }]) => chunk_file_uri,
242 _ => return Err(VerifierError::FailureToGetDIDContent("".to_string())),
243 };
244
245 query_ipfs(chunk_file_uri, self.ipfs_client())
247 .map_err(|err| {
248 VerifierError::ErrorFetchingVerificationMaterial(
249 "Failed to fetch ION provisional index file.".to_string(),
250 err.into(),
251 )
252 })
253 .await
254 }
255
256 fn fetch_merkle_block(
258 &self,
259 block_hash: &BlockHash,
260 tx: &Transaction,
261 ) -> Result<Vec<u8>, VerifierError> {
262 self.rpc_client()
263 .get_tx_out_proof(&[tx.txid()], Some(block_hash))
264 .map_err(|e| {
265 VerifierError::ErrorFetchingVerificationMaterial(
266 "Failed to fetch Merkle proof via RPC.".to_string(),
267 e.into(),
268 )
269 })
270 }
271
272 fn fetch_block_header(&self, block_hash: &BlockHash) -> Result<Vec<u8>, VerifierError> {
273 block_header(block_hash, Some(self.rpc_client()))
274 .map_err(|e| {
275 VerifierError::ErrorFetchingVerificationMaterial(
276 "Failed to fetch Bitcoin block header via RPC.".to_string(),
277 e.into(),
278 )
279 })
280 .map(|block_header| bitcoin::consensus::serialize(&block_header))
281 }
282
283 pub async fn verification_bundle(
285 &self,
286 did: &str,
287 ) -> Result<Arc<VerificationBundle>, VerifierError> {
288 if !self.bundles.lock().unwrap().contains_key(did) {
290 self.fetch_bundle(did).await?;
291 }
292 Ok(self.bundles.lock().unwrap().get(did).cloned().unwrap())
293 }
294 async fn resolve_did(&self, did: &str) -> Result<(Document, DocumentMetadata), VerifierError> {
296 let (res_meta, doc, doc_meta) = self.resolver.resolve_as_result(did).await?;
297 if let (Some(doc), Some(doc_meta)) = (doc, doc_meta) {
298 Ok((doc, doc_meta))
299 } else {
300 Err(VerifierError::DIDResolutionError(
301 format!("Missing Document and/or DocumentMetadata for DID: {}", did),
302 ResolverError::FailureWithMetadata(res_meta).into(),
303 ))
304 }
305 }
306}
307impl<T> TrustchainVerifier<T, LightClient>
308where
309 T: Send + Sync + DIDResolver,
310{
311 pub fn with_endpoint(resolver: HTTPTrustchainResolver<T, LightClient>, endpoint: URL) -> Self {
314 Self {
315 resolver,
316 rpc_client: None,
317 ipfs_client: None,
318 bundles: Mutex::new(HashMap::new()),
319 endpoint: Some(endpoint),
320 _marker: PhantomData,
321 }
322 }
323 fn endpoint(&self) -> &str {
325 self.endpoint.as_ref().unwrap()
326 }
327 pub async fn fetch_bundle(&self, did: &str) -> Result<(), VerifierError> {
332 let response = reqwest::get(format!("{}did/bundle/{did}", self.endpoint()))
333 .await
334 .map_err(|e| {
335 VerifierError::ErrorFetchingVerificationMaterial(
336 format!("Error requesting bundle from endpoint: {}", self.endpoint()),
337 e.into(),
338 )
339 })?;
340 let bundle: VerificationBundle = serde_json::from_str(
341 &response
342 .text()
343 .map_err(|e| {
344 VerifierError::ErrorFetchingVerificationMaterial(
345 format!(
346 "Error extracting bundle response body from endpoint: {}",
347 self.endpoint()
348 ),
349 e.into(),
350 )
351 })
352 .await?,
353 )?;
354 self.bundles
356 .lock()
357 .unwrap()
358 .insert(did.to_string(), Arc::new(bundle));
359 Ok(())
360 }
361
362 pub async fn verification_bundle(
364 &self,
365 did: &str,
366 ) -> Result<Arc<VerificationBundle>, VerifierError> {
367 if !self.bundles.lock().unwrap().contains_key(did) {
369 self.fetch_bundle(did).await?;
370 }
371 Ok(self.bundles.lock().unwrap().get(did).cloned().unwrap())
372 }
373}
374
375impl<T, U> TrustchainVerifier<T, U>
376where
377 T: Send + Sync + DIDResolver,
378{
379 fn op_return_cid(&self, tx: &Transaction) -> Result<String, VerifierError> {
381 tx_to_op_return_cid(tx)
382 }
383}
384
385pub fn construct_commitment(
387 bundle: Arc<VerificationBundle>,
388) -> Result<IONCommitment, CommitmentError> {
389 IONCommitment::new(
390 bundle.did_doc.clone(),
391 bundle.chunk_file.clone(),
392 bundle.provisional_index_file.clone(),
393 bundle.core_index_file.clone(),
394 bundle.transaction.clone(),
395 bundle.merkle_block.clone(),
396 bundle.block_header.clone(),
397 )
398}
399
400pub fn content_deltas(chunk_file_json: &Value) -> Result<Vec<Delta>, VerifierError> {
402 let chunk_file: ChunkFile =
403 serde_json::from_value(chunk_file_json.to_owned()).map_err(|_| {
404 VerifierError::FailureToParseDIDContent(format!(
405 "Failed to parse chunk file: {}",
406 chunk_file_json
407 ))
408 })?;
409 Ok(chunk_file.deltas)
410}
411
412#[async_trait]
414impl<T> Verifier<T> for TrustchainVerifier<T, FullClient>
415where
416 T: Sync + Send + DIDResolver,
417{
418 fn validate_pow_hash(&self, hash: &str) -> Result<(), VerifierError> {
419 let block_hash = BlockHash::from_str(hash)
420 .map_err(|_| VerifierError::InvalidProofOfWorkHash(hash.to_string()))?;
421 let _block_header = block_header(&block_hash, Some(self.rpc_client()))
422 .map_err(|_| VerifierError::FailureToGetBlockHeader(hash.to_string()))?;
423 Ok(())
424 }
425
426 async fn did_commitment(&self, did: &str) -> Result<Box<dyn DIDCommitment>, VerifierError> {
427 let bundle = self.verification_bundle(did).await?;
428 Ok(construct_commitment(bundle).map(Box::new)?)
429 }
430
431 fn resolver(&self) -> &dyn TrustchainResolver {
432 &self.resolver
433 }
434
435 async fn verifiable_timestamp(
436 &self,
437 did: &str,
438 expected_timestamp: Timestamp,
439 ) -> Result<Box<dyn VerifiableTimestamp>, VerifierError> {
440 let did_commitment = self.did_commitment(did).await?;
441 let ion_commitment = did_commitment
443 .as_any()
444 .downcast_ref::<IONCommitment>()
445 .unwrap(); let timestamp_commitment = Box::new(BlockTimestampCommitment::new(
447 ion_commitment
448 .chained_commitment()
449 .commitments()
450 .last()
451 .expect("Unexpected empty commitment chain.")
452 .candidate_data()
453 .to_owned(),
454 expected_timestamp,
455 )?);
456 Ok(Box::new(IONTimestamp::new(
457 did_commitment,
458 timestamp_commitment,
459 )))
460 }
461}
462
463#[async_trait]
464impl<T> Verifier<T> for TrustchainVerifier<T, LightClient>
465where
466 T: Sync + Send + DIDResolver,
467{
468 fn validate_pow_hash(&self, hash: &str) -> Result<(), VerifierError> {
469 if hash.chars().take_while(|&c| c == '0').count() < crate::MIN_POW_ZEROS {
475 return Err(VerifierError::InvalidProofOfWorkHash(format!(
476 "{}, only has {} zeros but MIN_POW_ZEROS is {}",
477 hash,
478 hash.chars().take_while(|&c| c == '0').count(),
479 crate::MIN_POW_ZEROS
480 )));
481 }
482
483 Ok(())
485 }
486
487 async fn did_commitment(&self, did: &str) -> Result<Box<dyn DIDCommitment>, VerifierError> {
488 let bundle = self.verification_bundle(did).await?;
489 Ok(construct_commitment(bundle).map(Box::new)?)
490 }
491
492 fn resolver(&self) -> &dyn TrustchainResolver {
493 &self.resolver
494 }
495
496 async fn verifiable_timestamp(
497 &self,
498 did: &str,
499 expected_timestamp: Timestamp,
500 ) -> Result<Box<dyn VerifiableTimestamp>, VerifierError> {
501 let did_commitment = self.did_commitment(did).await?;
502 let ion_commitment = did_commitment
504 .as_any()
505 .downcast_ref::<IONCommitment>()
506 .unwrap(); let timestamp_commitment = Box::new(BlockTimestampCommitment::new(
508 ion_commitment
509 .chained_commitment()
510 .commitments()
511 .last()
512 .expect("Unexpected empty commitment chain.")
513 .candidate_data()
514 .to_owned(),
515 expected_timestamp,
516 )?);
517 Ok(Box::new(IONTimestamp::new(
518 did_commitment,
519 timestamp_commitment,
520 )))
521 }
522}
523
524pub struct IONTimestamp {
526 did_commitment: Box<dyn DIDCommitment>,
527 timestamp_commitment: Box<dyn TimestampCommitment>,
528}
529
530impl IONTimestamp {
531 fn new(
532 did_commitment: Box<dyn DIDCommitment>,
533 timestamp_commitment: Box<dyn TimestampCommitment>,
534 ) -> Self {
535 Self {
536 did_commitment,
537 timestamp_commitment,
538 }
539 }
540
541 pub fn did(&self) -> &str {
543 self.did_commitment.did()
544 }
545 pub fn did_document(&self) -> &Document {
547 self.did_commitment.did_document()
548 }
549}
550
551impl VerifiableTimestamp for IONTimestamp {
552 fn did_commitment(&self) -> &dyn DIDCommitment {
553 self.did_commitment.as_ref()
554 }
555
556 fn timestamp_commitment(&self) -> &dyn TimestampCommitment {
557 self.timestamp_commitment.as_ref()
558 }
559}
560
561#[cfg(test)]
562mod tests {
563 use super::*;
564 use crate::{
565 data::{
566 TEST_BLOCK_HEADER_HEX, TEST_CHUNK_FILE_HEX, TEST_CORE_INDEX_FILE_HEX,
567 TEST_MERKLE_BLOCK_HEX, TEST_PROVISIONAL_INDEX_FILE_HEX, TEST_TRANSACTION_HEX,
568 },
569 trustchain_resolver,
570 };
571 use bitcoin::{BlockHeader, MerkleBlock};
572 use flate2::read::GzDecoder;
573 use std::{io::Read, str::FromStr};
574 use trustchain_core::commitment::TrivialCommitment;
575
576 const ENDPOINT: &str = "http://localhost:3000/";
577
578 #[test]
579 #[ignore = "Integration test requires Bitcoin RPC"]
580 fn test_op_return_cid() {
581 let resolver = trustchain_resolver(ENDPOINT);
582 let target = TrustchainVerifier::new(resolver);
583
584 let expected = "QmRvgZm4J3JSxfk4wRjE2u2Hi2U7VmobYnpqhqH5QP6J97";
587
588 let block_hash =
590 BlockHash::from_str("000000000000000eaa9e43748768cd8bf34f43aaa03abd9036c463010a0c6e7f")
591 .unwrap();
592 let tx_index = 3;
593 let tx = transaction(&block_hash, tx_index, Some(target.rpc_client())).unwrap();
594
595 let actual = target.op_return_cid(&tx).unwrap();
596 assert_eq!(expected, actual);
597 }
598
599 #[tokio::test]
600 #[ignore = "Integration test requires ION"]
601 async fn test_resolve_did() {
602 let resolver = trustchain_resolver(ENDPOINT);
604 let target = TrustchainVerifier::new(resolver);
605 let did = "did:ion:test:EiCClfEdkTv_aM3UnBBhlOV89LlGhpQAbfeZLFdFxVFkEg";
606 let result = target.resolve_did(did).await;
607 assert!(result.is_ok());
608 }
609
610 #[tokio::test]
611 #[ignore = "Integration test requires IPFS"]
612 async fn test_fetch_chunk_file() {
613 let resolver = trustchain_resolver(ENDPOINT);
614 let target = TrustchainVerifier::new(resolver);
615
616 let prov_index_file = hex::decode(TEST_PROVISIONAL_INDEX_FILE_HEX).unwrap();
617
618 let result = target.fetch_chunk_file(&prov_index_file).await;
619 assert!(result.is_ok());
620 let chunk_file_bytes = result.unwrap();
621
622 let mut decoder = GzDecoder::new(&*chunk_file_bytes);
623 let mut ipfs_content_str = String::new();
624 let value: serde_json::Value = match decoder.read_to_string(&mut ipfs_content_str) {
625 Ok(_) => serde_json::from_str(&ipfs_content_str).unwrap(),
626 Err(_) => panic!(),
627 };
628 assert!(value.is_object());
629 assert!(value.as_object().unwrap().contains_key("deltas"));
630 }
631
632 #[tokio::test]
633 #[ignore = "Integration test requires IPFS"]
634 async fn test_fetch_core_index_file() {
635 let resolver = trustchain_resolver(ENDPOINT);
636 let target = TrustchainVerifier::new(resolver);
637
638 let cid = "QmRvgZm4J3JSxfk4wRjE2u2Hi2U7VmobYnpqhqH5QP6J97";
639 let result = target.fetch_core_index_file(cid).await;
640 assert!(result.is_ok());
641 let core_index_file_bytes = result.unwrap();
642
643 let mut decoder = GzDecoder::new(&*core_index_file_bytes);
644 let mut ipfs_content_str = String::new();
645 let value: serde_json::Value = match decoder.read_to_string(&mut ipfs_content_str) {
646 Ok(_) => serde_json::from_str(&ipfs_content_str).unwrap(),
647 Err(_) => panic!(),
648 };
649 assert!(value.is_object());
650 assert!(value
651 .as_object()
652 .unwrap()
653 .contains_key("provisionalIndexFileUri"));
654 }
655
656 #[tokio::test]
657 #[ignore = "Integration test requires ION, MongoDB, IPFS and Bitcoin RPC"]
658 async fn test_fetch_bundle() {
659 let resolver = trustchain_resolver(ENDPOINT);
661 let target = TrustchainVerifier::new(resolver);
662
663 assert!(target.bundles.lock().unwrap().is_empty());
664 let did = "did:ion:test:EiCClfEdkTv_aM3UnBBhlOV89LlGhpQAbfeZLFdFxVFkEg";
665 target.fetch_bundle(did).await.unwrap();
666
667 assert!(!target.bundles.lock().unwrap().is_empty());
668 assert_eq!(target.bundles.lock().unwrap().len(), 1);
669 assert!(target.bundles.lock().unwrap().contains_key(did));
670 }
671
672 #[tokio::test]
673 #[ignore = "Integration test requires ION, MongoDB, IPFS and Bitcoin RPC"]
674 async fn test_commitment() {
675 let resolver = trustchain_resolver(ENDPOINT);
677 let target = TrustchainVerifier::new(resolver);
678
679 let did = "did:ion:test:EiCClfEdkTv_aM3UnBBhlOV89LlGhpQAbfeZLFdFxVFkEg";
680
681 assert!(target.bundles.lock().unwrap().is_empty());
682 let result = target.did_commitment(did).await.unwrap();
683
684 assert!(!target.bundles.lock().unwrap().is_empty());
686 assert_eq!(target.bundles.lock().unwrap().len(), 1);
687 let bundle = target.bundles.lock().unwrap().get(did).cloned().unwrap();
688 let commitment = construct_commitment(bundle).unwrap();
689 assert_eq!(result.hash().unwrap(), commitment.hash().unwrap());
690 }
691
692 #[test]
693 fn test_chunk_file_deserialize() {
694 let bytes = hex::decode(TEST_CHUNK_FILE_HEX).unwrap();
695 let mut decoder = GzDecoder::new(&*bytes);
696 let mut ipfs_content_str = String::new();
697 let value: serde_json::Value = match decoder.read_to_string(&mut ipfs_content_str) {
698 Ok(_) => serde_json::from_str(&ipfs_content_str).unwrap(),
699 Err(_) => panic!(),
700 };
701 assert!(value.is_object());
702 assert!(value.as_object().unwrap().contains_key("deltas"));
703 }
704
705 #[test]
706 fn test_prov_index_file_deserialize() {
707 let bytes = hex::decode(TEST_PROVISIONAL_INDEX_FILE_HEX).unwrap();
708 let mut decoder = GzDecoder::new(&*bytes);
709 let mut ipfs_content_str = String::new();
710 let value: serde_json::Value = match decoder.read_to_string(&mut ipfs_content_str) {
711 Ok(_) => serde_json::from_str(&ipfs_content_str).unwrap(),
712 Err(_) => panic!(),
713 };
714 assert!(value.is_object());
715 assert!(value.as_object().unwrap().contains_key("chunks"));
716 }
717
718 #[test]
719 fn test_core_index_file_deserialize() {
720 let bytes = hex::decode(TEST_CORE_INDEX_FILE_HEX).unwrap();
721 let mut decoder = GzDecoder::new(&*bytes);
722 let mut ipfs_content_str = String::new();
723 let value: serde_json::Value = match decoder.read_to_string(&mut ipfs_content_str) {
724 Ok(_) => serde_json::from_str(&ipfs_content_str).unwrap(),
725 Err(_) => panic!(),
726 };
727 assert!(value.is_object());
728 assert!(value
729 .as_object()
730 .unwrap()
731 .contains_key("provisionalIndexFileUri"));
732 }
733
734 #[test]
735 fn test_tx_deserialize() {
736 let bytes = hex::decode(TEST_TRANSACTION_HEX).unwrap();
737 let tx: Transaction =
738 bitcoin::util::psbt::serialize::Deserialize::deserialize(&bytes).unwrap();
739 let expected_txid = "9dc43cca950d923442445340c2e30bc57761a62ef3eaf2417ec5c75784ea9c2c";
740 assert_eq!(tx.txid().to_string(), expected_txid);
741 }
742
743 #[test]
744 fn test_merkle_block_deserialize() {
745 let bytes = hex::decode(TEST_MERKLE_BLOCK_HEX).unwrap();
746 let merkle_block: MerkleBlock = bitcoin::consensus::deserialize(&bytes).unwrap();
747 let header = merkle_block.header;
748 let expected_merkle_root =
749 "7dce795209d4b5051da3f5f5293ac97c2ec677687098062044654111529cad69";
750 assert_eq!(header.merkle_root.to_string(), expected_merkle_root);
751 }
752
753 #[test]
754 fn test_block_header_deserialize() {
755 let bytes = hex::decode(TEST_BLOCK_HEADER_HEX).unwrap();
756 let header: BlockHeader = bitcoin::consensus::deserialize(&bytes).unwrap();
757 let expected_merkle_root =
758 "7dce795209d4b5051da3f5f5293ac97c2ec677687098062044654111529cad69";
759 assert_eq!(header.merkle_root.to_string(), expected_merkle_root);
760 }
761}