Skip to content

Commit

Permalink
Implement block headers storage for IndexedDB and re-enable spv in wa…
Browse files Browse the repository at this point in the history
…sm (#1644)

* start indexddb block header storage impl

Signed-off-by: borngraced <samiodev@icloud.com>

* save dev state

Signed-off-by: borngraced <samiodev@icloud.com>

* impl IDBBlockHeadersStorage and fix clippy
Signed-off-by: borngraced <samiodev@icloud.com>

* impl add_block_headers_to_storage and unit test.
Signed-off-by: borngraced <samiodev@icloud.com>

* impl indexeddb get_block_header and unit test.

Signed-off-by: borngraced <samiodev@icloud.com>

* saved ev state

Signed-off-by: borngraced <samiodev@icloud.com>

* impl get_block_height_by_hash and unit test

Signed-off-by: borngraced <samiodev@icloud.com>

* impl get_block_header_raw and unit test

Signed-off-by: borngraced <samiodev@icloud.com>

* impl get_last_block_height and unit test for indexed db

Signed-off-by: borngraced <samiodev@icloud.com>

* fix test_get_last_block_height

Signed-off-by: borngraced <samiodev@icloud.com>

* impl get_last_block_header_with_non_max_bits and unit test

Signed-off-by: borngraced <samiodev@icloud.com>

* fmt imports

Signed-off-by: borngraced <samiodev@icloud.com>

* re-enable spv for wasm.

Signed-off-by: borngraced <samiodev@icloud.com>

* use multi_index for querying and inserting block_header

Signed-off-by: borngraced <samiodev@icloud.com>

* use multi_index for querying block_header hash

Signed-off-by: borngraced <samiodev@icloud.com>

* fix wasm imports and index
Signed-off-by: borngraced <samiodev@icloud.com>

* fix review notes

Signed-off-by: borngraced <samiodev@icloud.com>

* refactor sql storage and indexeddb tests

Signed-off-by: borngraced <samiodev@icloud.com>

* saved dev state after merge

Signed-off-by: borngraced <samiodev@icloud.com>

* fix failing unit tests

Signed-off-by: borngraced <samiodev@icloud.com>

* fix review notes

Signed-off-by: borngraced <samiodev@icloud.com>

---------

Signed-off-by: borngraced <samiodev@icloud.com>
  • Loading branch information
borngraced committed Feb 20, 2023
1 parent 0c507cc commit d01dd41
Show file tree
Hide file tree
Showing 12 changed files with 721 additions and 260 deletions.
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

0 comments on commit d01dd41

Please sign in to comment.