mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat(interface): implicit trait bound for DB cursors (#122)
* feat(interface): implicit trait bound for DB cursors * test cursor * walking fixed for RO * impl for Walker for DupCursor
This commit is contained in:
@ -1,5 +1,7 @@
|
||||
//! Cursor wrapper for libmdbx-sys.
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::utils::*;
|
||||
use libmdbx::{self, TransactionKind, WriteFlags, RO, RW};
|
||||
use reth_interfaces::db::{
|
||||
@ -67,14 +69,20 @@ impl<'tx, K: TransactionKind, T: Table> DbCursorRO<'tx, T> for Cursor<'tx, K, T>
|
||||
decode!(self.inner.get_current())
|
||||
}
|
||||
|
||||
fn walk(&'tx mut self, start_key: <T as Table>::Key) -> Result<Walker<'tx, T>, Error> {
|
||||
fn walk<'cursor>(
|
||||
&'cursor mut self,
|
||||
start_key: T::Key,
|
||||
) -> Result<Walker<'cursor, 'tx, T, Self>, Error>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let start = self
|
||||
.inner
|
||||
.set_range(start_key.encode().as_ref())
|
||||
.map_err(|e| Error::Internal(e.into()))?
|
||||
.map(decoder::<T>);
|
||||
|
||||
Ok(Walker::<'tx, T> { cursor: self, start })
|
||||
Ok(Walker::<'cursor, 'tx, T, Self> { cursor: self, start, _tx_phantom: PhantomData {} })
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,14 +107,18 @@ impl<'tx, K: TransactionKind, T: DupSort> DbDupCursorRO<'tx, T> for Cursor<'tx,
|
||||
}
|
||||
|
||||
/// Returns an iterator starting at a key greater or equal than `start_key` of a DUPSORT table.
|
||||
fn walk_dup(&'tx mut self, key: T::Key, subkey: T::SubKey) -> Result<DupWalker<'tx, T>, Error> {
|
||||
fn walk_dup<'cursor>(
|
||||
&'cursor mut self,
|
||||
key: T::Key,
|
||||
subkey: T::SubKey,
|
||||
) -> Result<DupWalker<'cursor, 'tx, T, Self>, Error> {
|
||||
let start = self
|
||||
.inner
|
||||
.get_both_range(key.encode().as_ref(), subkey.encode().as_ref())
|
||||
.map_err(|e| Error::Internal(e.into()))?
|
||||
.map(decode_one::<T>);
|
||||
|
||||
Ok(DupWalker::<'tx, T> { cursor: self, start })
|
||||
Ok(DupWalker::<'cursor, 'tx, T, Self> { cursor: self, start, _tx_phantom: PhantomData {} })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -137,7 +137,7 @@ mod tests {
|
||||
use libmdbx::{NoWriteMap, WriteMap};
|
||||
use reth_interfaces::db::{
|
||||
tables::{Headers, PlainState},
|
||||
Database, DbTx, DbTxMut,
|
||||
Database, DbCursorRO, DbTx, DbTxMut,
|
||||
};
|
||||
use reth_primitives::{Account, Address, Header, H256, U256};
|
||||
use std::str::FromStr;
|
||||
@ -175,6 +175,31 @@ mod tests {
|
||||
tx.commit().expect(ERROR_COMMIT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn db_cursor_walk() {
|
||||
let env = test_utils::create_test_db::<NoWriteMap>(EnvKind::RW);
|
||||
|
||||
let value = Header::default();
|
||||
let key = (1u64, H256::zero());
|
||||
|
||||
// PUT
|
||||
let tx = env.tx_mut().expect(ERROR_INIT_TX);
|
||||
tx.put::<Headers>(key.into(), value.clone()).expect(ERROR_PUT);
|
||||
tx.commit().expect(ERROR_COMMIT);
|
||||
|
||||
// Cursor
|
||||
let tx = env.tx().expect(ERROR_INIT_TX);
|
||||
let mut cursor = tx.cursor::<Headers>().unwrap();
|
||||
|
||||
let first = cursor.first().unwrap();
|
||||
assert!(first.is_some(), "First should be our put");
|
||||
|
||||
// Walk
|
||||
let walk = cursor.walk(key.into()).unwrap();
|
||||
let first = walk.into_iter().next().unwrap().unwrap();
|
||||
assert_eq!(first.1, value, "First next should be put value");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn db_closure_put_get() {
|
||||
let path = TempDir::new().expect(test_utils::ERROR_TEMPDIR).into_path();
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
use crate::{kv::cursor::Cursor, utils::decode_one};
|
||||
use libmdbx::{EnvironmentKind, Transaction, TransactionKind, WriteFlags, RW};
|
||||
use reth_interfaces::db::{DbTx, DbTxMut, DupSort, Encode, Error, Table};
|
||||
use reth_interfaces::db::{DbTx, DbTxGAT, DbTxMut, DbTxMutGAT, DupSort, Encode, Error, Table};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Wrapper for the libmdbx transaction.
|
||||
@ -39,18 +39,24 @@ impl<'env, K: TransactionKind, E: EnvironmentKind> Tx<'env, K, E> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env, K: TransactionKind, E: EnvironmentKind> DbTx<'env> for Tx<'env, K, E> {
|
||||
/// Cursor GAT
|
||||
type Cursor<T: Table> = Cursor<'env, K, T>;
|
||||
/// DupCursor GAT
|
||||
type DupCursor<T: DupSort> = Cursor<'env, K, T>;
|
||||
/// Iterate over read only values in database.
|
||||
fn cursor<T: Table>(&self) -> Result<Self::Cursor<T>, Error> {
|
||||
impl<'a, K: TransactionKind, E: EnvironmentKind> DbTxGAT<'a> for Tx<'_, K, E> {
|
||||
type Cursor<T: Table> = Cursor<'a, K, T>;
|
||||
type DupCursor<T: DupSort> = Cursor<'a, K, T>;
|
||||
}
|
||||
|
||||
impl<'a, K: TransactionKind, E: EnvironmentKind> DbTxMutGAT<'a> for Tx<'_, K, E> {
|
||||
type CursorMut<T: Table> = Cursor<'a, RW, T>;
|
||||
type DupCursorMut<T: DupSort> = Cursor<'a, RW, T>;
|
||||
}
|
||||
|
||||
impl<'tx, K: TransactionKind, E: EnvironmentKind> DbTx<'tx> for Tx<'tx, K, E> {
|
||||
// Iterate over read only values in database.
|
||||
fn cursor<T: Table>(&'tx self) -> Result<<Self as DbTxGAT<'tx>>::Cursor<T>, Error> {
|
||||
self.new_cursor()
|
||||
}
|
||||
|
||||
/// Iterate over read only values in database.
|
||||
fn cursor_dup<T: DupSort>(&self) -> Result<Self::DupCursor<T>, Error> {
|
||||
fn cursor_dup<T: DupSort>(&'tx self) -> Result<<Self as DbTxGAT<'tx>>::DupCursor<T>, Error> {
|
||||
self.new_cursor()
|
||||
}
|
||||
|
||||
@ -70,11 +76,7 @@ impl<'env, K: TransactionKind, E: EnvironmentKind> DbTx<'env> for Tx<'env, K, E>
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env, E: EnvironmentKind> DbTxMut<'env> for Tx<'env, RW, E> {
|
||||
type CursorMut<T: Table> = Cursor<'env, RW, T>;
|
||||
|
||||
type DupCursorMut<T: DupSort> = Cursor<'env, RW, T>;
|
||||
|
||||
impl<E: EnvironmentKind> DbTxMut<'_> for Tx<'_, RW, E> {
|
||||
fn put<T: Table>(&self, key: T::Key, value: T::Value) -> Result<(), Error> {
|
||||
self.inner
|
||||
.put(
|
||||
@ -111,11 +113,13 @@ impl<'env, E: EnvironmentKind> DbTxMut<'env> for Tx<'env, RW, E> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cursor_mut<T: Table>(&self) -> Result<Self::CursorMut<T>, Error> {
|
||||
fn cursor_mut<T: Table>(&self) -> Result<<Self as DbTxMutGAT<'_>>::CursorMut<T>, Error> {
|
||||
self.new_cursor()
|
||||
}
|
||||
|
||||
fn cursor_dup_mut<T: DupSort>(&self) -> Result<Self::DupCursorMut<T>, Error> {
|
||||
fn cursor_dup_mut<T: DupSort>(
|
||||
&self,
|
||||
) -> Result<<Self as DbTxMutGAT<'_>>::DupCursorMut<T>, Error> {
|
||||
self.new_cursor()
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use super::{
|
||||
Database, DatabaseGAT, DbCursorRO, DbCursorRW, DbDupCursorRO, DbDupCursorRW, DbTx, DbTxMut,
|
||||
DupSort, Table,
|
||||
Database, DatabaseGAT, DbCursorRO, DbCursorRW, DbDupCursorRO, DbDupCursorRW, DbTx, DbTxGAT,
|
||||
DbTxMut, DbTxMutGAT, DupSort, DupWalker, Error, Table, Walker,
|
||||
};
|
||||
|
||||
/// Mock database used for testing with inner BTreeMap structure
|
||||
@ -37,11 +37,17 @@ pub struct TxMock {
|
||||
_table: BTreeMap<Vec<u8>, Vec<u8>>,
|
||||
}
|
||||
|
||||
impl<'a> DbTxGAT<'a> for TxMock {
|
||||
type Cursor<T: Table> = CursorMock;
|
||||
type DupCursor<T: DupSort> = CursorMock;
|
||||
}
|
||||
|
||||
impl<'a> DbTxMutGAT<'a> for TxMock {
|
||||
type CursorMut<T: Table> = CursorMock;
|
||||
type DupCursorMut<T: DupSort> = CursorMock;
|
||||
}
|
||||
|
||||
impl<'a> DbTx<'a> for TxMock {
|
||||
type Cursor<T: super::Table> = CursorMock;
|
||||
|
||||
type DupCursor<T: super::DupSort> = CursorMock;
|
||||
|
||||
fn get<T: super::Table>(&self, _key: T::Key) -> Result<Option<T::Value>, super::Error> {
|
||||
todo!()
|
||||
}
|
||||
@ -50,20 +56,18 @@ impl<'a> DbTx<'a> for TxMock {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn cursor<T: super::Table>(&self) -> Result<Self::Cursor<T>, super::Error> {
|
||||
fn cursor<T: super::Table>(&self) -> Result<<Self as DbTxGAT<'_>>::Cursor<T>, super::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn cursor_dup<T: super::DupSort>(&self) -> Result<Self::DupCursor<T>, super::Error> {
|
||||
fn cursor_dup<T: super::DupSort>(
|
||||
&self,
|
||||
) -> Result<<Self as DbTxGAT<'_>>::DupCursor<T>, super::Error> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DbTxMut<'a> for TxMock {
|
||||
type CursorMut<T: super::Table> = CursorMock;
|
||||
|
||||
type DupCursorMut<T: super::DupSort> = CursorMock;
|
||||
|
||||
fn put<T: super::Table>(&self, _key: T::Key, _value: T::Value) -> Result<(), super::Error> {
|
||||
todo!()
|
||||
}
|
||||
@ -76,11 +80,15 @@ impl<'a> DbTxMut<'a> for TxMock {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn cursor_mut<T: super::Table>(&self) -> Result<Self::CursorMut<T>, super::Error> {
|
||||
fn cursor_mut<T: super::Table>(
|
||||
&self,
|
||||
) -> Result<<Self as DbTxMutGAT<'_>>::CursorMut<T>, super::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn cursor_dup_mut<T: super::DupSort>(&self) -> Result<Self::DupCursorMut<T>, super::Error> {
|
||||
fn cursor_dup_mut<T: super::DupSort>(
|
||||
&self,
|
||||
) -> Result<<Self as DbTxMutGAT<'_>>::DupCursorMut<T>, super::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
@ -123,7 +131,13 @@ impl<'tx, T: Table> DbCursorRO<'tx, T> for CursorMock {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn walk(&'tx mut self, _start_key: T::Key) -> Result<super::Walker<'tx, T>, super::Error> {
|
||||
fn walk<'cursor>(
|
||||
&'cursor mut self,
|
||||
_start_key: T::Key,
|
||||
) -> Result<Walker<'cursor, 'tx, T, Self>, Error>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@ -141,11 +155,14 @@ impl<'tx, T: DupSort> DbDupCursorRO<'tx, T> for CursorMock {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn walk_dup(
|
||||
&'tx mut self,
|
||||
fn walk_dup<'cursor>(
|
||||
&'cursor mut self,
|
||||
_key: <T>::Key,
|
||||
_subkey: <T as DupSort>::SubKey,
|
||||
) -> Result<super::DupWalker<'tx, T>, super::Error> {
|
||||
) -> Result<DupWalker<'cursor, 'tx, T, Self>, Error>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@ pub mod models;
|
||||
mod table;
|
||||
pub mod tables;
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub use error::Error;
|
||||
pub use table::*;
|
||||
|
||||
@ -67,25 +69,22 @@ pub trait Database: for<'a> DatabaseGAT<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Read only transaction
|
||||
pub trait DbTx<'a> {
|
||||
/// Implements the GAT method from:
|
||||
/// https://sabrinajewson.org/blog/the-better-alternative-to-lifetime-gats#the-better-gats.
|
||||
///
|
||||
/// Sealed trait which cannot be implemented by 3rd parties, exposed only for implementers
|
||||
pub trait DbTxGAT<'a, __ImplicitBounds: Sealed = Bounds<&'a Self>>: Send + Sync {
|
||||
/// Cursor GAT
|
||||
type Cursor<T: Table>: DbCursorRO<'a, T>;
|
||||
/// DupCursor GAT
|
||||
type DupCursor<T: DupSort>: DbDupCursorRO<'a, T> + DbCursorRO<'a, T>;
|
||||
/// Get value
|
||||
fn get<T: Table>(&self, key: T::Key) -> Result<Option<T::Value>, Error>;
|
||||
/// Commit for read only transaction will consume and free transaction and allows
|
||||
/// freeing of memory pages
|
||||
fn commit(self) -> Result<bool, Error>;
|
||||
/// Iterate over read only values in table.
|
||||
fn cursor<T: Table>(&self) -> Result<Self::Cursor<T>, Error>;
|
||||
/// Iterate over read only values in dup sorted table.
|
||||
fn cursor_dup<T: DupSort>(&self) -> Result<Self::DupCursor<T>, Error>;
|
||||
}
|
||||
|
||||
/// Read write transaction that allows writing to database
|
||||
pub trait DbTxMut<'a> {
|
||||
/// Implements the GAT method from:
|
||||
/// https://sabrinajewson.org/blog/the-better-alternative-to-lifetime-gats#the-better-gats.
|
||||
///
|
||||
/// Sealed trait which cannot be implemented by 3rd parties, exposed only for implementers
|
||||
pub trait DbTxMutGAT<'a, __ImplicitBounds: Sealed = Bounds<&'a Self>>: Send + Sync {
|
||||
/// Cursor GAT
|
||||
type CursorMut<T: Table>: DbCursorRW<'a, T> + DbCursorRO<'a, T>;
|
||||
/// DupCursor GAT
|
||||
@ -93,6 +92,23 @@ pub trait DbTxMut<'a> {
|
||||
+ DbCursorRW<'a, T>
|
||||
+ DbDupCursorRO<'a, T>
|
||||
+ DbCursorRO<'a, T>;
|
||||
}
|
||||
|
||||
/// Read only transaction
|
||||
pub trait DbTx<'tx>: for<'a> DbTxGAT<'a> {
|
||||
/// Get value
|
||||
fn get<T: Table>(&self, key: T::Key) -> Result<Option<T::Value>, Error>;
|
||||
/// Commit for read only transaction will consume and free transaction and allows
|
||||
/// freeing of memory pages
|
||||
fn commit(self) -> Result<bool, Error>;
|
||||
/// Iterate over read only values in table.
|
||||
fn cursor<T: Table>(&'tx self) -> Result<<Self as DbTxGAT<'tx>>::Cursor<T>, Error>;
|
||||
/// Iterate over read only values in dup sorted table.
|
||||
fn cursor_dup<T: DupSort>(&'tx self) -> Result<<Self as DbTxGAT<'tx>>::DupCursor<T>, Error>;
|
||||
}
|
||||
|
||||
/// Read write transaction that allows writing to database
|
||||
pub trait DbTxMut<'tx>: for<'a> DbTxMutGAT<'a> {
|
||||
/// Put value to database
|
||||
fn put<T: Table>(&self, key: T::Key, value: T::Value) -> Result<(), Error>;
|
||||
/// Delete value from database
|
||||
@ -100,9 +116,11 @@ pub trait DbTxMut<'a> {
|
||||
/// Clears database.
|
||||
fn clear<T: Table>(&self) -> Result<(), Error>;
|
||||
/// Cursor mut
|
||||
fn cursor_mut<T: Table>(&self) -> Result<Self::CursorMut<T>, Error>;
|
||||
fn cursor_mut<T: Table>(&self) -> Result<<Self as DbTxMutGAT<'tx>>::CursorMut<T>, Error>;
|
||||
/// DupCursor mut.
|
||||
fn cursor_dup_mut<T: DupSort>(&self) -> Result<Self::DupCursorMut<T>, Error>;
|
||||
fn cursor_dup_mut<T: DupSort>(
|
||||
&self,
|
||||
) -> Result<<Self as DbTxMutGAT<'tx>>::DupCursorMut<T>, Error>;
|
||||
}
|
||||
|
||||
/// Alias type for a `(key, value)` result coming from a cursor.
|
||||
@ -136,8 +154,18 @@ pub trait DbCursorRO<'tx, T: Table> {
|
||||
/// Returns the current `(key, value)` pair of the cursor.
|
||||
fn current(&mut self) -> PairResult<T>;
|
||||
|
||||
/// Inner
|
||||
fn inner(&'tx mut self) -> &'tx mut Self {
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns an iterator starting at a key greater or equal than `start_key`.
|
||||
fn walk(&'tx mut self, start_key: T::Key) -> Result<Walker<'tx, T>, Error>;
|
||||
fn walk<'cursor>(
|
||||
&'cursor mut self,
|
||||
start_key: T::Key,
|
||||
) -> Result<Walker<'cursor, 'tx, T, Self>, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
/// Read only curor over DupSort table.
|
||||
@ -153,7 +181,13 @@ pub trait DbDupCursorRO<'tx, T: DupSort> {
|
||||
|
||||
/// Returns an iterator starting at a key greater or equal than `start_key` of a DUPSORT
|
||||
/// table.
|
||||
fn walk_dup(&'tx mut self, key: T::Key, subkey: T::SubKey) -> Result<DupWalker<'tx, T>, Error>;
|
||||
fn walk_dup<'cursor>(
|
||||
&'cursor mut self,
|
||||
key: T::Key,
|
||||
subkey: T::SubKey,
|
||||
) -> Result<DupWalker<'cursor, 'tx, T, Self>, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
/// Read write cursor over table.
|
||||
@ -178,14 +212,18 @@ pub trait DbDupCursorRW<'tx, T: DupSort> {
|
||||
}
|
||||
|
||||
/// Provides an iterator to `Cursor` when handling `Table`.
|
||||
pub struct Walker<'cursor, T: Table> {
|
||||
pub struct Walker<'cursor, 'tx, T: Table, CURSOR: DbCursorRO<'tx, T> + Sized> {
|
||||
/// Cursor to be used to walk through the table.
|
||||
pub cursor: &'cursor mut dyn DbCursorRO<'cursor, T>,
|
||||
pub cursor: &'cursor mut CURSOR,
|
||||
/// `(key, value)` where to start the walk.
|
||||
pub start: IterPairResult<T>,
|
||||
/// Phantom data for 'tx.
|
||||
pub _tx_phantom: PhantomData<&'tx T>,
|
||||
}
|
||||
|
||||
impl<'cursor, T: Table> std::iter::Iterator for Walker<'cursor, T> {
|
||||
impl<'cursor, 'tx, T: Table, CURSOR: DbCursorRO<'tx, T> + Sized> std::iter::Iterator
|
||||
for Walker<'cursor, 'tx, T, CURSOR>
|
||||
{
|
||||
type Item = Result<(T::Key, T::Value), Error>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let start = self.start.take();
|
||||
@ -198,14 +236,18 @@ impl<'cursor, T: Table> std::iter::Iterator for Walker<'cursor, T> {
|
||||
}
|
||||
|
||||
/// Provides an iterator to `Cursor` when handling a `DupSort` table.
|
||||
pub struct DupWalker<'cursor, T: DupSort> {
|
||||
pub struct DupWalker<'cursor, 'tx, T: DupSort, CURSOR: DbDupCursorRO<'tx, T> + Sized> {
|
||||
/// Cursor to be used to walk through the table.
|
||||
pub cursor: &'cursor mut dyn DbDupCursorRO<'cursor, T>,
|
||||
pub cursor: &'cursor mut CURSOR,
|
||||
/// Value where to start the walk.
|
||||
pub start: Option<Result<T::Value, Error>>,
|
||||
/// Phantom data for 'tx.
|
||||
pub _tx_phantom: PhantomData<&'tx T>,
|
||||
}
|
||||
|
||||
impl<'cursor, T: DupSort> std::iter::Iterator for DupWalker<'cursor, T> {
|
||||
impl<'cursor, 'tx, T: DupSort, CURSOR: DbDupCursorRO<'tx, T> + Sized> std::iter::Iterator
|
||||
for DupWalker<'cursor, 'tx, T, CURSOR>
|
||||
{
|
||||
type Item = Result<T::Value, Error>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let start = self.start.take();
|
||||
|
||||
@ -21,7 +21,7 @@ pub type HeaderHash = H256;
|
||||
/// element as BlockNumber, helps out with querying/sorting.
|
||||
///
|
||||
/// Since it's used as a key, the `BlockNumber` is not compressed when encoding it.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct BlockNumHash((BlockNumber, BlockHash));
|
||||
|
||||
@ -58,7 +58,9 @@ impl Decode for BlockNumHash {
|
||||
let value: bytes::Bytes = value.into();
|
||||
|
||||
let num = u64::from_be_bytes(
|
||||
value.as_ref().try_into().map_err(|_| Error::Decode(eyre!("Into bytes error.")))?,
|
||||
value.as_ref()[..8]
|
||||
.try_into()
|
||||
.map_err(|_| Error::Decode(eyre!("Into bytes error.")))?,
|
||||
);
|
||||
let hash = H256::decode(value.slice(8..))?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user