Skip to content

Commit

Permalink
feat: add support for DbType::RocksDb (#998)
Browse files Browse the repository at this point in the history
Add `rocksdb` an additional feature that, when combined with `fuel-core-lib`, provides persistent storage capabilities while using `fuel-core` as a library.
  • Loading branch information
Salka1988 authored Jul 13, 2023
1 parent ea4687d commit 294f91b
Show file tree
Hide file tree
Showing 14 changed files with 225 additions and 11 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,10 @@ jobs:
args: --all-targets
download_sway_artifacts: sway-examples
- cargo_command: clippy
args: --all-targets --all-features
args: --all-targets --features "default fuel-core-lib test-type-paths"
download_sway_artifacts: sway-examples-w-type-paths
- cargo_command: nextest
args: run --all-targets --all-features --workspace
args: run --all-targets --features "default fuel-core-lib test-type-paths" --workspace
download_sway_artifacts: sway-examples-w-type-paths
install_fuel_core: true
- cargo_command: nextest
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ itertools = "0.10"
portpicker = "0.1.1"
proc-macro2 = "1.0"
quote = "1.0"
rand = { version = "0.8", default-features = false, features = ["std_rng", "getrandom"]}
rand = { version = "0.8", default-features = false, features = ["std_rng", "getrandom"] }
regex = "1.8.1"
serde = { version = "1.0", default-features = false }
serde_json = "1.0.96"
Expand All @@ -79,7 +79,7 @@ strum = "0.24"
strum_macros = "0.24"
syn = "2.0.15"
tai64 = { version = "4.0", default-features = false }
tempfile = { version ="3.5.0", default-features = false }
tempfile = { version = "3.5.0", default-features = false }
thiserror = { version = "1.0.40", default-features = false }
tokio = { version = "1.27.0", default-features = false }
trybuild = "1.0.80"
Expand Down
1 change: 1 addition & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- [Connecting to a Fuel node](./connecting/index.md)
- [Connecting to the Testnet or an external node](./connecting/external-node.md)
- [Running a short-lived Fuel node with the SDK](./connecting/short-lived.md)
- [Rocksdb](./connecting/rocksdb.md)
- [Querying the blockchain](./connecting/querying.md)
- [Accounts](./accounts.md)
- [Managing wallets](./wallets/index.md)
Expand Down
16 changes: 16 additions & 0 deletions docs/src/connecting/rocksdb.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
## RocksDb

RocksDb enables the preservation of the blockchain's state locally, facilitating its future utilization.

To create or use a local database, follow these instructions:

```rust,ignore
{{#include ../../../examples/cookbook/src/lib.rs:create_or_use_rocksdb}}
```

> Note 1: If the specified database does not exist, a new database will be created at that path.
> Note 2: To utilize the code snippets above, either:
> - the `fuel-core` binary must be present
> or
> - both the `fuel-core-lib` and `rocksdb` features need to be enabled.
10 changes: 9 additions & 1 deletion docs/src/connecting/short-lived.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,16 @@ let wallet = launch_provider_and_get_wallet().await;

## Fuel-core lib

The `fuel-core-lib` is a feature defined in the `fuels` library, allowing us to run a `fuel-core` node without installing the `fuel-core` binary on the local machine. Using the `fuel-core-lib` feature flag entails downloading all the dependencies needed to run the fuel-core node.
The `fuel-core-lib` feature allows us to run a `fuel-core` node without installing the `fuel-core` binary on the local machine. Using the `fuel-core-lib` feature flag entails downloading all the dependencies needed to run the fuel-core node.

```rust,ignore
fuels = { version = "0.43.0", features = ["fuel-core-lib"] }
```

## RocksDb

The `rocksdb` is an additional feature that, when combined with `fuel-core-lib`, provides persistent storage capabilities while using `fuel-core` as a library.

```rust,ignore
fuels = { version = "0.43.0", features = ["rocksdb"] }
```
1 change: 1 addition & 0 deletions examples/contracts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ tokio = { workspace = true, features = ["full"] }

[features]
fuel-core-lib = ["fuels/fuel-core-lib"]
rocksdb = ["fuels/rocksdb"]
4 changes: 4 additions & 0 deletions examples/cookbook/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ description = "Fuel Rust SDK cookbook examples."
fuels = { workspace = true }
rand = { workspace = true }
tokio = { workspace = true, features = ["full"] }

[features]
rocksdb = ["fuels/rocksdb"]
fuel-core-lib = ["fuels/fuel-core-lib"]
21 changes: 20 additions & 1 deletion examples/cookbook/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ mod tests {
use std::str::FromStr;

use fuels::prelude::*;

// ANCHOR: transfer_multiple_setup
let mut wallet_1 = WalletUnlocked::new_random(None);
let mut wallet_2 = WalletUnlocked::new_random(None);
Expand Down Expand Up @@ -185,4 +184,24 @@ mod tests {

Ok(())
}

#[tokio::test]
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
async fn create_or_use_rocksdb() -> Result<()> {
use fuels::prelude::*;
use std::path::PathBuf;

// ANCHOR: create_or_use_rocksdb
let provider_config = Config {
database_path: PathBuf::from("/tmp/.spider/db"),
database_type: DbType::RocksDb,
..Config::local_node()
};
// ANCHOR_END: create_or_use_rocksdb

launch_custom_provider_and_get_wallets(Default::default(), Some(provider_config), None)
.await;

Ok(())
}
}
2 changes: 1 addition & 1 deletion packages/fuels-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ description = "Fuel Rust SDK core."
[dependencies]
bech32 = "0.9.0"
chrono = "0.4.2"
uint = { version = "0.9.5", default-features = false }
fuel-abi-types = { workspace = true }
fuel-asm = { workspace = true }
fuel-core = { workspace = true, default-features = false, optional = true }
Expand All @@ -32,6 +31,7 @@ sha2 = { workspace = true }
strum = { workspace = true }
strum_macros = { workspace = true }
thiserror = { workspace = true, default-features = false }
uint = { version = "0.9.5", default-features = false }

[features]
default = ["std"]
Expand Down
2 changes: 2 additions & 0 deletions packages/fuels-test-helpers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use std::net::SocketAddr;
#[cfg(feature = "fuels-accounts")]
pub use accounts::*;
#[cfg(feature = "fuel-core-lib")]
pub use fuel_core::service::DbType;
#[cfg(feature = "fuel-core-lib")]
use fuel_core::service::FuelService;
#[cfg(feature = "fuel-core-lib")]
pub use fuel_core::service::{config::Trigger, Config};
Expand Down
44 changes: 42 additions & 2 deletions packages/fuels-test-helpers/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::{
fmt,
io::Write,
net::{Ipv4Addr, SocketAddr},
path::PathBuf,
process::Stdio,
time::Duration,
};
Expand All @@ -25,6 +26,8 @@ use tempfile::NamedTempFile;
use tokio::{process::Command, sync::oneshot};

use crate::utils::{into_coin_configs, into_message_configs};
// Set the cache for tests to 10MB, which is the default size in `fuel-core`.
pub const DEFAULT_CACHE_SIZE: usize = 10 * 1024 * 1024;

#[derive(Clone, Debug)]
pub enum Trigger {
Expand All @@ -40,9 +43,18 @@ pub enum Trigger {
},
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum DbType {
InMemory,
RocksDb,
}

#[derive(Clone, Debug)]
pub struct Config {
pub addr: SocketAddr,
pub max_database_cache_size: usize,
pub database_path: PathBuf,
pub database_type: DbType,
pub utxo_validation: bool,
pub manual_blocks_enabled: bool,
pub block_production: Trigger,
Expand All @@ -54,6 +66,9 @@ impl Config {
pub fn local_node() -> Self {
Self {
addr: SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), 0),
max_database_cache_size: DEFAULT_CACHE_SIZE,
database_path: Default::default(),
database_type: DbType::InMemory,
utxo_validation: false,
manual_blocks_enabled: false,
block_production: Trigger::Instant,
Expand Down Expand Up @@ -221,6 +236,7 @@ pub async fn new_fuel_node(

tokio::spawn(async move {
let config_json = get_node_config_json(coins, messages, chain_config);

let temp_config_file = write_temp_config_file(config_json);

let port = config.addr.port().to_string();
Expand All @@ -230,12 +246,36 @@ pub async fn new_fuel_node(
"127.0.0.1".to_string(),
"--port".to_string(),
port,
"--db-type".to_string(),
"in-memory".to_string(),
"--chain".to_string(),
temp_config_file.path().to_str().unwrap().to_string(),
];

args.extend(vec![
"--db-type".to_string(),
match config.database_type {
DbType::InMemory => "in-memory",
DbType::RocksDb => "rocks-db",
}
.to_string(),
]);

if let DbType::RocksDb = config.database_type {
let path = if config.database_path.as_os_str().is_empty() {
PathBuf::from(std::env::var("HOME").expect("HOME env var missing")).join(".fuel/db")
} else {
config.database_path
};
args.extend(vec![
"--db-path".to_string(),
path.to_string_lossy().to_string(),
]);
}

if config.max_database_cache_size != DEFAULT_CACHE_SIZE {
args.push("--max-database-cache-size".to_string());
args.push(config.max_database_cache_size.to_string());
}

if config.utxo_validation {
args.push("--utxo-validation".to_string());
}
Expand Down
2 changes: 2 additions & 0 deletions packages/fuels/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ fuel-core = { workspace = true, default-features = false }
fuel-core-types = { workspace = true }
hex = { workspace = true, default-features = false }
sha2 = { workspace = true }
tempfile = { workspace = true }
tokio = { workspace = true }

[features]
Expand All @@ -46,3 +47,4 @@ std = [
# TODO: To be removed once https://github.com/FuelLabs/fuels-rs/issues/881 is unblocked.
test-type-paths = []
fuel-core-lib = ["fuels-test-helpers?/fuel-core-lib", "dep:fuel-core"]
rocksdb = ["fuel-core?/rocksdb"]
4 changes: 2 additions & 2 deletions packages/fuels/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ pub mod fuel_node {
#[cfg(feature = "fuel-core-lib")]
pub use fuel_core::chain_config::ChainConfig;
#[cfg(feature = "fuel-core-lib")]
pub use fuel_core::service::{config::Trigger, Config, FuelService};
pub use fuel_core::service::{config::Trigger, Config, DbType, FuelService};
#[cfg(not(feature = "fuel-core-lib"))]
pub use fuels_test_helpers::node::{ChainConfig, Config, FuelService, Trigger};
pub use fuels_test_helpers::node::{ChainConfig, Config, DbType, FuelService, Trigger};
}

/// Easy imports of frequently used
Expand Down
121 changes: 121 additions & 0 deletions packages/fuels/tests/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1321,3 +1321,124 @@ async fn low_level_call() -> Result<()> {

Ok(())
}

#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
#[test]
fn db_rocksdb() {
use fuels::accounts::fuel_crypto::SecretKey;
use fuels::accounts::wallet::WalletUnlocked;
use fuels::client::{PageDirection, PaginationRequest};
use fuels::prelude::DEFAULT_COIN_AMOUNT;
use fuels::prelude::{setup_test_provider, Config, DbType, ViewOnlyAccount};
use std::fs;
use std::str::FromStr;

let temp_dir = tempfile::tempdir()
.expect("Failed to make tempdir")
.into_path();
let temp_dir_name = temp_dir
.file_name()
.expect("Failed to get file name")
.to_string_lossy()
.to_string();
let temp_database_path = temp_dir.join("db");

tokio::runtime::Runtime::new()
.expect("Tokio runtime failed")
.block_on(async {
let wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)
.unwrap(),
None,
);

const NUMBER_OF_ASSETS: u64 = 2;

let mut node_config = Config {
database_path: temp_database_path.clone(),
database_type: DbType::RocksDb,
..Config::local_node()
};

node_config.manual_blocks_enabled = true;

let chain_config = ChainConfig {
chain_name: temp_dir_name.clone(),
initial_state: None,
transaction_parameters: Default::default(),
..ChainConfig::local_testnet()
};

let (coins, _) = setup_multiple_assets_coins(
wallet.address(),
NUMBER_OF_ASSETS,
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);

let (provider, _) =
setup_test_provider(coins.clone(), vec![], Some(node_config), Some(chain_config))
.await;

provider.produce_blocks(2, None).await?;

Ok::<(), Box<dyn std::error::Error>>(())
})
.unwrap();

// The runtime needs to be terminated because the node can currently only be killed when the runtime itself shuts down.

tokio::runtime::Runtime::new()
.expect("Tokio runtime failed")
.block_on(async {
let node_config = Config {
database_path: temp_database_path.clone(),
database_type: DbType::RocksDb,
..Config::local_node()
};

let (provider, _) = setup_test_provider(vec![], vec![], Some(node_config), None).await;
// the same wallet that was used when rocksdb was built. When we connect it to the provider, we expect it to have the same amount of assets
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)
.unwrap(),
None,
);

wallet.set_provider(provider.clone());

let blocks = provider
.get_blocks(PaginationRequest {
cursor: None,
results: 10,
direction: PageDirection::Forward,
})
.await?
.results;

assert_eq!(provider.chain_info().await?.name, temp_dir_name);
assert_eq!(blocks.len(), 3);
assert_eq!(
*wallet.get_balances().await?.iter().next().unwrap().1,
DEFAULT_COIN_AMOUNT
);
assert_eq!(
*wallet.get_balances().await?.iter().next().unwrap().1,
DEFAULT_COIN_AMOUNT
);
assert_eq!(wallet.get_balances().await?.len(), 2);

fs::remove_dir_all(
temp_database_path
.parent()
.expect("Db parent folder does not exist"),
)?;

Ok::<(), Box<dyn std::error::Error>>(())
})
.unwrap();
}

0 comments on commit 294f91b

Please sign in to comment.