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

fix(indexeddb): fix IDB cursor.continue_() call after drop #2028

Merged
merged 19 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use super::BlockHeaderStorageTable;
use async_trait::async_trait;
use chain::BlockHeader;
use mm2_core::mm_ctx::MmArc;
use mm2_db::indexed_db::cursor_prelude::CursorError;
use mm2_db::indexed_db::{BeBigUint, ConstructibleDb, DbIdentifier, DbInstance, DbLocked, IndexedDb, IndexedDbBuilder,
InitDbResult, MultiIndex, SharedDb};
use mm2_err_handle::prelude::*;
Expand Down Expand Up @@ -71,59 +72,58 @@ impl BlockHeaderStorageOps for IDBBlockHeadersStorage {
&self,
headers: HashMap<u64, BlockHeader>,
) -> Result<(), BlockHeaderStorageError> {
let ticker = self.ticker.clone();
let ticker = &self.ticker;
let locked_db = self
.lock_db()
.await
.map_err(|err| BlockHeaderStorageError::add_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::add_err(ticker, err.to_string()))?;
let db_transaction = locked_db
.get_inner()
.transaction()
.await
.map_err(|err| BlockHeaderStorageError::add_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::add_err(ticker, err.to_string()))?;
let block_headers_db = db_transaction
.table::<BlockHeaderStorageTable>()
.await
.map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::table_err(ticker, err.to_string()))?;

for (height, header) in headers {
let hash = header.hash().reversed().to_string();
let raw_header = hex::encode(header.raw());
let bits: u32 = header.bits.into();
let headers_to_store = BlockHeaderStorageTable {
ticker: ticker.clone(),
ticker: self.ticker.clone(),
height: BeBigUint::from(height),
bits,
hash,
raw_header,
};
let index_keys = MultiIndex::new(BlockHeaderStorageTable::TICKER_HEIGHT_INDEX)
.with_value(&ticker)
.map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?
.with_value(ticker)
.map_err(|err| BlockHeaderStorageError::table_err(ticker, err.to_string()))?
.with_value(BeBigUint::from(height))
.map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::table_err(ticker, err.to_string()))?;

block_headers_db
.replace_item_by_unique_multi_index(index_keys, &headers_to_store)
.await
.map_err(|err| BlockHeaderStorageError::add_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::add_err(ticker, err.to_string()))?;
}
Ok(())
}

async fn get_block_header(&self, height: u64) -> Result<Option<BlockHeader>, BlockHeaderStorageError> {
let ticker = self.ticker.clone();
if let Some(raw_header) = self.get_block_header_raw(height).await? {
let serialized = &hex::decode(raw_header).map_err(|e| BlockHeaderStorageError::DecodeError {
coin: ticker.clone(),
coin: self.ticker.clone(),
reason: e.to_string(),
})?;
let mut reader = Reader::new_with_coin_variant(serialized, ticker.as_str().into());
let mut reader = Reader::new_with_coin_variant(serialized, self.ticker.as_str().into());
let header: BlockHeader =
reader
.read()
.map_err(|e: serialization::Error| BlockHeaderStorageError::DecodeError {
coin: ticker,
coin: self.ticker.clone(),
reason: e.to_string(),
})?;

Expand All @@ -134,53 +134,53 @@ impl BlockHeaderStorageOps for IDBBlockHeadersStorage {
}

async fn get_block_header_raw(&self, height: u64) -> Result<Option<String>, BlockHeaderStorageError> {
let ticker = self.ticker.clone();
let ticker = &self.ticker;
let locked_db = self
.lock_db()
.await
.map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::get_err(ticker, err.to_string()))?;
let db_transaction = locked_db
.get_inner()
.transaction()
.await
.map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::get_err(ticker, err.to_string()))?;
let block_headers_db = db_transaction
.table::<BlockHeaderStorageTable>()
.await
.map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::table_err(ticker, err.to_string()))?;
let index_keys = MultiIndex::new(BlockHeaderStorageTable::TICKER_HEIGHT_INDEX)
.with_value(&ticker)
.map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?
.with_value(ticker)
.map_err(|err| BlockHeaderStorageError::table_err(ticker, err.to_string()))?
.with_value(BeBigUint::from(height))
.map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::table_err(ticker, err.to_string()))?;

Ok(block_headers_db
.get_item_by_unique_multi_index(index_keys)
.await
.map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))?
.map_err(|err| BlockHeaderStorageError::get_err(ticker, err.to_string()))?
.map(|raw| raw.1.raw_header))
}

async fn get_last_block_height(&self) -> Result<Option<u64>, BlockHeaderStorageError> {
let ticker = self.ticker.clone();
let ticker = &self.ticker;
let locked_db = self
.lock_db()
.await
.map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::get_err(ticker, err.to_string()))?;
let db_transaction = locked_db
.get_inner()
.transaction()
.await
.map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::get_err(ticker, err.to_string()))?;
let block_headers_db = db_transaction
.table::<BlockHeaderStorageTable>()
.await
.map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::table_err(ticker, err.to_string()))?;

let maybe_item = block_headers_db
.cursor_builder()
.only("ticker", ticker.clone())
.map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))?
.only("ticker", self.ticker.clone())
.map_err(|err| BlockHeaderStorageError::get_err(ticker, err.to_string()))?
// We need to provide any constraint on the `height` property
// since `ticker_height` consists of both `ticker` and `height` properties.
.bound("height", BeBigUint::from(0u64), BeBigUint::from(u64::MAX))
Expand All @@ -189,16 +189,16 @@ impl BlockHeaderStorageOps for IDBBlockHeadersStorage {
.reverse()
.open_cursor(BlockHeaderStorageTable::TICKER_HEIGHT_INDEX)
.await
.map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))?
.map_err(|err| BlockHeaderStorageError::get_err(ticker, err.to_string()))?
.next()
.await
.map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::get_err(ticker, err.to_string()))?;

maybe_item
.map(|(_, item)| {
item.height
.to_u64()
.ok_or_else(|| BlockHeaderStorageError::get_err(&ticker, "height is too large".to_string()))
.ok_or_else(|| BlockHeaderStorageError::get_err(ticker, "height is too large".to_string()))
})
.transpose()
}
Expand All @@ -207,44 +207,45 @@ impl BlockHeaderStorageOps for IDBBlockHeadersStorage {
&self,
max_bits: u32,
) -> Result<Option<BlockHeader>, BlockHeaderStorageError> {
let ticker = self.ticker.clone();
let ticker = &self.ticker;
let locked_db = self
.lock_db()
.await
.map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::get_err(ticker, err.to_string()))?;
let db_transaction = locked_db
.get_inner()
.transaction()
.await
.map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::get_err(ticker, err.to_string()))?;
let block_headers_db = db_transaction
.table::<BlockHeaderStorageTable>()
.await
.map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::table_err(ticker, err.to_string()))?;

let mut cursor = block_headers_db
let condition = move |block| {
serde_json::from_value::<BlockHeaderStorageTable>(block)
.map_to_mm(|err| CursorError::ErrorDeserializingItem(err.to_string()))
.map(|header| header.bits != max_bits)
};
let maybe_next = block_headers_db
.cursor_builder()
.only("ticker", ticker.clone())
.map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))?
.only("ticker", self.ticker.clone())
.map_err(|err| BlockHeaderStorageError::get_err(ticker, err.to_string()))?
// We need to provide any constraint on the `height` property
// since `ticker_height` consists of both `ticker` and `height` properties.
.bound("height", BeBigUint::from(0u64), BeBigUint::from(u64::MAX))
// Cursor returns values from the lowest to highest key indexes.
// But we need to get the most highest height, so reverse the cursor direction.
.reverse()
.where_(condition)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would happen if there are more than one object/record that satisfies the condition? I see that we return the first object, are we sure we won't get Uncaught Error: closure invoked recursively or after being dropped. This is part of the reason I wanted where and limit, so we can get more than one object that satisfies the condition, if we want this in other cases (other than the case here where we want only the first object).

P.S. If we don't get the error when there are more than one object that satisfies the condition, then the inclusion of limit and refactoring of where to return more than one object if they exist can be done in a different PR since this PR purpose is to fix the error only.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm this seems like another enhancement that's not related to this PR.

This PR only handle case for retrieving the first item that meets a condition(if specified) which solves js exception error: Uncaught Error: closure invoked recursively or after being But for sure this PR indeed fixed that and also optimizes calls to indexeddb.

I will work on the later issue during next print.

.open_cursor(BlockHeaderStorageTable::TICKER_HEIGHT_INDEX)
.await
.map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))?;

while let Some((_item_id, header)) = cursor
.map_err(|err| BlockHeaderStorageError::get_err(ticker, err.to_string()))?
.next()
.await
.map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))?
{
if header.bits == max_bits {
continue;
}
.map_err(|err| BlockHeaderStorageError::get_err(ticker, err.to_string()))?;

if let Some((_item_id, header)) = maybe_next {
let serialized = &hex::decode(header.raw_header).map_err(|e| BlockHeaderStorageError::DecodeError {
coin: ticker.clone(),
reason: e.to_string(),
Expand All @@ -254,7 +255,7 @@ impl BlockHeaderStorageOps for IDBBlockHeadersStorage {
reader
.read()
.map_err(|e: serialization::Error| BlockHeaderStorageError::DecodeError {
coin: ticker,
coin: self.ticker.clone(),
reason: e.to_string(),
})?;

Expand All @@ -265,36 +266,36 @@ impl BlockHeaderStorageOps for IDBBlockHeadersStorage {
}

async fn get_block_height_by_hash(&self, hash: H256) -> Result<Option<i64>, BlockHeaderStorageError> {
let ticker = self.ticker.clone();
let ticker = &self.ticker;
let locked_db = self
.lock_db()
.await
.map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::get_err(ticker, err.to_string()))?;
let db_transaction = locked_db
.get_inner()
.transaction()
.await
.map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::get_err(ticker, err.to_string()))?;
let block_headers_db = db_transaction
.table::<BlockHeaderStorageTable>()
.await
.map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::table_err(ticker, err.to_string()))?;
let index_keys = MultiIndex::new(BlockHeaderStorageTable::HASH_TICKER_INDEX)
.with_value(hash.to_string())
.map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?
.with_value(&ticker)
.map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::table_err(ticker, err.to_string()))?
.with_value(ticker)
.map_err(|err| BlockHeaderStorageError::table_err(ticker, err.to_string()))?;

let maybe_item = block_headers_db
.get_item_by_unique_multi_index(index_keys)
.await
.map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::get_err(ticker, err.to_string()))?;

maybe_item
.map(|(_, item)| {
item.height
.to_i64()
.ok_or_else(|| BlockHeaderStorageError::get_err(&ticker, "height is too large".to_string()))
.ok_or_else(|| BlockHeaderStorageError::get_err(ticker, "height is too large".to_string()))
})
.transpose()
}
Expand All @@ -304,61 +305,61 @@ impl BlockHeaderStorageOps for IDBBlockHeadersStorage {
from_height: u64,
to_height: u64,
) -> Result<(), BlockHeaderStorageError> {
let ticker = self.ticker.clone();
let ticker = &self.ticker;
let locked_db = self
.lock_db()
.await
.map_err(|err| BlockHeaderStorageError::delete_err(&ticker, err.to_string(), from_height, to_height))?;
.map_err(|err| BlockHeaderStorageError::delete_err(ticker, err.to_string(), from_height, to_height))?;
let db_transaction = locked_db
.get_inner()
.transaction()
.await
.map_err(|err| BlockHeaderStorageError::delete_err(&ticker, err.to_string(), from_height, to_height))?;
.map_err(|err| BlockHeaderStorageError::delete_err(ticker, err.to_string(), from_height, to_height))?;
let block_headers_db = db_transaction
.table::<BlockHeaderStorageTable>()
.await
.map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::table_err(ticker, err.to_string()))?;

for height in from_height..=to_height {
let index_keys = MultiIndex::new(BlockHeaderStorageTable::TICKER_HEIGHT_INDEX)
.with_value(&ticker)
.map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?
.with_value(ticker)
.map_err(|err| BlockHeaderStorageError::table_err(ticker, err.to_string()))?
.with_value(BeBigUint::from(height))
.map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::table_err(ticker, err.to_string()))?;

block_headers_db
.delete_item_by_unique_multi_index(index_keys)
.await
.map_err(|err| BlockHeaderStorageError::delete_err(&ticker, err.to_string(), from_height, to_height))?;
.map_err(|err| BlockHeaderStorageError::delete_err(ticker, err.to_string(), from_height, to_height))?;
}

Ok(())
}

async fn is_table_empty(&self) -> Result<(), BlockHeaderStorageError> {
let ticker = self.ticker.clone();
let ticker = &self.ticker;
let locked_db = self
.lock_db()
.await
.map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::table_err(ticker, err.to_string()))?;
let db_transaction = locked_db
.get_inner()
.transaction()
.await
.map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::table_err(ticker, err.to_string()))?;
let block_headers_db = db_transaction
.table::<BlockHeaderStorageTable>()
.await
.map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::table_err(ticker, err.to_string()))?;

let items = block_headers_db
.get_items("ticker", ticker.clone())
.await
.map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?;
.map_err(|err| BlockHeaderStorageError::table_err(ticker, err.to_string()))?;

if !items.is_empty() {
return Err(BlockHeaderStorageError::table_err(
&ticker,
ticker,
"Table is not empty".to_string(),
));
};
Expand Down
Loading
Loading