Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[r2r] Implement block headers storage for IndexedDB and re-enable spv in wasm #1644

Merged
merged 22 commits into from
Feb 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mm2src/coins/tx_history_storage/tx_history_v2_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ async fn test_get_history_for_addresses_impl() {
assert_get_history_result(result, Vec::new(), 4, 4);
}

#[cfg(test)]
#[cfg(all(test, not(target_arch = "wasm32")))]
mod native_tests {
use super::wallet_id_for_test;
use crate::my_tx_history_v2::TxHistoryStorage;
Expand Down

This file was deleted.

269 changes: 263 additions & 6 deletions mm2src/coins/utxo/utxo_block_header_storage/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#[cfg(target_arch = "wasm32")] mod indexedb_block_header_storage;
#[cfg(target_arch = "wasm32")]
pub use indexedb_block_header_storage::IndexedDBBlockHeadersStorage;

#[cfg(not(target_arch = "wasm32"))] mod sql_block_header_storage;
#[cfg(not(target_arch = "wasm32"))]
pub use sql_block_header_storage::SqliteBlockHeadersStorage;

#[cfg(target_arch = "wasm32")] mod wasm;
#[cfg(target_arch = "wasm32")]
pub use wasm::IDBBlockHeadersStorage;

use async_trait::async_trait;
use chain::BlockHeader;
use mm2_core::mm_ctx::MmArc;
Expand Down Expand Up @@ -39,9 +39,9 @@ impl BlockHeaderStorage {
}

#[cfg(target_arch = "wasm32")]
pub(crate) fn new_from_ctx(_ctx: MmArc, _ticker: String) -> Result<Self, BlockHeaderStorageError> {
pub(crate) fn new_from_ctx(ctx: MmArc, ticker: String) -> Result<Self, BlockHeaderStorageError> {
Ok(BlockHeaderStorage {
inner: Box::new(IndexedDBBlockHeadersStorage {}),
inner: Box::new(IDBBlockHeadersStorage::new(&ctx, ticker)),
})
}

Expand All @@ -57,6 +57,9 @@ impl BlockHeaderStorage {
inner: Box::new(SqliteBlockHeadersStorage { ticker, conn }),
})
}

#[cfg(test)]
pub(crate) fn into_inner(self) -> Box<dyn BlockHeaderStorageOps> { self.inner }
}

#[async_trait]
Expand Down Expand Up @@ -101,4 +104,258 @@ impl BlockHeaderStorageOps for BlockHeaderStorage {
async fn remove_headers_up_to_height(&self, to_height: u64) -> Result<(), BlockHeaderStorageError> {
self.inner.remove_headers_up_to_height(to_height).await
}

async fn is_table_empty(&self) -> Result<(), BlockHeaderStorageError> { self.inner.is_table_empty().await }
}

#[cfg(test)]
mod block_headers_storage_tests {
use super::*;
use chain::BlockHeaderBits;
use mm2_test_helpers::for_tests::mm_ctx_with_custom_db;

cfg_wasm32! {
use wasm_bindgen_test::*;
use spv_validation::work::MAX_BITS_BTC;

wasm_bindgen_test_configure!(run_in_browser);
}

cfg_native! {
use spv_validation::work::MAX_BITS_BTC;

}

pub(crate) async fn test_add_block_headers_impl(for_coin: &str) {
let ctx = mm_ctx_with_custom_db();
let storage = BlockHeaderStorage::new_from_ctx(ctx, for_coin.to_string())
.unwrap()
.into_inner();
storage.init().await.unwrap();

let mut headers = HashMap::with_capacity(1);
let block_header: BlockHeader = "0000002076d41d3e4b0bfd4c0d3b30aa69fdff3ed35d85829efd04000000000000000000b386498b583390959d9bac72346986e3015e83ac0b54bc7747a11a494ac35c94bb3ce65a53fb45177f7e311c".into();
headers.insert(520481, block_header);
storage.add_block_headers_to_storage(headers).await.unwrap();
assert!(!storage.is_table_empty().await.is_ok());
}

pub(crate) async fn test_get_block_header_impl(for_coin: &str) {
let ctx = mm_ctx_with_custom_db();
let storage = BlockHeaderStorage::new_from_ctx(ctx, for_coin.to_string())
.unwrap()
.into_inner();
storage.init().await.unwrap();

let mut headers = HashMap::with_capacity(1);
let block_header: BlockHeader = "0000002076d41d3e4b0bfd4c0d3b30aa69fdff3ed35d85829efd04000000000000000000b386498b583390959d9bac72346986e3015e83ac0b54bc7747a11a494ac35c94bb3ce65a53fb45177f7e311c".into();
headers.insert(520481, block_header);

storage.add_block_headers_to_storage(headers).await.unwrap();
assert!(!storage.is_table_empty().await.is_ok());

let hex = storage.get_block_header_raw(520481).await.unwrap().unwrap();
assert_eq!(hex, "0000002076d41d3e4b0bfd4c0d3b30aa69fdff3ed35d85829efd04000000000000000000b386498b583390959d9bac72346986e3015e83ac0b54bc7747a11a494ac35c94bb3ce65a53fb45177f7e311c".to_string());

let block_header = storage.get_block_header(520481).await.unwrap().unwrap();
let block_hash: H256 = "0000000000000000002e31d0714a5ab23100945ff87ba2d856cd566a3c9344ec".into();
assert_eq!(block_header.hash(), block_hash.reversed());

let height = storage.get_block_height_by_hash(block_hash).await.unwrap().unwrap();
assert_eq!(height, 520481);
}

pub(crate) async fn test_get_last_block_header_with_non_max_bits_impl(for_coin: &str) {
let ctx = mm_ctx_with_custom_db();
let storage = BlockHeaderStorage::new_from_ctx(ctx, for_coin.to_string())
.unwrap()
.into_inner();
storage.init().await.unwrap();

let mut headers = HashMap::with_capacity(2);

// This block has max difficulty
// https://live.blockcypher.com/btc-testnet/block/00000000961a9d117feb57e516e17217207a849bf6cdfce529f31d9a96053530/
let block_header: BlockHeader = "02000000ea01a61a2d7420a1b23875e40eb5eb4ca18b378902c8e6384514ad0000000000c0c5a1ae80582b3fe319d8543307fa67befc2a734b8eddb84b1780dfdf11fa2b20e71353ffff001d00805fe0".into();
headers.insert(201595, block_header);

// https://live.blockcypher.com/btc-testnet/block/0000000000ad144538e6c80289378ba14cebb50ee47538b2a120742d1aa601ea/
let expected_block_header: BlockHeader = "02000000cbed7fd98f1f06e85c47e13ff956533642056be45e7e6b532d4d768f00000000f2680982f333fcc9afa7f9a5e2a84dc54b7fe10605cd187362980b3aa882e9683be21353ab80011c813e1fc0".into();
headers.insert(201594, expected_block_header.clone());

// This block has max difficulty
// https://live.blockcypher.com/btc-testnet/block/0000000000ad144538e6c80289378ba14cebb50ee47538b2a120742d1aa601ea/
let block_header: BlockHeader = "020000001f38c8e30b30af912fbd4c3e781506713cfb43e73dff6250348e060000000000afa8f3eede276ccb4c4ee649ad9823fc181632f262848ca330733e7e7e541beb9be51353ffff001d00a63037".into();
headers.insert(201593, block_header);

storage.add_block_headers_to_storage(headers).await.unwrap();
assert!(!storage.is_table_empty().await.is_ok());

let actual_block_header = storage
.get_last_block_header_with_non_max_bits(MAX_BITS_BTC)
.await
.unwrap()
.unwrap();
assert_ne!(actual_block_header.bits, BlockHeaderBits::Compact(MAX_BITS_BTC.into()));
assert_eq!(actual_block_header, expected_block_header);
}

pub(crate) async fn test_get_last_block_height_impl(for_coin: &str) {
let ctx = mm_ctx_with_custom_db();
let storage = BlockHeaderStorage::new_from_ctx(ctx, for_coin.to_string())
.unwrap()
.into_inner();
storage.init().await.unwrap();

let mut headers = HashMap::with_capacity(2);

// https://live.blockcypher.com/btc-testnet/block/00000000961a9d117feb57e516e17217207a849bf6cdfce529f31d9a96053530/
let block_header: BlockHeader = "02000000ea01a61a2d7420a1b23875e40eb5eb4ca18b378902c8e6384514ad0000000000c0c5a1ae80582b3fe319d8543307fa67befc2a734b8eddb84b1780dfdf11fa2b20e71353ffff001d00805fe0".into();
headers.insert(201595, block_header);

// https://live.blockcypher.com/btc-testnet/block/0000000000ad144538e6c80289378ba14cebb50ee47538b2a120742d1aa601ea/
let block_header: BlockHeader = "02000000cbed7fd98f1f06e85c47e13ff956533642056be45e7e6b532d4d768f00000000f2680982f333fcc9afa7f9a5e2a84dc54b7fe10605cd187362980b3aa882e9683be21353ab80011c813e1fc0".into();
headers.insert(201594, block_header);

// https://live.blockcypher.com/btc-testnet/block/0000000000ad144538e6c80289378ba14cebb50ee47538b2a120742d1aa601ea/
let block_header: BlockHeader = "020000001f38c8e30b30af912fbd4c3e781506713cfb43e73dff6250348e060000000000afa8f3eede276ccb4c4ee649ad9823fc181632f262848ca330733e7e7e541beb9be51353ffff001d00a63037".into();
headers.insert(201593, block_header);

storage.add_block_headers_to_storage(headers).await.unwrap();
assert!(!storage.is_table_empty().await.is_ok());

let last_block_height = storage.get_last_block_height().await.unwrap();
assert_eq!(last_block_height.unwrap(), 201595);
}

pub(crate) async fn test_remove_headers_up_to_height_impl(for_coin: &str) {
let ctx = mm_ctx_with_custom_db();
let storage = BlockHeaderStorage::new_from_ctx(ctx, for_coin.to_string())
.unwrap()
.into_inner();
storage.init().await.unwrap();

let mut headers = HashMap::with_capacity(2);

// https://live.blockcypher.com/btc-testnet/block/00000000961a9d117feb57e516e17217207a849bf6cdfce529f31d9a96053530/
let block_header: BlockHeader = "02000000ea01a61a2d7420a1b23875e40eb5eb4ca18b378902c8e6384514ad0000000000c0c5a1ae80582b3fe319d8543307fa67befc2a734b8eddb84b1780dfdf11fa2b20e71353ffff001d00805fe0".into();
headers.insert(201595, block_header);

// https://live.blockcypher.com/btc-testnet/block/0000000000ad144538e6c80289378ba14cebb50ee47538b2a120742d1aa601ea/
let block_header: BlockHeader = "02000000cbed7fd98f1f06e85c47e13ff956533642056be45e7e6b532d4d768f00000000f2680982f333fcc9afa7f9a5e2a84dc54b7fe10605cd187362980b3aa882e9683be21353ab80011c813e1fc0".into();
headers.insert(201594, block_header);

// https://live.blockcypher.com/btc-testnet/block/0000000000ad144538e6c80289378ba14cebb50ee47538b2a120742d1aa601ea/
let block_header: BlockHeader = "020000001f38c8e30b30af912fbd4c3e781506713cfb43e73dff6250348e060000000000afa8f3eede276ccb4c4ee649ad9823fc181632f262848ca330733e7e7e541beb9be51353ffff001d00a63037".into();
headers.insert(201593, block_header);

storage.add_block_headers_to_storage(headers).await.unwrap();
assert!(!storage.is_table_empty().await.is_ok());

// Remove 2 headers from storage.
storage.remove_headers_up_to_height(201594).await.unwrap();

// Validate that blockers 201593..201594 are removed from storage.
for h in 201593..201594 {
let block_header = storage.get_block_header(h).await.unwrap();
assert!(block_header.is_none());
}

// Last height should be 201595
let last_block_height = storage.get_last_block_height().await.unwrap();
assert_eq!(last_block_height.unwrap(), 201595);
}
}

#[cfg(all(test, not(target_arch = "wasm32")))]
mod native_tests {
use super::*;
use crate::utxo::utxo_block_header_storage::block_headers_storage_tests::*;
use common::block_on;
use mm2_test_helpers::for_tests::mm_ctx_with_custom_db;

#[test]
fn test_init_collection() {
let for_coin = "init_collection";
let ctx = mm_ctx_with_custom_db();
let storage = BlockHeaderStorage::new_from_ctx(ctx, for_coin.to_string())
.unwrap()
.into_inner();

let initialized = block_on(storage.is_initialized_for()).unwrap();
assert!(!initialized);

block_on(storage.init()).unwrap();
// repetitive init must not fail
block_on(storage.init()).unwrap();

let initialized = block_on(storage.is_initialized_for()).unwrap();
assert!(initialized);
}

const FOR_COIN_GET: &str = "get";
const FOR_COIN_INSERT: &str = "insert";
#[test]
fn test_add_block_headers() { block_on(test_add_block_headers_impl(FOR_COIN_INSERT)) }

#[test]
fn test_test_get_block_header() { block_on(test_get_block_header_impl(FOR_COIN_GET)) }

#[test]
fn test_get_last_block_header_with_non_max_bits() {
block_on(test_get_last_block_header_with_non_max_bits_impl(FOR_COIN_GET))
}

#[test]
fn test_get_last_block_height() { block_on(test_get_last_block_height_impl(FOR_COIN_GET)) }

#[test]
fn test_remove_headers_up_to_height() { block_on(test_remove_headers_up_to_height_impl(FOR_COIN_GET)) }
}

#[cfg(all(test, target_arch = "wasm32"))]
mod wasm_test {
use super::*;
use crate::utxo::utxo_block_header_storage::block_headers_storage_tests::*;
use common::log::wasm_log::register_wasm_log;
use mm2_test_helpers::for_tests::mm_ctx_with_custom_db;
use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};

wasm_bindgen_test_configure!(run_in_browser);

const FOR_COIN: &str = "RICK";

#[wasm_bindgen_test]
async fn test_storage_init() {
let ctx = mm_ctx_with_custom_db();
let storage = IDBBlockHeadersStorage::new(&ctx, "RICK".to_string());

register_wasm_log();

let initialized = storage.is_initialized_for().await.unwrap();
assert!(initialized);

// repetitive init must not fail
storage.init().await.unwrap();

let initialized = storage.is_initialized_for().await.unwrap();
assert!(initialized);
}

#[wasm_bindgen_test]
async fn test_add_block_headers() { test_add_block_headers_impl(FOR_COIN).await }

#[wasm_bindgen_test]
async fn test_test_get_block_header() { test_get_block_header_impl(FOR_COIN).await }

#[wasm_bindgen_test]
async fn test_get_last_block_header_with_non_max_bits() {
test_get_last_block_header_with_non_max_bits_impl(FOR_COIN).await
}

#[wasm_bindgen_test]
async fn test_get_last_block_height() { test_get_last_block_height_impl(FOR_COIN).await }

#[wasm_bindgen_test]
async fn test_remove_headers_up_to_height() { test_remove_headers_up_to_height_impl(FOR_COIN).await }
}
Loading