-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
Move storage traits implementation to the fuel-core-storage
crate
#1576
Move storage traits implementation to the fuel-core-storage
crate
#1576
Conversation
…ion-to-own-crate' into feature/move-storage-implementation-to-own-crate
($($ty:ty, $size:expr),*) => { | ||
$( | ||
impl Decode<$ty> for Primitive<{ $size }> { | ||
fn decode(bytes: &[u8]) -> anyhow::Result<$ty> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should use dedicated encoding errors instead of anyhow?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe the problem is that I don't know what the enum will look like=D We have only two errors right now: postcard and TryFromSliceError
.
The idea is to make the Decode
trait as generic as possible and it means something like:
enum DecodeError {
TryFromSliceError(TryFromSliceError),
Other(anyhow::Error)
}
And I decided to go simply with anyhow::Error
.
|
||
/// The key convertor used to convert the key from the `ContractsState` table | ||
/// to the key of the `ContractsStateMerkleMetadata` table. | ||
pub struct KeyConvertor; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pub struct KeyConvertor; | |
pub struct KeyConverter; |
crates/storage/src/lib.rs
Outdated
/// Some implementations can perform batch operations faster than one by one. | ||
pub trait StorageBatchMutate<Type: Mappable>: StorageMutate<Type> { | ||
/// Initialize the storage with batch insertion. This method is more performant than | ||
/// [`Self::insert_batch`] in some case. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// [`Self::insert_batch`] in some case. | |
/// [`Self::insert_batch`] in some cases. |
/// for the `Database`. Not all default implementations of the `StructuredStorage` are suitable | ||
/// for the `Database`. Sometimes we want to override some of them and add a custom implementation | ||
/// with additional logic. For example, we want to override the `StorageMutate` trait for the `Messages` | ||
/// table to also track the owner of messages. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this not apply anymore once we separate executor logic from other kinds of data?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Creates the foreign column ensuring that the id and name | ||
/// are not already used by the [`Column`] required tables. | ||
pub fn new(id: u32, name: &'static str) -> anyhow::Result<Self> { | ||
for column in enum_iterator::all::<Column>() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a check we should do at compile time (i.e. const eval) rather than every time a ForeignColumn is instantiated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is impossible to do during compile time because the id
can be known only during execution. We could try to add support for const fn new
, but it doesn't worth it. We don't use ForeignColumn
for our logic. It exists for anyone who decides to implement something on top of us=)
But I think in the follow-up PRs(after extractions of off-chain logic from the executor), maybe we will even remove that variant.
…-crate # Conflicts: # CHANGELOG.md # crates/fuel-core/src/database.rs # crates/fuel-core/src/database/storage.rs # crates/storage/src/tables.rs
…ion-to-own-crate' into feature/move-storage-implementation-to-own-crate
…-crate # Conflicts: # CHANGELOG.md # crates/fuel-core/src/database.rs # crates/fuel-core/src/database/storage.rs
crates/storage/src/structure.rs
Outdated
fn init( | ||
storage: &mut S, | ||
column: S::Column, | ||
set: &mut dyn Iterator<Item = (&M::Key, &M::Value)>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer a generic/impl
here over type erasure. Is there a specific reason we want dyn
over having some concrete type at compile time?
|
||
impl DatabaseColumn for Transactions { | ||
/// Teh tables allows to iterate over all transactions owned by an address. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Teh tables allows to iterate over all transactions owned by an address. | |
/// These tables allow iteration over all transactions owned by an address. |
Decode, | ||
Encode, | ||
}; | ||
use fuel_core_types::fuel_vm::ContractsAssetKey; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use fuel_core_types::fuel_vm::ContractsAssetKey; | |
use fuel_core_types::fuel_vm::{ | |
ContractsAssetKey, | |
ContractsStateKey, | |
}; |
/// The codec is used for types that are already represented by bytes. | ||
pub struct Raw; | ||
|
||
impl<K> Encode<K> for Raw |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Maybe use a consistent name for generics for Encode/Decode since K
/V
implies Key
and Value
impl Mappable for OwnedMessageIds { | ||
type Key = OwnedMessageKey; | ||
type OwnedKey = Self::Key; | ||
type Value = (); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type of value written to the database is ()
- Is this right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added additional test to prove all possible use cases
Closes #1549 ## Overview The change extracts the off-chain-related logic from the executor and moves it to the GraphQL off-chain worker. It creates two new concepts - Off-chain and On-chain databases where the GraphQL worker has exclusive ownership of the database and may modify it without intersecting with the On-chain database. ## Challenges caused by the change Delegating updating of the state to something other than `BlockImporter` causes several new problems: - The commitment to the on-chain and off-chain databases is done in different places. The off-chain database may be out of sync with the on-chain database due to race conditions. - The result of the block execution(receipts, statuses) is not stored anywhere and may be lost due to emergency shutdown. We don't want to duplicate on-chain data inside of the off-chain database, so the GraphQL service works with two sources of data, which leads to two problems: - The off-chain database may be out of sync with the on-chain database due to race conditions causing failing requests. - The view of the databases during the GraphQL request may change, causing invalid responses with a mix of old and new data. We had this problem before, but now it is more critical. ## Solutions to the challenges ### Out of sync The change applies two steps to solve this issue. The main one is a new trait for the database: ```rust /// Provides a view of the storage at the given height. /// It guarantees to be atomic, meaning the view is immutable to outside modifications. pub trait AtomicView<View>: Send + Sync { /// Returns the view of the storage at the given `height`. fn view_at(&self, height: BlockHeight) -> StorageResult<View>; /// Returns the view of the storage for the latest block height. fn latest_view(&self) -> View; } ``` Another one to await on the `BlockCommiter` side finishing processing the `ImportResult` by all listeners. The goal of the trait is to provide an immutable read-only view of the database at a specific time. However, this trait has not yet been implemented properly during this PR and will be implemented in the following PRs. The `view_at` requires functionality from #451. We already can implement the `latest_view` method via [`RocksDB::Transaction`](https://github.com/facebook/rocksdb/wiki/Transactions#reading-from-a-transaction), but it is better to do it after merging #1576. Waiting on the `BlockImporter` side is a temporary solution to not escalate the problem. But maybe we can keep it later to guarantee the consistent state of the blockchain. ### Losing result of execution The `AtomicView` trait also solves the issue of losing the state of the execution because it is possible to get a view of the database at a specific block height and execute the block again receiving the same execution result. Waiting inside the `BlockImporter` guarantees that we will not lose more than one `ImportResult`. ### Inconsistent database view within GraphQL requests The GraphQL now has `ReadDatabase`: ```rust pub type OnChainView = Arc<dyn OnChainDatabase>; pub type OffChainView = Arc<dyn OffChainDatabase>; pub struct ReadDatabase { on_chain: Box<dyn AtomicView<OnChainView>>, off_chain: Box<dyn AtomicView<OffChainView>>, } ``` It implements the `view` method that returns the `ReadView` type. The `ReadView` implements all required methods by using internal on-chain view and off-chain view. The `AtomicView` allows us to get the `last_view` of the off-chain database and get the `view_at(off_chain_view.last_height())` of the on-chain database creating a consistent view for both databases at a specific height. The change also adds a `ViewExtension` to the GraphQL that creates a `ReadView` for each request. ```rust /// The extension that adds the `ReadView` to the request context. /// It guarantees that the request works with the one view of the database, /// and external database modification cannot affect the result. struct ViewExtension; #[async_trait::async_trait] impl Extension for ViewExtension { async fn prepare_request( &self, ctx: &ExtensionContext<'_>, request: Request, next: NextPrepareRequest<'_>, ) -> ServerResult<Request> { let database: &ReadDatabase = ctx.data_unchecked(); let view = database.view(); let request = request.data(view); next.run(ctx, request).await } } ``` ## Implementation details - The `ExecutionResult` now also has receipts for the transaction along with its status. The off-chain worker will insert them later into the database, while the `dry_run` can fetch them immediately. - All API requests now work with the `ReadView` instead of the `Database` type. The `ReadDatabase` is only used in one place in the `ViewExtension`. - The `BlockImpoerter::comit_result` now is `async` and awaits for the previous block to be processed by all listeners. The execution of the `execute_and_commit` now runs `verify_and_execute_block` in the spawned task in the `tokio_rayon`. ## Follow up - #1580 - #1581 - #1582 - #1583 - #1584
Applying comments from the #1576
## Version v0.23.0 ### Added - [#1713](#1713): Added automatic `impl` of traits `StorageWrite` and `StorageRead` for `StructuredStorage`. Tables that use a `Blueprint` can be read and written using these interfaces provided by structured storage types. - [#1671](#1671): Added a new `Merklized` blueprint that maintains the binary Merkle tree over the storage data. It supports only the insertion of the objects without removing them. - [#1657](#1657): Moved `ContractsInfo` table from `fuel-vm` to on-chain tables, and created version-able `ContractsInfoType` to act as the table's data type. ### Changed - [#1723](#1723): Notify about imported blocks from the off-chain worker. - [#1717](#1717): The fix for the [#1657](#1657) to include the contract into `ContractsInfo` table. - [#1657](#1657): Upgrade to `fuel-vm` 0.46.0. - [#1671](#1671): The logic related to the `FuelBlockIdsToHeights` is moved to the off-chain worker. - [#1663](#1663): Reduce the punishment criteria for mempool gossipping. - [#1658](#1658): Removed `Receipts` table. Instead, receipts are part of the `TransactionStatuses` table. - [#1640](#1640): Upgrade to fuel-vm 0.45.0. - [#1635](#1635): Move updating of the owned messages and coins to off-chain worker. - [#1650](#1650): Add api endpoint for getting estimates for future gas prices - [#1649](#1649): Add api endpoint for getting latest gas price - [#1600](#1640): Upgrade to fuel-vm 0.45.0 - [#1633](#1633): Notify services about importing of the genesis block. - [#1625](#1625): Making relayer independent from the executor and preparation for the force transaction inclusion. - [#1613](#1613): Add api endpoint to retrieve a message by its nonce. - [#1612](#1612): Use `AtomicView` in all services for consistent results. - [#1597](#1597): Unify namespacing for `libp2p` modules - [#1591](#1591): Simplify libp2p dependencies and not depend on all sub modules directly. - [#1590](#1590): Use `AtomicView` in the `TxPool` to read the state of the database during insertion of the transactions. - [#1587](#1587): Use `BlockHeight` as a primary key for the `FuelsBlock` table. - [#1585](#1585): Let `NetworkBehaviour` macro generate `FuelBehaviorEvent` in p2p - [#1579](#1579): The change extracts the off-chain-related logic from the executor and moves it to the GraphQL off-chain worker. It creates two new concepts - Off-chain and On-chain databases where the GraphQL worker has exclusive ownership of the database and may modify it without intersecting with the On-chain database. - [#1577](#1577): Moved insertion of sealed blocks into the `BlockImporter` instead of the executor. - [#1574](#1574): Penalizes peers for sending invalid responses or for not replying at all. - [#1601](#1601): Fix formatting in docs and check that `cargo doc` passes in the CI. - [#1636](#1636): Add more docs to GraphQL DAP API. #### Breaking - [#1725](#1725): All API endpoints now are prefixed with `/v1` version. New usage looks like: `/v1/playground`, `/v1/graphql`, `/v1/graphql-sub`, `/v1/metrics`, `/v1/health`. - [#1722](#1722): Bugfix: Zero `predicate_gas_used` field during validation of the produced block. - [#1714](#1714): The change bumps the `fuel-vm` to `0.47.1`. It breaks several breaking changes into the protocol: - All malleable fields are zero during the execution and unavailable through the GTF getters. Accessing them via the memory directly is still possible, but they are zero. - The `Transaction` doesn't define the gas price anymore. The gas price is defined by the block producer and recorded in the `Mint` transaction at the end of the block. A price of future blocks can be fetched through a [new API nedopoint](#1641) and the price of the last block can be fetch or via the block or another [API endpoint](#1647). - The `GasPrice` policy is replaced with the `Tip` policy. The user may specify in the native tokens how much he wants to pay the block producer to include his transaction in the block. It is the prioritization mechanism to incentivize the block producer to include users transactions earlier. - The `MaxFee` policy is mandatory to set. Without it, the transaction pool will reject the transaction. Since the block producer defines the gas price, the only way to control how much user agreed to pay can be done only through this policy. - The `maturity` field is removed from the `Input::Coin`. The same affect can be achieve with the `Maturity` policy on the transaction and predicate. This changes breaks how input coin is created and removes the passing of this argument. - The metadata of the `Checked<Tx>` doesn't contain `max_fee` and `min_fee` anymore. Only `max_gas` and `min_gas`. The `max_fee` is controlled by the user via the `MaxFee` policy. - Added automatic `impl` of traits `StorageWrite` and `StorageRead` for `StructuredStorage`. Tables that use a `Blueprint` can be read and written using these interfaces provided by structured storage types. - [#1712](#1712): Make `ContractUtxoInfo` type a version-able enum for use in the `ContractsLatestUtxo`table. - [#1657](#1657): Changed `CROO` gas price type from `Word` to `DependentGasPrice`. The dependent gas price values are dummy values while awaiting updated benchmarks. - [#1671](#1671): The GraphQL API uses block height instead of the block id where it is possible. The transaction status contains `block_height` instead of the `block_id`. - [#1675](#1675): Simplify GQL schema by disabling contract resolvers in most cases, and just return a ContractId scalar instead. - [#1658](#1658): Receipts are part of the transaction status. Removed `reason` from the `TransactionExecutionResult::Failed`. It can be calculated based on the program state and receipts. Also, it is not possible to fetch `receipts` from the `Transaction` directly anymore. Instead, you need to fetch `status` and its receipts. - [#1646](#1646): Remove redundant receipts from queries. - [#1639](#1639): Make Merkle metadata, i.e. `SparseMerkleMetadata` and `DenseMerkleMetadata` type version-able enums - [#1632](#1632): Make `Message` type a version-able enum - [#1631](#1631): Modify api endpoint to dry run multiple transactions. - [#1629](#1629): Use a separate database for each data domain. Each database has its own folder where data is stored. - [#1628](#1628): Make `CompressedCoin` type a version-able enum - [#1616](#1616): Make `BlockHeader` type a version-able enum - [#1614](#1614): Use the default consensus key regardless of trigger mode. The change is breaking because it removes the `--dev-keys` argument. If the `debug` flag is set, the default consensus key will be used, regardless of the trigger mode. - [#1596](#1596): Make `Consensus` type a version-able enum - [#1593](#1593): Make `Block` type a version-able enum - [#1576](#1576): The change moves the implementation of the storage traits for required tables from `fuel-core` to `fuel-core-storage` crate. The change also adds a more flexible configuration of the encoding/decoding per the table and allows the implementation of specific behaviors for the table in a much easier way. It unifies the encoding between database, SMTs, and iteration, preventing mismatching bytes representation on the Rust type system level. Plus, it increases the re-usage of the code by applying the same blueprint to other tables. It is a breaking PR because it changes database encoding/decoding for some tables. ### StructuredStorage The change adds a new type `StructuredStorage`. It is a wrapper around the key-value storage that implements the storage traits(`StorageInspect`, `StorageMutate`, `StorageRead`, etc) for the tables with blueprint. This blueprint works in tandem with the `TableWithBlueprint` trait. The table may implement `TableWithBlueprint` specifying the blueprint, as an example: ```rust impl TableWithBlueprint for ContractsRawCode { type Blueprint = Plain<Raw, Raw>; fn column() -> Column { Column::ContractsRawCode } } ``` It is a definition of the blueprint for the `ContractsRawCode` table. It has a plain blueprint meaning it simply encodes/decodes bytes and stores/loads them into/from the storage. As a key codec and value codec, it uses a `Raw` encoding/decoding that simplifies writing bytes and loads them back into the memory without applying any serialization or deserialization algorithm. If the table implements `TableWithBlueprint` and the selected codec satisfies all blueprint requirements, the corresponding storage traits for that table are implemented on the `StructuredStorage` type. ### Codecs Each blueprint allows customizing the key and value codecs. It allows the use of different codecs for different tables, taking into account the complexity and weight of the data and providing a way of more optimal implementation. That property may be very useful to perform migration in a more easier way. Plus, it also can be a `no_std` migration potentially allowing its fraud proving. An example of migration: ```rust /// Define the table for V1 value encoding/decoding. impl TableWithBlueprint for ContractsRawCodeV1 { type Blueprint = Plain<Raw, Raw>; fn column() -> Column { Column::ContractsRawCode } } /// Define the table for V2 value encoding/decoding. /// It uses `Postcard` codec for the value instead of `Raw` codec. /// /// # Dev-note: The columns is the same. impl TableWithBlueprint for ContractsRawCodeV2 { type Blueprint = Plain<Raw, Postcard>; fn column() -> Column { Column::ContractsRawCode } } fn migration(storage: &mut Database) { let mut iter = storage.iter_all::<ContractsRawCodeV1>(None); while let Ok((key, value)) = iter.next() { // Insert into the same table but with another codec. storage.storage::<ContractsRawCodeV2>().insert(key, value); } } ``` ### Structures The blueprint of the table defines its behavior. As an example, a `Plain` blueprint simply encodes/decodes bytes and stores/loads them into/from the storage. The `SMT` blueprint builds a sparse merkle tree on top of the key-value pairs. Implementing a blueprint one time, we can apply it to any table satisfying the requirements of this blueprint. It increases the re-usage of the code and minimizes duplication. It can be useful if we decide to create global roots for all required tables that are used in fraud proving. ```rust impl TableWithBlueprint for SpentMessages { type Blueprint = Plain<Raw, Postcard>; fn column() -> Column { Column::SpentMessages } } | | \|/ impl TableWithBlueprint for SpentMessages { type Blueprint = Sparse<Raw, Postcard, SpentMessagesMerkleMetadata, SpentMessagesMerkleNodes>; fn column() -> Column { Column::SpentMessages } } ``` ### Side changes #### `iter_all` The `iter_all` functionality now accepts the table instead of `K` and `V` generics. It is done to use the correct codec during deserialization. Also, the table definition provides the column. #### Duplicated unit tests The `fuel-core-storage` crate provides macros that generate unit tests. Almost all tables had the same test like `get`, `insert`, `remove`, `exist`. All duplicated tests were moved to macros. The unique one still stays at the same place where it was before. #### `StorageBatchMutate` Added a new `StorageBatchMutate` trait that we can move to `fuel-storage` crate later. It allows batch operations on the storage. It may be more performant in some cases. - [#1573](#1573): Remove nested p2p request/response encoding. Only breaks p2p networking compatibility with older fuel-core versions, but is otherwise fully internal. ## What's Changed * Fix the example tx in README.md by @Dentosal in #1543 * Weekly `cargo update` by @github-actions in #1557 * Update LICENSE by @Voxelot in #1558 * Remove duplicating logic in the `KeyValueStore` trait by @xgreenx in #1559 * Move `KeyValueStore` to the `fuel-core-storage` crate by @xgreenx in #1566 * Update `libp2p` from `0.50.0` to `0.53.1` by @MitchTurner in #1379 * Weekly `cargo update` by @github-actions in #1575 * Weekly `cargo update` by @github-actions in #1578 * Moved insertion of the blocks into the `BlockImporter` instead of the executor by @xgreenx in #1577 * Let `NetworkBehaviour` macro generate `FuelBehaviorEvent` in p2p by @MitchTurner in #1585 * Remove RustSec Ignore for RUSTSEC-2022-0093 by @MitchTurner in #1586 * Simplify p2p request/response message serialization by @Dentosal in #1573 * Weekly `cargo update` by @github-actions in #1588 * Depend on libp2p directly instead of submodules by @MitchTurner in #1591 * Make `Block` versionable by @MitchTurner in #1593 * Switch katyo/publish-crates@v2 to xgreenx/publish-crates@v1 for the CI by @Dentosal in #1603 * Weekly `cargo update` by @github-actions in #1599 * Fix cargo doc, check for cargo doc in the CI by @Dentosal in #1601 * Move storage traits implementation to the `fuel-core-storage` crate by @xgreenx in #1576 * Extract off chain logic from the executor by @xgreenx in #1579 * Applying comments from the PR for the storage crate by @xgreenx in #1610 * Use `BlockHeight` as a primary key for the `FuelsBlock` table by @xgreenx in #1587 * Use `AtomicView` in the `TxPool` by @xgreenx in #1590 * Unify namespacing in P2P codebase by @MitchTurner in #1597 * Weekly `cargo update` by @github-actions in #1611 * Make `Consensus` version-able by @MitchTurner in #1596 * feat: add api endpoint to retrieve messages by nonce by @matt-user in #1613 * Update Docker tags for Flux integration by @liviudm in #1617 * Remove dev consensus key by @MujkicA in #1614 * Make `BlockHeader` a versionable enum by @MitchTurner in #1616 * feat: Versionable CompressedCoin by @bvrooman in #1628 * Use `AtomicView` in all services by @xgreenx in #1612 * Notify services about importing of the genesis block by @xgreenx in #1633 * Weekly `cargo update` by @github-actions in #1634 * feat: Versionable `Message` by @bvrooman in #1632 * Making relayer independent from the executor by @xgreenx in #1625 * Upgrade to the fuel-vm 0.44.0 by @Dentosal in #1600 * Upgrade to fuel-vm 0.45.0 by @MitchTurner in #1640 * feat: Versionable Merkle metadata by @bvrooman in #1639 * Add docs for GraphQL DAP endpoints by @Dentosal in #1636 * Use a separate database for each data domain by @xgreenx in #1629 * feat: dry run multiple transactions by @matt-user in #1631 * Weekly `cargo update` by @github-actions in #1644 * Decrease peer reputation on request timeouts and decode errors by @Dentosal in #1574 * refactor: remove redudant receipts queries by @matt-user in #1646 * Latest gas price endpoint by @MitchTurner in #1649 * Estimate gas price API Endpoint by @MitchTurner in #1650 * Fix for the race condition with tx status and receipts by @xgreenx in #1658 * Bump up rust to 1.75 by @MitchTurner in #1656 * Weekly `cargo update` by @github-actions in #1655 * Duplication of the `0.22.1` release by @xgreenx in #1665 * Move updating of the owned messages and coins to off-chain worker by @xgreenx in #1635 * Weekly `cargo update` by @github-actions in #1682 * Remove nested contract resolvers in GQL schema by @Voxelot in #1675 * Soften txpool p2p reputation requirements by @Voxelot in #1663 * Remove failing deployment job by @Voxelot in #1692 * chore: format cargo tomls and add ci check by @segfault-magnet in #1691 * Extraction of the `FuelBlockSecondaryKeyBlockHeights` table to an off-chain worker by @xgreenx in #1671 * Weekly `cargo update` by @github-actions in #1707 * Increase liveness probe timeout to 5 minutes by @rfuelsh in #1709 * feat: Add `ContractInfo` table and versionable `ContractInfoType` to on-chain storage by @bvrooman in #1657 * feat: Versionable `ContractUtxoInfo` for the `ContractsLatestUtxo` table by @bvrooman in #1712 * Weekly `cargo update` by @github-actions in #1719 * Bump `fuel-vm` dep to 0.47.1 by @MitchTurner in #1714 * Bugfix: Calling of the contract with enabled `utxo_validation` fails by @bvrooman in #1717 * Bugfix: Zero `predicate_gas_used` field during validation of the produced block by @xgreenx in #1722 * Notify about imported blocks from the off-chain worker by @xgreenx in #1723 * Added versioning suffix to the GraphQL endpoint by @xgreenx in #1725 ## New Contributors * @liviudm made their first contribution in #1617 **Full Changelog**: v0.22.0...v0.23.0
Closes FuelLabs/fuel-core#1549 ## Overview The change extracts the off-chain-related logic from the executor and moves it to the GraphQL off-chain worker. It creates two new concepts - Off-chain and On-chain databases where the GraphQL worker has exclusive ownership of the database and may modify it without intersecting with the On-chain database. ## Challenges caused by the change Delegating updating of the state to something other than `BlockImporter` causes several new problems: - The commitment to the on-chain and off-chain databases is done in different places. The off-chain database may be out of sync with the on-chain database due to race conditions. - The result of the block execution(receipts, statuses) is not stored anywhere and may be lost due to emergency shutdown. We don't want to duplicate on-chain data inside of the off-chain database, so the GraphQL service works with two sources of data, which leads to two problems: - The off-chain database may be out of sync with the on-chain database due to race conditions causing failing requests. - The view of the databases during the GraphQL request may change, causing invalid responses with a mix of old and new data. We had this problem before, but now it is more critical. ## Solutions to the challenges ### Out of sync The change applies two steps to solve this issue. The main one is a new trait for the database: ```rust /// Provides a view of the storage at the given height. /// It guarantees to be atomic, meaning the view is immutable to outside modifications. pub trait AtomicView<View>: Send + Sync { /// Returns the view of the storage at the given `height`. fn view_at(&self, height: BlockHeight) -> StorageResult<View>; /// Returns the view of the storage for the latest block height. fn latest_view(&self) -> View; } ``` Another one to await on the `BlockCommiter` side finishing processing the `ImportResult` by all listeners. The goal of the trait is to provide an immutable read-only view of the database at a specific time. However, this trait has not yet been implemented properly during this PR and will be implemented in the following PRs. The `view_at` requires functionality from FuelLabs/fuel-core#451. We already can implement the `latest_view` method via [`RocksDB::Transaction`](https://github.com/facebook/rocksdb/wiki/Transactions#reading-from-a-transaction), but it is better to do it after merging FuelLabs/fuel-core#1576. Waiting on the `BlockImporter` side is a temporary solution to not escalate the problem. But maybe we can keep it later to guarantee the consistent state of the blockchain. ### Losing result of execution The `AtomicView` trait also solves the issue of losing the state of the execution because it is possible to get a view of the database at a specific block height and execute the block again receiving the same execution result. Waiting inside the `BlockImporter` guarantees that we will not lose more than one `ImportResult`. ### Inconsistent database view within GraphQL requests The GraphQL now has `ReadDatabase`: ```rust pub type OnChainView = Arc<dyn OnChainDatabase>; pub type OffChainView = Arc<dyn OffChainDatabase>; pub struct ReadDatabase { on_chain: Box<dyn AtomicView<OnChainView>>, off_chain: Box<dyn AtomicView<OffChainView>>, } ``` It implements the `view` method that returns the `ReadView` type. The `ReadView` implements all required methods by using internal on-chain view and off-chain view. The `AtomicView` allows us to get the `last_view` of the off-chain database and get the `view_at(off_chain_view.last_height())` of the on-chain database creating a consistent view for both databases at a specific height. The change also adds a `ViewExtension` to the GraphQL that creates a `ReadView` for each request. ```rust /// The extension that adds the `ReadView` to the request context. /// It guarantees that the request works with the one view of the database, /// and external database modification cannot affect the result. struct ViewExtension; #[async_trait::async_trait] impl Extension for ViewExtension { async fn prepare_request( &self, ctx: &ExtensionContext<'_>, request: Request, next: NextPrepareRequest<'_>, ) -> ServerResult<Request> { let database: &ReadDatabase = ctx.data_unchecked(); let view = database.view(); let request = request.data(view); next.run(ctx, request).await } } ``` ## Implementation details - The `ExecutionResult` now also has receipts for the transaction along with its status. The off-chain worker will insert them later into the database, while the `dry_run` can fetch them immediately. - All API requests now work with the `ReadView` instead of the `Database` type. The `ReadDatabase` is only used in one place in the `ViewExtension`. - The `BlockImpoerter::comit_result` now is `async` and awaits for the previous block to be processed by all listeners. The execution of the `execute_and_commit` now runs `verify_and_execute_block` in the spawned task in the `tokio_rayon`. ## Follow up - FuelLabs/fuel-core#1580 - FuelLabs/fuel-core#1581 - FuelLabs/fuel-core#1582 - FuelLabs/fuel-core#1583 - FuelLabs/fuel-core#1584
Applying comments from the FuelLabs/fuel-core#1576
## Version v0.23.0 ### Added - [#1713](FuelLabs/fuel-core#1713): Added automatic `impl` of traits `StorageWrite` and `StorageRead` for `StructuredStorage`. Tables that use a `Blueprint` can be read and written using these interfaces provided by structured storage types. - [#1671](FuelLabs/fuel-core#1671): Added a new `Merklized` blueprint that maintains the binary Merkle tree over the storage data. It supports only the insertion of the objects without removing them. - [#1657](FuelLabs/fuel-core#1657): Moved `ContractsInfo` table from `fuel-vm` to on-chain tables, and created version-able `ContractsInfoType` to act as the table's data type. ### Changed - [#1723](FuelLabs/fuel-core#1723): Notify about imported blocks from the off-chain worker. - [#1717](FuelLabs/fuel-core#1717): The fix for the [#1657](FuelLabs/fuel-core#1657) to include the contract into `ContractsInfo` table. - [#1657](FuelLabs/fuel-core#1657): Upgrade to `fuel-vm` 0.46.0. - [#1671](FuelLabs/fuel-core#1671): The logic related to the `FuelBlockIdsToHeights` is moved to the off-chain worker. - [#1663](FuelLabs/fuel-core#1663): Reduce the punishment criteria for mempool gossipping. - [#1658](FuelLabs/fuel-core#1658): Removed `Receipts` table. Instead, receipts are part of the `TransactionStatuses` table. - [#1640](FuelLabs/fuel-core#1640): Upgrade to fuel-vm 0.45.0. - [#1635](FuelLabs/fuel-core#1635): Move updating of the owned messages and coins to off-chain worker. - [#1650](FuelLabs/fuel-core#1650): Add api endpoint for getting estimates for future gas prices - [#1649](FuelLabs/fuel-core#1649): Add api endpoint for getting latest gas price - [#1600](FuelLabs/fuel-core#1640): Upgrade to fuel-vm 0.45.0 - [#1633](FuelLabs/fuel-core#1633): Notify services about importing of the genesis block. - [#1625](FuelLabs/fuel-core#1625): Making relayer independent from the executor and preparation for the force transaction inclusion. - [#1613](FuelLabs/fuel-core#1613): Add api endpoint to retrieve a message by its nonce. - [#1612](FuelLabs/fuel-core#1612): Use `AtomicView` in all services for consistent results. - [#1597](FuelLabs/fuel-core#1597): Unify namespacing for `libp2p` modules - [#1591](FuelLabs/fuel-core#1591): Simplify libp2p dependencies and not depend on all sub modules directly. - [#1590](FuelLabs/fuel-core#1590): Use `AtomicView` in the `TxPool` to read the state of the database during insertion of the transactions. - [#1587](FuelLabs/fuel-core#1587): Use `BlockHeight` as a primary key for the `FuelsBlock` table. - [#1585](FuelLabs/fuel-core#1585): Let `NetworkBehaviour` macro generate `FuelBehaviorEvent` in p2p - [#1579](FuelLabs/fuel-core#1579): The change extracts the off-chain-related logic from the executor and moves it to the GraphQL off-chain worker. It creates two new concepts - Off-chain and On-chain databases where the GraphQL worker has exclusive ownership of the database and may modify it without intersecting with the On-chain database. - [#1577](FuelLabs/fuel-core#1577): Moved insertion of sealed blocks into the `BlockImporter` instead of the executor. - [#1574](FuelLabs/fuel-core#1574): Penalizes peers for sending invalid responses or for not replying at all. - [#1601](FuelLabs/fuel-core#1601): Fix formatting in docs and check that `cargo doc` passes in the CI. - [#1636](FuelLabs/fuel-core#1636): Add more docs to GraphQL DAP API. #### Breaking - [#1725](FuelLabs/fuel-core#1725): All API endpoints now are prefixed with `/v1` version. New usage looks like: `/v1/playground`, `/v1/graphql`, `/v1/graphql-sub`, `/v1/metrics`, `/v1/health`. - [#1722](FuelLabs/fuel-core#1722): Bugfix: Zero `predicate_gas_used` field during validation of the produced block. - [#1714](FuelLabs/fuel-core#1714): The change bumps the `fuel-vm` to `0.47.1`. It breaks several breaking changes into the protocol: - All malleable fields are zero during the execution and unavailable through the GTF getters. Accessing them via the memory directly is still possible, but they are zero. - The `Transaction` doesn't define the gas price anymore. The gas price is defined by the block producer and recorded in the `Mint` transaction at the end of the block. A price of future blocks can be fetched through a [new API nedopoint](FuelLabs/fuel-core#1641) and the price of the last block can be fetch or via the block or another [API endpoint](FuelLabs/fuel-core#1647). - The `GasPrice` policy is replaced with the `Tip` policy. The user may specify in the native tokens how much he wants to pay the block producer to include his transaction in the block. It is the prioritization mechanism to incentivize the block producer to include users transactions earlier. - The `MaxFee` policy is mandatory to set. Without it, the transaction pool will reject the transaction. Since the block producer defines the gas price, the only way to control how much user agreed to pay can be done only through this policy. - The `maturity` field is removed from the `Input::Coin`. The same affect can be achieve with the `Maturity` policy on the transaction and predicate. This changes breaks how input coin is created and removes the passing of this argument. - The metadata of the `Checked<Tx>` doesn't contain `max_fee` and `min_fee` anymore. Only `max_gas` and `min_gas`. The `max_fee` is controlled by the user via the `MaxFee` policy. - Added automatic `impl` of traits `StorageWrite` and `StorageRead` for `StructuredStorage`. Tables that use a `Blueprint` can be read and written using these interfaces provided by structured storage types. - [#1712](FuelLabs/fuel-core#1712): Make `ContractUtxoInfo` type a version-able enum for use in the `ContractsLatestUtxo`table. - [#1657](FuelLabs/fuel-core#1657): Changed `CROO` gas price type from `Word` to `DependentGasPrice`. The dependent gas price values are dummy values while awaiting updated benchmarks. - [#1671](FuelLabs/fuel-core#1671): The GraphQL API uses block height instead of the block id where it is possible. The transaction status contains `block_height` instead of the `block_id`. - [#1675](FuelLabs/fuel-core#1675): Simplify GQL schema by disabling contract resolvers in most cases, and just return a ContractId scalar instead. - [#1658](FuelLabs/fuel-core#1658): Receipts are part of the transaction status. Removed `reason` from the `TransactionExecutionResult::Failed`. It can be calculated based on the program state and receipts. Also, it is not possible to fetch `receipts` from the `Transaction` directly anymore. Instead, you need to fetch `status` and its receipts. - [#1646](FuelLabs/fuel-core#1646): Remove redundant receipts from queries. - [#1639](FuelLabs/fuel-core#1639): Make Merkle metadata, i.e. `SparseMerkleMetadata` and `DenseMerkleMetadata` type version-able enums - [#1632](FuelLabs/fuel-core#1632): Make `Message` type a version-able enum - [#1631](FuelLabs/fuel-core#1631): Modify api endpoint to dry run multiple transactions. - [#1629](FuelLabs/fuel-core#1629): Use a separate database for each data domain. Each database has its own folder where data is stored. - [#1628](FuelLabs/fuel-core#1628): Make `CompressedCoin` type a version-able enum - [#1616](FuelLabs/fuel-core#1616): Make `BlockHeader` type a version-able enum - [#1614](FuelLabs/fuel-core#1614): Use the default consensus key regardless of trigger mode. The change is breaking because it removes the `--dev-keys` argument. If the `debug` flag is set, the default consensus key will be used, regardless of the trigger mode. - [#1596](FuelLabs/fuel-core#1596): Make `Consensus` type a version-able enum - [#1593](FuelLabs/fuel-core#1593): Make `Block` type a version-able enum - [#1576](FuelLabs/fuel-core#1576): The change moves the implementation of the storage traits for required tables from `fuel-core` to `fuel-core-storage` crate. The change also adds a more flexible configuration of the encoding/decoding per the table and allows the implementation of specific behaviors for the table in a much easier way. It unifies the encoding between database, SMTs, and iteration, preventing mismatching bytes representation on the Rust type system level. Plus, it increases the re-usage of the code by applying the same blueprint to other tables. It is a breaking PR because it changes database encoding/decoding for some tables. ### StructuredStorage The change adds a new type `StructuredStorage`. It is a wrapper around the key-value storage that implements the storage traits(`StorageInspect`, `StorageMutate`, `StorageRead`, etc) for the tables with blueprint. This blueprint works in tandem with the `TableWithBlueprint` trait. The table may implement `TableWithBlueprint` specifying the blueprint, as an example: ```rust impl TableWithBlueprint for ContractsRawCode { type Blueprint = Plain<Raw, Raw>; fn column() -> Column { Column::ContractsRawCode } } ``` It is a definition of the blueprint for the `ContractsRawCode` table. It has a plain blueprint meaning it simply encodes/decodes bytes and stores/loads them into/from the storage. As a key codec and value codec, it uses a `Raw` encoding/decoding that simplifies writing bytes and loads them back into the memory without applying any serialization or deserialization algorithm. If the table implements `TableWithBlueprint` and the selected codec satisfies all blueprint requirements, the corresponding storage traits for that table are implemented on the `StructuredStorage` type. ### Codecs Each blueprint allows customizing the key and value codecs. It allows the use of different codecs for different tables, taking into account the complexity and weight of the data and providing a way of more optimal implementation. That property may be very useful to perform migration in a more easier way. Plus, it also can be a `no_std` migration potentially allowing its fraud proving. An example of migration: ```rust /// Define the table for V1 value encoding/decoding. impl TableWithBlueprint for ContractsRawCodeV1 { type Blueprint = Plain<Raw, Raw>; fn column() -> Column { Column::ContractsRawCode } } /// Define the table for V2 value encoding/decoding. /// It uses `Postcard` codec for the value instead of `Raw` codec. /// /// # Dev-note: The columns is the same. impl TableWithBlueprint for ContractsRawCodeV2 { type Blueprint = Plain<Raw, Postcard>; fn column() -> Column { Column::ContractsRawCode } } fn migration(storage: &mut Database) { let mut iter = storage.iter_all::<ContractsRawCodeV1>(None); while let Ok((key, value)) = iter.next() { // Insert into the same table but with another codec. storage.storage::<ContractsRawCodeV2>().insert(key, value); } } ``` ### Structures The blueprint of the table defines its behavior. As an example, a `Plain` blueprint simply encodes/decodes bytes and stores/loads them into/from the storage. The `SMT` blueprint builds a sparse merkle tree on top of the key-value pairs. Implementing a blueprint one time, we can apply it to any table satisfying the requirements of this blueprint. It increases the re-usage of the code and minimizes duplication. It can be useful if we decide to create global roots for all required tables that are used in fraud proving. ```rust impl TableWithBlueprint for SpentMessages { type Blueprint = Plain<Raw, Postcard>; fn column() -> Column { Column::SpentMessages } } | | \|/ impl TableWithBlueprint for SpentMessages { type Blueprint = Sparse<Raw, Postcard, SpentMessagesMerkleMetadata, SpentMessagesMerkleNodes>; fn column() -> Column { Column::SpentMessages } } ``` ### Side changes #### `iter_all` The `iter_all` functionality now accepts the table instead of `K` and `V` generics. It is done to use the correct codec during deserialization. Also, the table definition provides the column. #### Duplicated unit tests The `fuel-core-storage` crate provides macros that generate unit tests. Almost all tables had the same test like `get`, `insert`, `remove`, `exist`. All duplicated tests were moved to macros. The unique one still stays at the same place where it was before. #### `StorageBatchMutate` Added a new `StorageBatchMutate` trait that we can move to `fuel-storage` crate later. It allows batch operations on the storage. It may be more performant in some cases. - [#1573](FuelLabs/fuel-core#1573): Remove nested p2p request/response encoding. Only breaks p2p networking compatibility with older fuel-core versions, but is otherwise fully internal. ## What's Changed * Fix the example tx in README.md by @Dentosal in FuelLabs/fuel-core#1543 * Weekly `cargo update` by @github-actions in FuelLabs/fuel-core#1557 * Update LICENSE by @Voxelot in FuelLabs/fuel-core#1558 * Remove duplicating logic in the `KeyValueStore` trait by @xgreenx in FuelLabs/fuel-core#1559 * Move `KeyValueStore` to the `fuel-core-storage` crate by @xgreenx in FuelLabs/fuel-core#1566 * Update `libp2p` from `0.50.0` to `0.53.1` by @MitchTurner in FuelLabs/fuel-core#1379 * Weekly `cargo update` by @github-actions in FuelLabs/fuel-core#1575 * Weekly `cargo update` by @github-actions in FuelLabs/fuel-core#1578 * Moved insertion of the blocks into the `BlockImporter` instead of the executor by @xgreenx in FuelLabs/fuel-core#1577 * Let `NetworkBehaviour` macro generate `FuelBehaviorEvent` in p2p by @MitchTurner in FuelLabs/fuel-core#1585 * Remove RustSec Ignore for RUSTSEC-2022-0093 by @MitchTurner in FuelLabs/fuel-core#1586 * Simplify p2p request/response message serialization by @Dentosal in FuelLabs/fuel-core#1573 * Weekly `cargo update` by @github-actions in FuelLabs/fuel-core#1588 * Depend on libp2p directly instead of submodules by @MitchTurner in FuelLabs/fuel-core#1591 * Make `Block` versionable by @MitchTurner in FuelLabs/fuel-core#1593 * Switch katyo/publish-crates@v2 to xgreenx/publish-crates@v1 for the CI by @Dentosal in FuelLabs/fuel-core#1603 * Weekly `cargo update` by @github-actions in FuelLabs/fuel-core#1599 * Fix cargo doc, check for cargo doc in the CI by @Dentosal in FuelLabs/fuel-core#1601 * Move storage traits implementation to the `fuel-core-storage` crate by @xgreenx in FuelLabs/fuel-core#1576 * Extract off chain logic from the executor by @xgreenx in FuelLabs/fuel-core#1579 * Applying comments from the PR for the storage crate by @xgreenx in FuelLabs/fuel-core#1610 * Use `BlockHeight` as a primary key for the `FuelsBlock` table by @xgreenx in FuelLabs/fuel-core#1587 * Use `AtomicView` in the `TxPool` by @xgreenx in FuelLabs/fuel-core#1590 * Unify namespacing in P2P codebase by @MitchTurner in FuelLabs/fuel-core#1597 * Weekly `cargo update` by @github-actions in FuelLabs/fuel-core#1611 * Make `Consensus` version-able by @MitchTurner in FuelLabs/fuel-core#1596 * feat: add api endpoint to retrieve messages by nonce by @matt-user in FuelLabs/fuel-core#1613 * Update Docker tags for Flux integration by @liviudm in FuelLabs/fuel-core#1617 * Remove dev consensus key by @MujkicA in FuelLabs/fuel-core#1614 * Make `BlockHeader` a versionable enum by @MitchTurner in FuelLabs/fuel-core#1616 * feat: Versionable CompressedCoin by @bvrooman in FuelLabs/fuel-core#1628 * Use `AtomicView` in all services by @xgreenx in FuelLabs/fuel-core#1612 * Notify services about importing of the genesis block by @xgreenx in FuelLabs/fuel-core#1633 * Weekly `cargo update` by @github-actions in FuelLabs/fuel-core#1634 * feat: Versionable `Message` by @bvrooman in FuelLabs/fuel-core#1632 * Making relayer independent from the executor by @xgreenx in FuelLabs/fuel-core#1625 * Upgrade to the fuel-vm 0.44.0 by @Dentosal in FuelLabs/fuel-core#1600 * Upgrade to fuel-vm 0.45.0 by @MitchTurner in FuelLabs/fuel-core#1640 * feat: Versionable Merkle metadata by @bvrooman in FuelLabs/fuel-core#1639 * Add docs for GraphQL DAP endpoints by @Dentosal in FuelLabs/fuel-core#1636 * Use a separate database for each data domain by @xgreenx in FuelLabs/fuel-core#1629 * feat: dry run multiple transactions by @matt-user in FuelLabs/fuel-core#1631 * Weekly `cargo update` by @github-actions in FuelLabs/fuel-core#1644 * Decrease peer reputation on request timeouts and decode errors by @Dentosal in FuelLabs/fuel-core#1574 * refactor: remove redudant receipts queries by @matt-user in FuelLabs/fuel-core#1646 * Latest gas price endpoint by @MitchTurner in FuelLabs/fuel-core#1649 * Estimate gas price API Endpoint by @MitchTurner in FuelLabs/fuel-core#1650 * Fix for the race condition with tx status and receipts by @xgreenx in FuelLabs/fuel-core#1658 * Bump up rust to 1.75 by @MitchTurner in FuelLabs/fuel-core#1656 * Weekly `cargo update` by @github-actions in FuelLabs/fuel-core#1655 * Duplication of the `0.22.1` release by @xgreenx in FuelLabs/fuel-core#1665 * Move updating of the owned messages and coins to off-chain worker by @xgreenx in FuelLabs/fuel-core#1635 * Weekly `cargo update` by @github-actions in FuelLabs/fuel-core#1682 * Remove nested contract resolvers in GQL schema by @Voxelot in FuelLabs/fuel-core#1675 * Soften txpool p2p reputation requirements by @Voxelot in FuelLabs/fuel-core#1663 * Remove failing deployment job by @Voxelot in FuelLabs/fuel-core#1692 * chore: format cargo tomls and add ci check by @segfault-magnet in FuelLabs/fuel-core#1691 * Extraction of the `FuelBlockSecondaryKeyBlockHeights` table to an off-chain worker by @xgreenx in FuelLabs/fuel-core#1671 * Weekly `cargo update` by @github-actions in FuelLabs/fuel-core#1707 * Increase liveness probe timeout to 5 minutes by @rfuelsh in FuelLabs/fuel-core#1709 * feat: Add `ContractInfo` table and versionable `ContractInfoType` to on-chain storage by @bvrooman in FuelLabs/fuel-core#1657 * feat: Versionable `ContractUtxoInfo` for the `ContractsLatestUtxo` table by @bvrooman in FuelLabs/fuel-core#1712 * Weekly `cargo update` by @github-actions in FuelLabs/fuel-core#1719 * Bump `fuel-vm` dep to 0.47.1 by @MitchTurner in FuelLabs/fuel-core#1714 * Bugfix: Calling of the contract with enabled `utxo_validation` fails by @bvrooman in FuelLabs/fuel-core#1717 * Bugfix: Zero `predicate_gas_used` field during validation of the produced block by @xgreenx in FuelLabs/fuel-core#1722 * Notify about imported blocks from the off-chain worker by @xgreenx in FuelLabs/fuel-core#1723 * Added versioning suffix to the GraphQL endpoint by @xgreenx in FuelLabs/fuel-core#1725 ## New Contributors * @liviudm made their first contribution in FuelLabs/fuel-core#1617 **Full Changelog**: FuelLabs/fuel-core@v0.22.0...v0.23.0
Overview
Closes #1548
Closes #430
The change moves the implementation of the storage traits for required tables from
fuel-core
tofuel-core-storage
crate. The change also adds a more flexible configuration of the encoding/decoding per the table and allows the implementation of specific behaviors for the table in a much easier way. It unifies the encoding between database, SMTs, and iteration, preventing mismatching bytes representation on the Rust type system level. Plus, it increases the re-usage of the code by applying the same structure to other tables.It is a breaking PR because it changes database encoding/decoding for some tables.
StructuredStorage
The change adds a new type
StructuredStorage
. It is a wrapper around the key-value storage that implements the storage traits(StorageInspect
,StorageMutate
,StorageRead
, etc) for the tables with structure. This structure works in tandem with theTableWithStructure
trait. The table may implementTableWithStructure
specifying the structure, as an example:It is a definition of the structure for the
ContractsRawCode
table. It has a plain structure meaning it simply encodes/decodes bytes and stores/loads them into/from the storage. As a key codec and value codec, it uses aRaw
encoding/decoding that simplifies writing bytes and loads them back into the memory without applying any serialization or deserialization algorithm.If the table implements
TableWithStructure
and the selected codec satisfies all structure requirements, the corresponding storage traits for that table are implemented on theStructuredStorage
type.Codecs
Each structure allows customizing the key and value codecs. It allows the use of different codecs for different tables, taking into account the complexity and weight of the data and providing a way of more optimal implementation.
That property may be very useful to perform migration in a more easier way. Plus, it also can be a
no_std
migration potentially allowing its fraud proving.An example of migration:
Structures
The structure of the table defines its behavior. As an example, a
Plain
structure simply encodes/decodes bytes and stores/loads them into/from the storage. TheSMT
structure builds a sparse merkle tree on top of the key-value pairs.Implementing a structure one time, we can apply it to any table satisfying the requirements of this structure. It increases the re-usage of the code and minimizes duplication.
It can be useful if we decide to create global roots for all required tables that are used in fraud proving.
Side changes
iter_all
The
iter_all
functionality now accepts the table instead ofK
andV
generics. It is done to use the correct codec during deserialization. Also, the table definition provides the column.Duplicated unit tests
The
fuel-core-storage
crate provides macros that generate unit tests. Almost all tables had the same test likeget
,insert
,remove
,exist
. All duplicated tests were moved to macros. The unique one still stays at the same place where it was before.StorageBatchMutate
Added a new
StorageBatchMutate
trait that we can move tofuel-storage
crate later. It allows batch operations on the storage. It may be more performant in some cases.Follow-up
It is one of the changes in the direction of the forkless upgrades for state transition functions and fraud proofs. The idea behind this is that the
fuel_core_executor::Executor
will work directly with theStructuredStorage
instead of theDatabase
. It will perform only state transition-related modifications to the storage, while all outside modifications like updating of receipts, transition status, block insertions, messages removing, and transaction storing will be a part of another service/process.