From 3b84b171fee89a0477398b0740610a297fa1b185 Mon Sep 17 00:00:00 2001 From: Mike the Tike Date: Fri, 12 Nov 2021 12:39:34 +0200 Subject: [PATCH] feat: add storage to validator node (#3551) Add state and chain storage to the validator node. Still a while to go, but this should be a good start --- .gitignore | 1 + Cargo.lock | 29 +- Cargo.toml | 2 +- applications/tari_app_grpc/build.rs | 9 +- .../tari_app_grpc/proto/base_node.proto | 14 +- .../proto}/validator_node.proto | 3 +- .../src/grpc/base_node_grpc_server.rs | 65 +++- .../tari_collectibles/src-tauri/Cargo.toml | 4 + .../tari_collectibles/src-tauri}/diesel.toml | 0 .../src-tauri}/migrations/.gitkeep | 0 .../2021-11-10-113031_init_schema/down.sql | 1 + .../2021-11-10-113031_init_schema/up.sql | 9 + .../src-tauri/src/app_state.rs | 34 ++- .../src/{ => clients}/base_node_client.rs | 66 +++++ .../src-tauri/src/clients/mod.rs | 28 ++ .../src/clients/validator_node_client.rs | 44 +++ .../src/{ => clients}/wallet_client.rs | 0 .../src-tauri/src/commands/accounts/mod.rs | 89 ++++++ .../src-tauri/src/commands/mod.rs | 2 + .../src-tauri/src/commands/tips/mod.rs | 23 ++ .../src-tauri/src/commands/tips/tip002.rs | 53 ++++ .../tari_collectibles/src-tauri/src/main.rs | 16 +- .../src-tauri/src/models/account.rs | 43 +++ .../src-tauri/src/models/mod.rs | 5 +- .../src-tauri/src/models/tip002_info.rs | 29 ++ .../tari_collectibles/src-tauri/src/schema.rs | 11 + .../src-tauri/src/settings.rs | 12 +- .../src-tauri/src/storage/mod.rs | 38 +++ .../src-tauri/src/storage/sqlite/mod.rs | 166 +++++++++++ .../src/storage/sqlite/models/account.rs | 35 +++ .../src/storage/sqlite/models/mod.rs | 24 ++ .../src-tauri/src/storage/storage_error.rs | 43 +++ .../tari_collectibles/src-tauri/temp.sqlite | Bin 0 -> 24576 bytes .../web-app/src/AccountDashboard.js | 54 ++++ .../tari_collectibles/web-app/src/App.js | 45 ++- .../web-app/src/NewAccount.js | 89 ++++++ .../tari_collectibles/web-app/src/binding.js | 10 + .../tari_collectibles/web-app/src/helpers.js | 27 ++ applications/tari_validator_node/Cargo.toml | 1 + applications/tari_validator_node/build.rs | 5 - .../proto/p2p/dan_consensus.proto | 4 +- .../tari_validator_node/src/dan_node.rs | 14 +- .../src/grpc/conversions.rs | 10 +- .../tari_validator_node/src/grpc/mod.rs | 4 - .../src/grpc/validator_node_grpc_server.rs | 2 +- applications/tari_validator_node/src/main.rs | 12 +- .../src/p2p/proto/conversions/dan.rs | 18 +- .../comms_interface/comms_request.rs | 6 + .../comms_interface/comms_response.rs | 4 + .../comms_interface/inbound_handlers.rs | 9 + .../comms_interface/local_interface.rs | 14 + base_layer/wallet/src/types.rs | 1 - dan_layer/core/proto/tips/tip001.proto | 12 + dan_layer/core/proto/tips/tip002.proto | 22 ++ dan_layer/core/src/digital_assets_error.rs | 2 + dan_layer/core/src/models/asset_definition.rs | 29 +- .../core/src/models/hot_stuff_message.rs | 75 ++++- .../core/src/models/hot_stuff_tree_node.rs | 14 +- dan_layer/core/src/models/mod.rs | 44 ++- .../core/src/models/quorum_certificate.rs | 20 +- dan_layer/core/src/models/state_root.rs | 28 ++ dan_layer/core/src/models/tari_dan_payload.rs | 8 +- dan_layer/core/src/models/view_id.rs | 20 +- .../core/src/services/asset_processor.rs | 6 +- .../node_addressable.rs | 7 +- .../core/src/services/payload_processor.rs | 24 +- dan_layer/core/src/storage/chain_db.rs | 277 ++++++++++++++++++ .../core/src/storage/chain_storage_service.rs | 8 +- dan_layer/core/src/storage/error.rs | 10 + dan_layer/core/src/storage/mod.rs | 223 ++++++-------- .../core/src/workers/consensus_worker.rs | 96 +++--- .../core/src/workers/states/commit_state.rs | 51 ++-- .../core/src/workers/states/decide_state.rs | 66 ++--- .../core/src/workers/states/next_view.rs | 7 +- .../src/workers/states/pre_commit_state.rs | 36 +-- dan_layer/core/src/workers/states/prepare.rs | 109 +++++-- dan_layer/core/src/workers/states/starting.rs | 28 +- dan_layer/storage_sqlite/Cargo.toml | 17 ++ dan_layer/storage_sqlite/diesel.toml | 5 + dan_layer/storage_sqlite/migrations/.gitkeep | 0 .../2021-11-02-185150_create_nodes/down.sql | 0 .../2021-11-02-185150_create_nodes/up.sql | 43 +++ dan_layer/storage_sqlite/src/error.rs | 59 ++++ .../src/lib.rs | 12 + .../storage_sqlite/src/models/instruction.rs | 42 +++ .../storage_sqlite/src/models/locked_qc.rs | 30 ++ dan_layer/storage_sqlite/src/models/mod.rs | 27 ++ dan_layer/storage_sqlite/src/models/node.rs | 40 +++ .../storage_sqlite/src/models/prepare_qc.rs | 30 ++ .../src/models/state_key_value.rs | 39 +++ dan_layer/storage_sqlite/src/schema.rs | 53 ++++ .../src/sqlite_backend_adapter.rs | 252 ++++++++++++++++ .../storage_sqlite/src/sqlite_db_factory.rs | 64 ++++ .../src/sqlite_storage_service.rs} | 41 +-- .../storage_sqlite/src/sqlite_transaction.rs | 37 +++ dan_layer/validator_node_sqlite/Cargo.toml | 10 - .../2021-11-02-185150_create_nodes/up.sql | 15 - dan_layer/validator_node_sqlite/src/schema.rs | 20 -- 98 files changed, 2778 insertions(+), 507 deletions(-) rename applications/{tari_validator_node/proto/grpc => tari_app_grpc/proto}/validator_node.proto (98%) rename {dan_layer/validator_node_sqlite => applications/tari_collectibles/src-tauri}/diesel.toml (100%) rename {dan_layer/validator_node_sqlite => applications/tari_collectibles/src-tauri}/migrations/.gitkeep (100%) create mode 100644 applications/tari_collectibles/src-tauri/migrations/2021-11-10-113031_init_schema/down.sql create mode 100644 applications/tari_collectibles/src-tauri/migrations/2021-11-10-113031_init_schema/up.sql rename applications/tari_collectibles/src-tauri/src/{ => clients}/base_node_client.rs (58%) create mode 100644 applications/tari_collectibles/src-tauri/src/clients/mod.rs create mode 100644 applications/tari_collectibles/src-tauri/src/clients/validator_node_client.rs rename applications/tari_collectibles/src-tauri/src/{ => clients}/wallet_client.rs (100%) create mode 100644 applications/tari_collectibles/src-tauri/src/commands/accounts/mod.rs create mode 100644 applications/tari_collectibles/src-tauri/src/commands/tips/mod.rs create mode 100644 applications/tari_collectibles/src-tauri/src/commands/tips/tip002.rs create mode 100644 applications/tari_collectibles/src-tauri/src/models/account.rs create mode 100644 applications/tari_collectibles/src-tauri/src/models/tip002_info.rs create mode 100644 applications/tari_collectibles/src-tauri/src/schema.rs create mode 100644 applications/tari_collectibles/src-tauri/src/storage/mod.rs create mode 100644 applications/tari_collectibles/src-tauri/src/storage/sqlite/mod.rs create mode 100644 applications/tari_collectibles/src-tauri/src/storage/sqlite/models/account.rs create mode 100644 applications/tari_collectibles/src-tauri/src/storage/sqlite/models/mod.rs create mode 100644 applications/tari_collectibles/src-tauri/src/storage/storage_error.rs create mode 100644 applications/tari_collectibles/src-tauri/temp.sqlite create mode 100644 applications/tari_collectibles/web-app/src/AccountDashboard.js create mode 100644 applications/tari_collectibles/web-app/src/NewAccount.js create mode 100644 applications/tari_collectibles/web-app/src/helpers.js create mode 100644 dan_layer/core/proto/tips/tip001.proto create mode 100644 dan_layer/core/proto/tips/tip002.proto create mode 100644 dan_layer/core/src/models/state_root.rs create mode 100644 dan_layer/core/src/storage/chain_db.rs create mode 100644 dan_layer/storage_sqlite/Cargo.toml create mode 100644 dan_layer/storage_sqlite/diesel.toml create mode 100644 dan_layer/storage_sqlite/migrations/.gitkeep rename dan_layer/{validator_node_sqlite => storage_sqlite}/migrations/2021-11-02-185150_create_nodes/down.sql (100%) create mode 100644 dan_layer/storage_sqlite/migrations/2021-11-02-185150_create_nodes/up.sql create mode 100644 dan_layer/storage_sqlite/src/error.rs rename dan_layer/{validator_node_sqlite => storage_sqlite}/src/lib.rs (80%) create mode 100644 dan_layer/storage_sqlite/src/models/instruction.rs create mode 100644 dan_layer/storage_sqlite/src/models/locked_qc.rs create mode 100644 dan_layer/storage_sqlite/src/models/mod.rs create mode 100644 dan_layer/storage_sqlite/src/models/node.rs create mode 100644 dan_layer/storage_sqlite/src/models/prepare_qc.rs create mode 100644 dan_layer/storage_sqlite/src/models/state_key_value.rs create mode 100644 dan_layer/storage_sqlite/src/schema.rs create mode 100644 dan_layer/storage_sqlite/src/sqlite_backend_adapter.rs create mode 100644 dan_layer/storage_sqlite/src/sqlite_db_factory.rs rename dan_layer/{core/src/storage/sqlite/mod.rs => storage_sqlite/src/sqlite_storage_service.rs} (74%) create mode 100644 dan_layer/storage_sqlite/src/sqlite_transaction.rs delete mode 100644 dan_layer/validator_node_sqlite/Cargo.toml delete mode 100644 dan_layer/validator_node_sqlite/migrations/2021-11-02-185150_create_nodes/up.sql delete mode 100644 dan_layer/validator_node_sqlite/src/schema.rs diff --git a/.gitignore b/.gitignore index e94d175872..f3128c3fcc 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ node_modules # ignore output files from windows ISS buildtools/Output/ +/applications/tari_collectibles/src-tauri/data \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index d6333968b7..7e10b717e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6087,6 +6087,8 @@ name = "tari_collectibles" version = "0.1.0" dependencies = [ "blake2", + "diesel", + "diesel_migrations", "futures 0.3.17", "rand 0.8.4", "serde 1.0.130", @@ -6098,7 +6100,9 @@ dependencies = [ "tari_utilities", "tauri", "tauri-build", + "thiserror", "tonic", + "uuid", ] [[package]] @@ -6418,6 +6422,21 @@ dependencies = [ "tonic", ] +[[package]] +name = "tari_dan_storage_sqlite" +version = "0.1.0" +dependencies = [ + "async-trait", + "diesel", + "diesel_migrations", + "dotenv", + "tari_common", + "tari_dan_core", + "thiserror", + "tokio 1.13.0", + "tokio-stream", +] + [[package]] name = "tari_infra_derive" version = "0.13.0" @@ -6748,6 +6767,7 @@ dependencies = [ "tari_core", "tari_crypto", "tari_dan_core", + "tari_dan_storage_sqlite", "tari_mmr", "tari_p2p", "tari_service_framework", @@ -7923,14 +7943,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ "getrandom 0.2.3", -] - -[[package]] -name = "validator_node_sqlite" -version = "0.1.0" -dependencies = [ - "diesel", - "dotenv", + "serde 1.0.130", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d066db85dc..166b4d0533 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ members = [ "comms/dht", "comms/rpc_macros", "dan_layer/core", - "dan_layer/validator_node_sqlite", + "dan_layer/storage_sqlite", "infrastructure/shutdown", "infrastructure/storage", "infrastructure/test_utils", diff --git a/applications/tari_app_grpc/build.rs b/applications/tari_app_grpc/build.rs index 8d75fb8e6c..bff25c2e61 100644 --- a/applications/tari_app_grpc/build.rs +++ b/applications/tari_app_grpc/build.rs @@ -3,6 +3,13 @@ fn main() -> Result<(), Box> { .build_client(true) .build_server(true) .format(false) - .compile(&["proto/base_node.proto", "proto/wallet.proto"], &["proto"])?; + .compile( + &[ + "proto/base_node.proto", + "proto/wallet.proto", + "proto/validator_node.proto", + ], + &["proto"], + )?; Ok(()) } diff --git a/applications/tari_app_grpc/proto/base_node.proto b/applications/tari_app_grpc/proto/base_node.proto index 0d98660415..a371b6dc68 100644 --- a/applications/tari_app_grpc/proto/base_node.proto +++ b/applications/tari_app_grpc/proto/base_node.proto @@ -21,7 +21,6 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. syntax = "proto3"; - import "types.proto"; package tari.rpc; @@ -85,6 +84,19 @@ service BaseNode { rpc GetTokens(GetTokensRequest) returns (stream GetTokensResponse); rpc ListAssetRegistrations(ListAssetRegistrationsRequest) returns (stream ListAssetRegistrationsResponse); + rpc GetAssetMetadata(GetAssetMetadataRequest) returns (GetAssetMetadataResponse); +} + +message GetAssetMetadataRequest { + bytes asset_public_key = 1; +} + +message GetAssetMetadataResponse { + optional string name = 2; + optional string description =3; + optional string image = 4; + bytes owner_commitment = 5; + OutputFeatures features = 6; } message ListAssetRegistrationsRequest { diff --git a/applications/tari_validator_node/proto/grpc/validator_node.proto b/applications/tari_app_grpc/proto/validator_node.proto similarity index 98% rename from applications/tari_validator_node/proto/grpc/validator_node.proto rename to applications/tari_app_grpc/proto/validator_node.proto index c0971aeeb9..bda36b219f 100644 --- a/applications/tari_validator_node/proto/grpc/validator_node.proto +++ b/applications/tari_app_grpc/proto/validator_node.proto @@ -21,7 +21,8 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. syntax = "proto3"; -package tari.validator_node.rpc; +import "types.proto"; +package tari.rpc; service ValidatorNode { rpc GetMetadata(GetMetadataRequest) returns (GetMetadataResponse); diff --git a/applications/tari_base_node/src/grpc/base_node_grpc_server.rs b/applications/tari_base_node/src/grpc/base_node_grpc_server.rs index 4563dc6270..a6c5cdbd57 100644 --- a/applications/tari_base_node/src/grpc/base_node_grpc_server.rs +++ b/applications/tari_base_node/src/grpc/base_node_grpc_server.rs @@ -47,7 +47,7 @@ use tari_core::{ StateMachineHandle, }, blocks::{Block, BlockHeader, NewBlockTemplate}, - chain_storage::ChainStorageError, + chain_storage::{ChainStorageError, PrunedOutput}, consensus::{emission::Emission, ConsensusManager, NetworkConsensus}, iterators::NonOverlappingIntegerPairIter, mempool::{service::LocalMempoolService, TxStorageResponse}, @@ -464,6 +464,69 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { Ok(Response::new(rx)) } + async fn get_asset_metadata( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + + let mut handler = self.node_service.clone(); + let metadata = handler + .get_asset_metadata( + PublicKey::from_bytes(&request.asset_public_key) + .map_err(|e| Status::invalid_argument("Not a valid asset public key"))?, + ) + .await + .map_err(|e| Status::internal(e.to_string()))?; + + if let Some(m) = metadata { + match m.output { + PrunedOutput::Pruned { + output_hash, + witness_hash, + } => return Err(Status::not_found("Output has been pruned")), + PrunedOutput::NotPruned { output } => { + if let Some(ref asset) = output.features.asset { + const ASSET_METADATA_TEMPLATE_ID: u32 = 1; + if asset.template_ids_implemented.contains(&ASSET_METADATA_TEMPLATE_ID) { + // TODO: move to a better location, or better yet, have the grpc caller split the metadata + let m = String::from_utf8(output.features.metadata.clone()).unwrap(); + let mut m = m + .as_str() + .split('|') + .map(|s| s.to_string()) + .collect::>() + .into_iter(); + let name = m.next(); + let description = m.next(); + let image = m.next(); + + // TODO Perhaps this should just return metadata and have the client read the metadata in a + // pattern described by the template + return Ok(Response::new(tari_rpc::GetAssetMetadataResponse { + name, + description, + image, + owner_commitment: Vec::from(output.commitment.as_bytes()), + features: Some(output.features.clone().into()), + })); + } + } + return Ok(Response::new(tari_rpc::GetAssetMetadataResponse { + name: None, + description: None, + image: None, + owner_commitment: Vec::from(output.commitment.as_bytes()), + features: Some(output.features.clone().into()), + })); + }, + }; + Err(Status::unknown("Could not find a matching arm")) + } else { + Err(Status::not_found("Could not find any utxo")) + } + } + async fn list_asset_registrations( &self, request: Request, diff --git a/applications/tari_collectibles/src-tauri/Cargo.toml b/applications/tari_collectibles/src-tauri/Cargo.toml index 7c8db7d842..eebfd923f5 100644 --- a/applications/tari_collectibles/src-tauri/Cargo.toml +++ b/applications/tari_collectibles/src-tauri/Cargo.toml @@ -28,6 +28,10 @@ serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } tonic = "0.5.2" tauri = { version = "1.0.0-beta.8", features = ["api-all"] } +diesel = { version = "1.4.8", features = ["sqlite"] } +diesel_migrations = "1.4.0" +thiserror = "1.0.30" +uuid = { version = "0.8.2", features = ["serde"] } [features] default = [ "custom-protocol" ] diff --git a/dan_layer/validator_node_sqlite/diesel.toml b/applications/tari_collectibles/src-tauri/diesel.toml similarity index 100% rename from dan_layer/validator_node_sqlite/diesel.toml rename to applications/tari_collectibles/src-tauri/diesel.toml diff --git a/dan_layer/validator_node_sqlite/migrations/.gitkeep b/applications/tari_collectibles/src-tauri/migrations/.gitkeep similarity index 100% rename from dan_layer/validator_node_sqlite/migrations/.gitkeep rename to applications/tari_collectibles/src-tauri/migrations/.gitkeep diff --git a/applications/tari_collectibles/src-tauri/migrations/2021-11-10-113031_init_schema/down.sql b/applications/tari_collectibles/src-tauri/migrations/2021-11-10-113031_init_schema/down.sql new file mode 100644 index 0000000000..3af2f77426 --- /dev/null +++ b/applications/tari_collectibles/src-tauri/migrations/2021-11-10-113031_init_schema/down.sql @@ -0,0 +1 @@ +-- lol, no \ No newline at end of file diff --git a/applications/tari_collectibles/src-tauri/migrations/2021-11-10-113031_init_schema/up.sql b/applications/tari_collectibles/src-tauri/migrations/2021-11-10-113031_init_schema/up.sql new file mode 100644 index 0000000000..f764251842 --- /dev/null +++ b/applications/tari_collectibles/src-tauri/migrations/2021-11-10-113031_init_schema/up.sql @@ -0,0 +1,9 @@ +create table accounts ( + id blob not null primary key, + asset_public_key blob not null unique, + name text, + description text, + image text, + committee_length integer not null, + committee_pub_keys blob not null +); \ No newline at end of file diff --git a/applications/tari_collectibles/src-tauri/src/app_state.rs b/applications/tari_collectibles/src-tauri/src/app_state.rs index 6e13a62617..c0f4256565 100644 --- a/applications/tari_collectibles/src-tauri/src/app_state.rs +++ b/applications/tari_collectibles/src-tauri/src/app_state.rs @@ -20,12 +20,22 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::{base_node_client::BaseNodeClient, settings::Settings, wallet_client::WalletClient}; +use crate::{ + clients::{BaseNodeClient, GrpcValidatorNodeClient, ValidatorNodeClient, WalletClient}, + settings::Settings, + storage::{ + sqlite::{SqliteCollectiblesStorage, SqliteDbFactory}, + StorageError, + }, +}; +use diesel::SqliteConnection; use std::sync::Arc; +use tari_common_types::types::PublicKey; use tauri::async_runtime::RwLock; pub struct AppState { config: Settings, + db_factory: SqliteDbFactory, } #[derive(Clone)] @@ -35,9 +45,11 @@ pub struct ConcurrentAppState { impl ConcurrentAppState { pub fn new() -> Self { + let settings = Settings::new(); Self { inner: Arc::new(RwLock::new(AppState { - config: Settings::new(), + db_factory: SqliteDbFactory::new(settings.data_dir.as_path()), + config: settings, })), } } @@ -52,4 +64,22 @@ impl ConcurrentAppState { BaseNodeClient::connect(format!("http://{}", lock.config.base_node_grpc_address)).await?; Ok(client) } + + pub async fn connect_validator_node_client( + &self, + _public_key: PublicKey, + ) -> Result { + // todo: convert this GRPC to tari comms + let lock = self.inner.read().await; + let client = GrpcValidatorNodeClient::connect(format!( + "http://{}", + lock.config.validator_node_grpc_address + )) + .await?; + Ok(client) + } + + pub async fn create_db(&self) -> Result { + self.inner.read().await.db_factory.create_db() + } } diff --git a/applications/tari_collectibles/src-tauri/src/base_node_client.rs b/applications/tari_collectibles/src-tauri/src/clients/base_node_client.rs similarity index 58% rename from applications/tari_collectibles/src-tauri/src/base_node_client.rs rename to applications/tari_collectibles/src-tauri/src/clients/base_node_client.rs index 60560b2690..5c172ec1cf 100644 --- a/applications/tari_collectibles/src-tauri/src/base_node_client.rs +++ b/applications/tari_collectibles/src-tauri/src/clients/base_node_client.rs @@ -22,6 +22,8 @@ use futures::StreamExt; use tari_app_grpc::tari_rpc as grpc; +use tari_common_types::types::PublicKey; +use tari_utilities::ByteArray; pub struct BaseNodeClient { client: grpc::base_node_client::BaseNodeClient, @@ -63,6 +65,70 @@ impl BaseNodeClient { Ok(assets) } + pub async fn get_asset_metadata( + &mut self, + asset_public_key: &PublicKey, + ) -> Result { + let client = self.client_mut(); + let request = grpc::GetAssetMetadataRequest { + asset_public_key: Vec::from(asset_public_key.as_bytes()), + }; + dbg!(&request); + let response = client + .get_asset_metadata(request) + .await + .map(|response| response.into_inner()) + .map_err(|s| format!("Could not get asset metadata: {}", s))?; + dbg!(&response); + Ok(response) + } + + // TODO: probably can get the full checkpoint instead + pub async fn get_sidechain_committee( + &mut self, + asset_public_key: &PublicKey, + ) -> Result, String> { + let client = self.client_mut(); + let request = grpc::GetTokensRequest { + asset_public_key: Vec::from(asset_public_key.as_bytes()), + unique_ids: vec![vec![3u8; 32]], + }; + + dbg!(&request); + let mut stream = client + .get_tokens(request) + .await + .map(|response| response.into_inner()) + .map_err(|s| format!("Could not get asset sidechain checkpoint"))?; + let mut i = 0; + // Could def do this better + #[allow(clippy::never_loop)] + while let Some(response) = stream.next().await { + i += 1; + if i > 10 { + break; + } + dbg!(&response); + let features = response + .map_err(|status| format!("Got an error status from GRPC:{}", status))? + .features; + if let Some(sidechain) = features.and_then(|f| f.sidechain_checkpoint) { + let pub_keys = sidechain + .committee + .iter() + .map(|s| PublicKey::from_bytes(s).map_err(|e| format!("Not a valid public key:{}", e))) + .collect::>()?; + return Ok(pub_keys); + } else { + return Err("Found utxo but was missing sidechain data".to_string()); + } + } + Err(format!( + "No side chain tokens were found out of {} streamed", + i + )) + } + fn client_mut( &mut self, ) -> &mut grpc::base_node_client::BaseNodeClient { diff --git a/applications/tari_collectibles/src-tauri/src/clients/mod.rs b/applications/tari_collectibles/src-tauri/src/clients/mod.rs new file mode 100644 index 0000000000..04c83bc776 --- /dev/null +++ b/applications/tari_collectibles/src-tauri/src/clients/mod.rs @@ -0,0 +1,28 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod base_node_client; +pub use base_node_client::BaseNodeClient; +mod wallet_client; +pub use wallet_client::WalletClient; +mod validator_node_client; +pub use validator_node_client::{GrpcValidatorNodeClient, ValidatorNodeClient}; diff --git a/applications/tari_collectibles/src-tauri/src/clients/validator_node_client.rs b/applications/tari_collectibles/src-tauri/src/clients/validator_node_client.rs new file mode 100644 index 0000000000..5beabd74fe --- /dev/null +++ b/applications/tari_collectibles/src-tauri/src/clients/validator_node_client.rs @@ -0,0 +1,44 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use tari_app_grpc::tari_rpc as grpc; + +pub trait ValidatorNodeClient {} + +pub struct GrpcValidatorNodeClient { + client: grpc::validator_node_client::ValidatorNodeClient, +} + +impl GrpcValidatorNodeClient { + pub async fn connect(endpoint: String) -> Result { + let s = Self { + client: grpc::validator_node_client::ValidatorNodeClient::connect(endpoint.clone()) + .await + .map_err(|e| { + format!( + "No connection to validator node. Is it running with gRPC on '{}'? Error: {}", + endpoint, e + ) + })?, + }; + Ok(s) + } +} diff --git a/applications/tari_collectibles/src-tauri/src/wallet_client.rs b/applications/tari_collectibles/src-tauri/src/clients/wallet_client.rs similarity index 100% rename from applications/tari_collectibles/src-tauri/src/wallet_client.rs rename to applications/tari_collectibles/src-tauri/src/clients/wallet_client.rs diff --git a/applications/tari_collectibles/src-tauri/src/commands/accounts/mod.rs b/applications/tari_collectibles/src-tauri/src/commands/accounts/mod.rs new file mode 100644 index 0000000000..a7bd304fae --- /dev/null +++ b/applications/tari_collectibles/src-tauri/src/commands/accounts/mod.rs @@ -0,0 +1,89 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use crate::{ + app_state::ConcurrentAppState, + models::{Account, NewAccount}, + storage::{AccountsTableGateway, CollectiblesStorage}, +}; +use tari_common_types::types::PublicKey; +use tari_utilities::hex::Hex; + +#[tauri::command] +pub(crate) async fn accounts_create( + asset_pub_key: String, + state: tauri::State<'_, ConcurrentAppState>, +) -> Result { + let asset_pub_key = + PublicKey::from_hex(asset_pub_key.as_str()).map_err(|e| format!("Invalid public key:{}", e))?; + let mut new_account = NewAccount { + asset_public_key: asset_pub_key.clone(), + name: None, + description: None, + image: None, + committee: None, + }; + + let mut client = state.connect_base_node_client().await?; + let chain_registration_data = client.get_asset_metadata(&asset_pub_key).await?; + new_account.name = chain_registration_data.name.clone(); + new_account.description = chain_registration_data.description.clone(); + new_account.image = chain_registration_data.image.clone(); + + let sidechain_committee = match client.get_sidechain_committee(&asset_pub_key).await { + Ok(s) => { + if s.is_empty() { + None + } else { + Some(s) + } + } + Err(e) => { + dbg!(e); + None + } + }; + new_account.committee = sidechain_committee; + + let result = state + .create_db() + .await + .map_err(|e| format!("Could not connect to DB:{}", e))? + .accounts() + .insert(new_account) + .map_err(|e| format!("Could not save account: {}", e))?; + Ok(result) +} + +#[tauri::command] +pub(crate) async fn accounts_list( + state: tauri::State<'_, ConcurrentAppState>, +) -> Result, String> { + let db = state + .create_db() + .await + .map_err(|e| format!("Could not connect to DB:{}", e))?; + let result = db + .accounts() + .list() + .map_err(|e| format!("Could list accounts from DB: {}", e))?; + Ok(result) +} diff --git a/applications/tari_collectibles/src-tauri/src/commands/mod.rs b/applications/tari_collectibles/src-tauri/src/commands/mod.rs index 546e37fb99..f16bd7b166 100644 --- a/applications/tari_collectibles/src-tauri/src/commands/mod.rs +++ b/applications/tari_collectibles/src-tauri/src/commands/mod.rs @@ -20,4 +20,6 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +pub mod accounts; pub mod assets; +pub mod tips; diff --git a/applications/tari_collectibles/src-tauri/src/commands/tips/mod.rs b/applications/tari_collectibles/src-tauri/src/commands/tips/mod.rs new file mode 100644 index 0000000000..e1cc5afffa --- /dev/null +++ b/applications/tari_collectibles/src-tauri/src/commands/tips/mod.rs @@ -0,0 +1,23 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +pub mod tip002; diff --git a/applications/tari_collectibles/src-tauri/src/commands/tips/tip002.rs b/applications/tari_collectibles/src-tauri/src/commands/tips/tip002.rs new file mode 100644 index 0000000000..9a9715e17f --- /dev/null +++ b/applications/tari_collectibles/src-tauri/src/commands/tips/tip002.rs @@ -0,0 +1,53 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::{ + app_state::ConcurrentAppState, + models::Tip002Info, + storage::{AccountsTableGateway, CollectiblesStorage}, +}; +use uuid::Uuid; + +#[tauri::command] +pub async fn tip002_get_info( + account_id: Uuid, + asset_public_key: String, + state: tauri::State<'_, ConcurrentAppState>, +) -> Result, String> { + // let db = state + // .create_db() + // .await + // .map_err(|e| format!("Could not open DB:{}", e))?; + // let account = db + // .accounts() + // .find(account_id) + // .map_err(|e| format!("Could not find account: {}", e))?; + // let committee = account.committee; + // let validator_client = state.connect_validator_node_client(committee[0]).await?; + // let asset_public_key = PublicKey::from_hex(asset_public_key + // let template_data = tari_tips::tip002::InfoRequest{ + // + // }; + // let template_data = template_data.encode_to_vec(); + // validator_client.call(comittee[0], "tip002", ) + todo!() +} diff --git a/applications/tari_collectibles/src-tauri/src/main.rs b/applications/tari_collectibles/src-tauri/src/main.rs index c624efa0e1..74a67eca37 100644 --- a/applications/tari_collectibles/src-tauri/src/main.rs +++ b/applications/tari_collectibles/src-tauri/src/main.rs @@ -1,3 +1,4 @@ +#![feature(array_methods)] #![cfg_attr( all(not(debug_assertions), target_os = "windows"), windows_subsystem = "windows" @@ -5,12 +6,19 @@ use crate::app_state::ConcurrentAppState; +#[macro_use] +extern crate diesel; + +#[macro_use] +extern crate diesel_migrations; + mod app_state; -mod base_node_client; +mod clients; mod commands; mod models; +mod schema; mod settings; -mod wallet_client; +mod storage; fn main() { let state = ConcurrentAppState::new(); @@ -21,7 +29,9 @@ fn main() { commands::assets::assets_create, commands::assets::assets_list_owned, commands::assets::assets_list_registered_assets, - commands::assets::assets_issue_simple_tokens + commands::assets::assets_issue_simple_tokens, + commands::accounts::accounts_create, + commands::accounts::accounts_list ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/applications/tari_collectibles/src-tauri/src/models/account.rs b/applications/tari_collectibles/src-tauri/src/models/account.rs new file mode 100644 index 0000000000..ce9f1f71e6 --- /dev/null +++ b/applications/tari_collectibles/src-tauri/src/models/account.rs @@ -0,0 +1,43 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use serde::{Deserialize, Serialize}; +use tari_common_types::types::PublicKey; +use uuid::Uuid; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Account { + pub id: Uuid, + pub asset_public_key: PublicKey, + pub name: Option, + pub description: Option, + pub image: Option, + pub committee: Option>, +} + +pub struct NewAccount { + pub asset_public_key: PublicKey, + pub name: Option, + pub description: Option, + pub image: Option, + pub committee: Option>, +} diff --git a/applications/tari_collectibles/src-tauri/src/models/mod.rs b/applications/tari_collectibles/src-tauri/src/models/mod.rs index 8659239d1e..ae96611dfb 100644 --- a/applications/tari_collectibles/src-tauri/src/models/mod.rs +++ b/applications/tari_collectibles/src-tauri/src/models/mod.rs @@ -22,6 +22,9 @@ mod asset_info; pub use asset_info::AssetInfo; - mod registered_asset_info; pub use registered_asset_info::RegisteredAssetInfo; +mod account; +pub use account::{Account, NewAccount}; +mod tip002_info; +pub use tip002_info::Tip002Info; diff --git a/applications/tari_collectibles/src-tauri/src/models/tip002_info.rs b/applications/tari_collectibles/src-tauri/src/models/tip002_info.rs new file mode 100644 index 0000000000..023c1ee83e --- /dev/null +++ b/applications/tari_collectibles/src-tauri/src/models/tip002_info.rs @@ -0,0 +1,29 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use serde::Serialize; +#[derive(Serialize)] +pub struct Tip002Info { + symbol: String, + decimals: u8, + total_supply: u128, // TODO: Should be 256 +} diff --git a/applications/tari_collectibles/src-tauri/src/schema.rs b/applications/tari_collectibles/src-tauri/src/schema.rs new file mode 100644 index 0000000000..4a5e97d3c6 --- /dev/null +++ b/applications/tari_collectibles/src-tauri/src/schema.rs @@ -0,0 +1,11 @@ +table! { + accounts (id) { + id -> Binary, + asset_public_key -> Binary, + name -> Nullable, + description -> Nullable, + image -> Nullable, + committee_length -> Integer, + committee_pub_keys -> Binary, + } +} diff --git a/applications/tari_collectibles/src-tauri/src/settings.rs b/applications/tari_collectibles/src-tauri/src/settings.rs index 99f42de261..c9c090587a 100644 --- a/applications/tari_collectibles/src-tauri/src/settings.rs +++ b/applications/tari_collectibles/src-tauri/src/settings.rs @@ -20,12 +20,13 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::env; +use std::{env, path::PathBuf}; pub struct Settings { pub(crate) wallet_grpc_address: String, pub(crate) base_node_grpc_address: String, - pub(crate) _favourite_assets: Vec, + pub(crate) validator_node_grpc_address: String, + pub(crate) data_dir: PathBuf, } impl Settings { @@ -35,14 +36,17 @@ impl Settings { // base_node_grpc_address: "localhost:18142".to_string(), // _favourite_assets: vec!["1234".to_string()], // } - + let data_dir = env::var("DATA_DIR").unwrap_or_else(|_| "data".to_string()); + let data_dir = PathBuf::from(data_dir); // TODO: remove this, just for convenience Self { wallet_grpc_address: env::var("WALLET_GRPC_ADDRESS") .unwrap_or_else(|_| "localhost:18143".to_string()), base_node_grpc_address: env::var("BASE_NODE_GRPC_ADDRESS") .unwrap_or_else(|_| "localhost:18142".to_string()), - _favourite_assets: vec!["1234".to_string()], + validator_node_grpc_address: env::var("VALIDATOR_NODE_GRPC_ADDRESS") + .unwrap_or_else(|_| "localhost:18144".to_string()), + data_dir, } } } diff --git a/applications/tari_collectibles/src-tauri/src/storage/mod.rs b/applications/tari_collectibles/src-tauri/src/storage/mod.rs new file mode 100644 index 0000000000..509cf84c4e --- /dev/null +++ b/applications/tari_collectibles/src-tauri/src/storage/mod.rs @@ -0,0 +1,38 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::models::{Account, NewAccount}; +pub mod sqlite; +mod storage_error; +pub use storage_error::StorageError; +use uuid::Uuid; + +pub trait CollectiblesStorage { + type Accounts: AccountsTableGateway; + fn accounts(&self) -> Self::Accounts; +} + +pub trait AccountsTableGateway { + fn list(&self) -> Result, StorageError>; + fn insert(&self, account: NewAccount) -> Result; + fn find(&self, account_id: Uuid) -> Result; +} diff --git a/applications/tari_collectibles/src-tauri/src/storage/sqlite/mod.rs b/applications/tari_collectibles/src-tauri/src/storage/sqlite/mod.rs new file mode 100644 index 0000000000..f2e34d60ea --- /dev/null +++ b/applications/tari_collectibles/src-tauri/src/storage/sqlite/mod.rs @@ -0,0 +1,166 @@ +use crate::{ + models::{Account, NewAccount}, + storage::{AccountsTableGateway, CollectiblesStorage, StorageError}, +}; +use diesel::{Connection, SqliteConnection}; +use std::path::Path; +use tari_utilities::ByteArray; +use uuid::Uuid; + +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +pub mod models; +use crate::schema::{self, accounts::dsl::accounts, *}; +use diesel::prelude::*; +use std::fs; +use tari_common_types::types::PublicKey; + +pub struct SqliteDbFactory { + database_url: String, +} +impl SqliteDbFactory { + pub fn new(data_dir: &Path) -> Self { + fs::create_dir_all(data_dir) + .expect(&format!("Could not create data directory: {:?}", data_dir)); + let database_url = data_dir + .join("collectibles.sqlite") + .into_os_string() + .into_string() + .unwrap(); + + Self { database_url } + } + + pub fn create_db(&self) -> Result { + let connection = SqliteConnection::establish(self.database_url.as_str())?; + connection.execute("PRAGMA foreign_keys = ON;"); + // Create the db + embed_migrations!("./migrations"); + embedded_migrations::run(&connection)?; + Ok(SqliteCollectiblesStorage { + database_url: self.database_url.clone(), + }) + } +} + +pub struct SqliteCollectiblesStorage { + database_url: String, +} + +impl CollectiblesStorage for SqliteCollectiblesStorage { + type Accounts = SqliteAccountsTableGateway; + + fn accounts(&self) -> Self::Accounts { + SqliteAccountsTableGateway { + database_url: self.database_url.clone(), + } + } +} + +pub struct SqliteAccountsTableGateway { + database_url: String, +} + +impl AccountsTableGateway for SqliteAccountsTableGateway { + fn list(&self) -> Result, StorageError> { + let conn = SqliteConnection::establish(self.database_url.as_str())?; + let results: Vec = schema::accounts::table + .order_by(schema::accounts::name.asc()) + .load(&conn)?; + Ok( + results + .iter() + .map(|r| SqliteAccountsTableGateway::convert_account(r)) + .collect::>()?, + ) + } + + fn insert(&self, account: NewAccount) -> Result { + let id = Uuid::new_v4(); + let mut committee_pub_keys = vec![]; + if let Some(pub_keys) = account.committee.as_ref() { + for key in pub_keys { + committee_pub_keys.extend_from_slice(key.as_bytes()); + } + } + // let committee_pub_keys = if committee_pub_keys.is_empty() { None} else {Some(committee_pub_keys)}; + + let sql_model = models::Account { + id: Vec::from(id.as_bytes().as_slice()), + asset_public_key: Vec::from(account.asset_public_key.as_bytes()), + name: account.name.clone(), + description: account.description.clone(), + image: account.image.clone(), + committee_length: account + .committee + .as_ref() + .map(|s| s.len() as i32) + .unwrap_or(0i32), + committee_pub_keys, + }; + let conn = SqliteConnection::establish(self.database_url.as_str())?; + + use crate::schema::accounts; + diesel::insert_into(accounts::table) + .values(sql_model) + .execute(&conn)?; + let result = Account { + id, + asset_public_key: account.asset_public_key, + name: account.name, + description: account.description, + image: account.image, + committee: account.committee, + }; + Ok(result) + } + + fn find(&self, account_id: Uuid) -> Result { + let conn = SqliteConnection::establish(self.database_url.as_str())?; + let db_account = schema::accounts::table + .find(Vec::from(account_id.as_bytes().as_slice())) + .get_result(&conn)?; + + SqliteAccountsTableGateway::convert_account(&db_account) + } +} + +impl SqliteAccountsTableGateway { + fn convert_account(r: &models::Account) -> Result { + let mut committee = Vec::with_capacity(r.committee_length as usize); + for i in 0..r.committee_length as usize { + committee.push(PublicKey::from_bytes(&r.committee_pub_keys[i * 32..(i + 1) * 32]).unwrap()); + } + Ok(Account { + id: Uuid::from_slice(&r.id).unwrap(), + asset_public_key: PublicKey::from_bytes(&r.asset_public_key).unwrap(), + name: r.name.clone(), + description: r.description.clone(), + image: r.image.clone(), + committee: if committee.is_empty() { + None + } else { + Some(committee) + }, + }) + } +} diff --git a/applications/tari_collectibles/src-tauri/src/storage/sqlite/models/account.rs b/applications/tari_collectibles/src-tauri/src/storage/sqlite/models/account.rs new file mode 100644 index 0000000000..d7ace4fe49 --- /dev/null +++ b/applications/tari_collectibles/src-tauri/src/storage/sqlite/models/account.rs @@ -0,0 +1,35 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::schema::*; +use diesel::prelude::*; + +#[derive(Queryable, Insertable, Identifiable)] +pub struct Account { + pub id: Vec, + pub asset_public_key: Vec, + pub name: Option, + pub description: Option, + pub image: Option, + pub committee_length: i32, + pub committee_pub_keys: Vec, +} diff --git a/applications/tari_collectibles/src-tauri/src/storage/sqlite/models/mod.rs b/applications/tari_collectibles/src-tauri/src/storage/sqlite/models/mod.rs new file mode 100644 index 0000000000..a13d48aa50 --- /dev/null +++ b/applications/tari_collectibles/src-tauri/src/storage/sqlite/models/mod.rs @@ -0,0 +1,24 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod account; +pub use account::Account; diff --git a/applications/tari_collectibles/src-tauri/src/storage/storage_error.rs b/applications/tari_collectibles/src-tauri/src/storage/storage_error.rs new file mode 100644 index 0000000000..97a96583c2 --- /dev/null +++ b/applications/tari_collectibles/src-tauri/src/storage/storage_error.rs @@ -0,0 +1,43 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use diesel; +use diesel_migrations; + +#[derive(Debug, thiserror::Error)] +pub enum StorageError { + #[error("Could not connect to database: {source}")] + ConnectionError { + #[from] + source: diesel::ConnectionError, + }, + #[error("General diesel error: {source}")] + DieselError { + #[from] + source: diesel::result::Error, + }, + #[error("Could not migrate the database")] + MigrationError { + #[from] + source: diesel_migrations::RunMigrationsError, + }, +} diff --git a/applications/tari_collectibles/src-tauri/temp.sqlite b/applications/tari_collectibles/src-tauri/temp.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..a3afcb033173898e1bef962260071f8cdb9e577a GIT binary patch literal 24576 zcmeI&O^?z*7zgl{w^b5APs9VsbMOGk>QdM>aW7i2>PEn|6q7xeNr91V>{@~i!2gdIg(uthv^ohg&Of5peXa}o1ZsBe3m1iuuk=8Q+v&{+s4@Hcwp z<>#AG)4=l_Avk9)_vhlA9AEG`57$##UF=VooJul}XnP`+a_gX2s`nbI;`lRu8C|$? z56bLV1j{x{7tT6qt#nZMrxUdFgyNT%>Rw&f)$4mpX~t#_kGRXC={fgo<~j4wPL>;G zkL0%QMs^%EKk+b1=-%mO@0D&gkJ>FdHBSb*c}A~|GaA0JXgKco_rus{GR`^~7$Zv` zoUTXcx$#0D_buv;P16`!Y#q`Xec7D|xFbvZ%Emip)q5fB*y_ z009U<00Izz00bZa0SNqe0{7K?tJ`UJG@06()@^sSr1e0PxlP)m + + Asset Details + + + + Balance: + + + ); + } +} + +export default withRouter(AccountDashboard); diff --git a/applications/tari_collectibles/web-app/src/App.js b/applications/tari_collectibles/web-app/src/App.js index 893bceb7b7..8db6f2ec61 100644 --- a/applications/tari_collectibles/web-app/src/App.js +++ b/applications/tari_collectibles/web-app/src/App.js @@ -32,7 +32,7 @@ import { AppBar, Box, CssBaseline, Divider, - Drawer, + Drawer, IconButton, List, ListItem, ListItemIcon, @@ -42,6 +42,7 @@ import { } from "@mui/material"; import DashboardIcon from "@mui/icons-material/Dashboard"; import CreateIcon from "@mui/icons-material/Create"; +import AddIcon from "@mui/icons-material/Add"; import AppsIcon from "@mui/icons-material/Apps"; import { ThemeProvider } from "@emotion/react"; import Create from "./Create"; @@ -51,6 +52,10 @@ import * as React from "react"; import PropTypes from "prop-types"; import Manage from "./Manage"; import AssetManager from "./AssetManager"; +import AccountDashboard from "./AccountDashboard"; +import NewAccount from "./NewAccount"; +import {useEffect, useState} from "react"; +import binding from "./binding"; const mdTheme = createTheme({ palette: { @@ -60,6 +65,20 @@ const mdTheme = createTheme({ }, }); +function IconButtonLink(props) { + const { icon, to} = props; + const renderLink = React.useMemo( + () => React.forwardRef(function Link(itemProps, ref) { + return ; + }), + [to] + ) + + return ( + {icon} + ); +} + function ListItemLink(props) { const { icon, primary, to } = props; @@ -88,6 +107,15 @@ ListItemLink.propTypes = { }; function App() { + const [accounts, setAccounts] = useState([]); + + useEffect(async () => { + let a = await binding.command_accounts_list(); + console.log(a); + setAccounts(a); + + }); + return (
@@ -108,6 +136,13 @@ function App() { icon={} /> + } to="/accounts/new"> + + }>My Assets + { accounts.map((item) => { + return (); + })} Issued Assets + + + + + + + + diff --git a/applications/tari_collectibles/web-app/src/NewAccount.js b/applications/tari_collectibles/web-app/src/NewAccount.js new file mode 100644 index 0000000000..9484c0d6dd --- /dev/null +++ b/applications/tari_collectibles/web-app/src/NewAccount.js @@ -0,0 +1,89 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import React, { useState, useMemo } from "react"; +import {withRouter} from "react-router-dom"; +import {Alert, Button, Container, Stack, TextField, Typography} from "@mui/material"; +import binding from "./binding"; + +class NewAccount extends React.Component { + constructor(props) { + super(props); + + this.state = { + error: null, + isSaving: false, + assetPublicKey: "" + }; + } + + onAssetPublicKeyChanged = (e) => { + this.setState({ assetPublicKey: e.target.value }); + }; + + onSave = async (e) => { + e.preventDefault(); + console.log(this.state.assetPublicKey); + this.setState({isSaving: true, error: null}); + try{ + await binding.command_accounts_create(this.state.assetPublicKey); + let history = this.props.history; +let path =`/assets/watched/details/${this.state.assetPublicKey}`; +console.log(path); + history.push(path); + return; + }catch(e) { + this.setState({error: e}); + console.error(e); + } + this.setState({isSaving: false}); + } + + render() { + return ( + + New asset account + + + {this.state.error ? ( + {this.state.error} + ) : ( + + )} + + + + ); + } +} + +export default withRouter(NewAccount); diff --git a/applications/tari_collectibles/web-app/src/binding.js b/applications/tari_collectibles/web-app/src/binding.js index a5d89a7141..3d88f78bc3 100644 --- a/applications/tari_collectibles/web-app/src/binding.js +++ b/applications/tari_collectibles/web-app/src/binding.js @@ -45,11 +45,21 @@ async function command_asset_issue_simple_tokens( }); } +async function command_accounts_create(assetPubKey) { + return await invoke("accounts_create", {assetPubKey}); +} + +async function command_accounts_list() { + return await invoke("accounts_list", {}); +} + const commands = { command_assets_create, command_assets_list_owned, command_assets_list_registered_assets, command_asset_issue_simple_tokens, + command_accounts_create, + command_accounts_list }; export default commands; diff --git a/applications/tari_collectibles/web-app/src/helpers.js b/applications/tari_collectibles/web-app/src/helpers.js new file mode 100644 index 0000000000..804b905e43 --- /dev/null +++ b/applications/tari_collectibles/web-app/src/helpers.js @@ -0,0 +1,27 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +export function toHexString(byteArray) { + return Array.from(byteArray, function(byte) { + return ('0' + (byte & 0xFF).toString(16)).slice(-2); + }).join('') +} diff --git a/applications/tari_validator_node/Cargo.toml b/applications/tari_validator_node/Cargo.toml index e4cc848be8..a58f6b66c5 100644 --- a/applications/tari_validator_node/Cargo.toml +++ b/applications/tari_validator_node/Cargo.toml @@ -22,6 +22,7 @@ tari_shutdown = { path = "../../infrastructure/shutdown" } tari_storage = { path = "../../infrastructure/storage" } tari_core = {path = "../../base_layer/core"} tari_dan_core = {path = "../../dan_layer/core"} +tari_dan_storage_sqlite = {path = "../../dan_layer/storage_sqlite"} anyhow = "1.0.32" async-trait = "0.1.50" diff --git a/applications/tari_validator_node/build.rs b/applications/tari_validator_node/build.rs index 54391752d1..c6ba71ff75 100644 --- a/applications/tari_validator_node/build.rs +++ b/applications/tari_validator_node/build.rs @@ -21,11 +21,6 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. fn main() -> Result<(), Box> { - tonic_build::configure() - .build_server(true) - .format(false) - .compile(&["proto/grpc/validator_node.proto"], &["proto"])?; - tari_common::build::ProtobufCompiler::new() .proto_paths(&["proto/p2p"]) .emit_rerun_if_changed_directives() diff --git a/applications/tari_validator_node/proto/p2p/dan_consensus.proto b/applications/tari_validator_node/proto/p2p/dan_consensus.proto index 3657ec34b6..2eeca051a1 100644 --- a/applications/tari_validator_node/proto/p2p/dan_consensus.proto +++ b/applications/tari_validator_node/proto/p2p/dan_consensus.proto @@ -18,11 +18,12 @@ message HotStuffMessage { QuorumCertificate justify = 3; HotStuffTreeNode node= 4; Signature partial_sig = 5; + optional bytes node_hash = 6; } message QuorumCertificate { HotStuffMessageType message_type = 1; - HotStuffTreeNode node = 2; + bytes node_hash = 2; uint64 view_number = 3; Signature signature = 4; } @@ -30,6 +31,7 @@ message QuorumCertificate { message HotStuffTreeNode { bytes parent = 1; TariDanPayload payload = 2; + uint32 height = 3; } message Signature{ diff --git a/applications/tari_validator_node/src/dan_node.rs b/applications/tari_validator_node/src/dan_node.rs index e54e4405ea..ce04adddd6 100644 --- a/applications/tari_validator_node/src/dan_node.rs +++ b/applications/tari_validator_node/src/dan_node.rs @@ -54,7 +54,7 @@ use tari_comms::{ use tari_comms_dht::{store_forward::SafConfig, DbConnectionUrl, Dht, DhtConfig}; use tari_crypto::tari_utilities::hex::Hex; use tari_dan_core::{ - models::{AssetDefinition, Committee}, + models::{AssetDefinition, Committee, TariDanPayload}, services::{ ConcreteAssetProcessor, ConcreteCommitteeManager, @@ -66,16 +66,10 @@ use tari_dan_core::{ TariDanPayloadProcessor, TariDanPayloadProvider, }, - storage::{ - sqlite::SqliteStorageService, - AssetDataStore, - BackendAdapter, - DbFactory, - LmdbAssetStore, - SqliteDbFactory, - }, + storage::{AssetDataStore, BackendAdapter, DbFactory, LmdbAssetStore}, workers::ConsensusWorker, }; +use tari_dan_storage_sqlite::{SqliteDbFactory, SqliteStorageService}; use tari_p2p::{ comms_connector::{pubsub_connector, SubscriptionFactory}, initialization::{spawn_comms_using_transport, P2pConfig, P2pInitializer}, @@ -171,7 +165,7 @@ impl DanNode { async fn start_asset_worker< TMempoolService: MempoolService + Clone, - TBackendAdapter: BackendAdapter + Send + Sync, + TBackendAdapter: BackendAdapter + Send + Sync, TDbFactory: DbFactory + Clone + Send + Sync, >( &self, diff --git a/applications/tari_validator_node/src/grpc/conversions.rs b/applications/tari_validator_node/src/grpc/conversions.rs index dbad8bcfb7..ff60b743fd 100644 --- a/applications/tari_validator_node/src/grpc/conversions.rs +++ b/applications/tari_validator_node/src/grpc/conversions.rs @@ -19,16 +19,18 @@ // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::grpc::validator_node_rpc as rpc; +use tari_app_grpc::tari_rpc; use tari_crypto::tari_utilities::ByteArray; use tari_dan_core::models::SidechainMetadata; -impl From for rpc::SidechainMetadata { +pub struct _st(tari_rpc::SidechainMetadata); + +impl From for _st { fn from(source: SidechainMetadata) -> Self { - Self { + Self(tari_rpc::SidechainMetadata { asset_public_key: source.asset_public_key().as_bytes().to_vec(), committed_height: source.committed_height().into(), committed_hash: source.committed_hash().clone().into(), - } + }) } } diff --git a/applications/tari_validator_node/src/grpc/mod.rs b/applications/tari_validator_node/src/grpc/mod.rs index de7a0e6019..125971e942 100644 --- a/applications/tari_validator_node/src/grpc/mod.rs +++ b/applications/tari_validator_node/src/grpc/mod.rs @@ -22,7 +22,3 @@ pub(crate) mod conversions; pub mod services; pub(crate) mod validator_node_grpc_server; - -pub mod validator_node_rpc { - tonic::include_proto!("tari.validator_node.rpc"); -} diff --git a/applications/tari_validator_node/src/grpc/validator_node_grpc_server.rs b/applications/tari_validator_node/src/grpc/validator_node_grpc_server.rs index f9a04bef3b..5e46861b7a 100644 --- a/applications/tari_validator_node/src/grpc/validator_node_grpc_server.rs +++ b/applications/tari_validator_node/src/grpc/validator_node_grpc_server.rs @@ -19,8 +19,8 @@ // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::grpc::validator_node_rpc as rpc; use std::marker::PhantomData; +use tari_app_grpc::tari_rpc as rpc; use tari_crypto::tari_utilities::ByteArray; use tari_dan_core::{ models::{Instruction, TemplateId, TokenId}, diff --git a/applications/tari_validator_node/src/main.rs b/applications/tari_validator_node/src/main.rs index b7c07b8b92..96e812750b 100644 --- a/applications/tari_validator_node/src/main.rs +++ b/applications/tari_validator_node/src/main.rs @@ -25,25 +25,21 @@ mod cmd_args; mod dan_node; mod grpc; mod p2p; -use crate::{ - dan_node::DanNode, - grpc::{ - validator_node_grpc_server::ValidatorNodeGrpcServer, - validator_node_rpc::validator_node_server::ValidatorNodeServer, - }, -}; +use crate::{dan_node::DanNode, grpc::validator_node_grpc_server::ValidatorNodeGrpcServer}; use futures::FutureExt; use log::*; use std::{ net::{IpAddr, Ipv4Addr, SocketAddr}, process, }; +use tari_app_grpc::tari_rpc::validator_node_server::ValidatorNodeServer; use tari_app_utilities::initialization::init_configuration; use tari_common::{configuration::bootstrap::ApplicationType, exit_codes::ExitCodes, GlobalConfig}; use tari_dan_core::{ services::{MempoolService, MempoolServiceHandle}, - storage::{BackendAdapter, ChainStorageService, DbFactory, SqliteDbFactory}, + storage::{BackendAdapter, ChainStorageService, DbFactory}, }; +use tari_dan_storage_sqlite::SqliteDbFactory; use tari_shutdown::{Shutdown, ShutdownSignal}; use tokio::{runtime, runtime::Runtime, task}; use tonic::transport::Server; diff --git a/applications/tari_validator_node/src/p2p/proto/conversions/dan.rs b/applications/tari_validator_node/src/p2p/proto/conversions/dan.rs index d0930de423..9b137040a4 100644 --- a/applications/tari_validator_node/src/p2p/proto/conversions/dan.rs +++ b/applications/tari_validator_node/src/p2p/proto/conversions/dan.rs @@ -49,6 +49,7 @@ impl From> for dan_proto::HotStuffMessage { justify: source.justify().map(|j| j.clone().into()), partial_sig: source.partial_sig().map(|s| s.clone().into()), view_number: source.view_number().as_u64(), + node_hash: source.node_hash().map(|s| s.0.clone()), } } } @@ -58,15 +59,16 @@ impl From> for dan_proto::HotStuffTreeNode { Self { parent: Vec::from(source.parent().as_bytes()), payload: Some(source.payload().clone().into()), + height: source.height(), } } } -impl From> for dan_proto::QuorumCertificate { - fn from(source: QuorumCertificate) -> Self { +impl From for dan_proto::QuorumCertificate { + fn from(source: QuorumCertificate) -> Self { Self { message_type: source.message_type().as_u8() as i32, - node: Some(source.node().clone().into()), + node_hash: Vec::from(source.node_hash().as_bytes()), view_number: source.view_number().as_u64(), signature: source.signature().map(|s| s.clone().into()), } @@ -122,23 +124,20 @@ impl TryFrom for HotStuffMessage { HotStuffMessageType::try_from(value.message_type as u8)?, value.justify.map(|j| j.try_into()).transpose()?, value.node.map(|n| n.try_into()).transpose()?, + value.node_hash.map(|v| TreeNodeHash(v.clone())), value.partial_sig.map(|p| p.try_into()).transpose()?, )) } } -impl TryFrom for QuorumCertificate { +impl TryFrom for QuorumCertificate { type Error = String; fn try_from(value: dan_proto::QuorumCertificate) -> Result { Ok(Self::new( HotStuffMessageType::try_from(value.message_type as u8)?, ViewId(value.view_number), - value - .node - .map(|n| n.try_into()) - .transpose()? - .ok_or_else(|| "node not provided on Quorum Certificate".to_string())?, + TreeNodeHash(value.node_hash), value.signature.map(|s| s.try_into()).transpose()?, )) } @@ -158,6 +157,7 @@ impl TryFrom for HotStuffTreeNode { .map(|p| p.try_into()) .transpose()? .ok_or_else(|| "payload not provided".to_string())?, + value.height, )) } } diff --git a/base_layer/core/src/base_node/comms_interface/comms_request.rs b/base_layer/core/src/base_node/comms_interface/comms_request.rs index 246dd680ac..0265cab586 100644 --- a/base_layer/core/src/base_node/comms_interface/comms_request.rs +++ b/base_layer/core/src/base_node/comms_interface/comms_request.rs @@ -62,6 +62,9 @@ pub enum NodeCommsRequest { FetchAssetRegistrations { range: RangeInclusive, }, + FetchAssetMetadata { + asset_public_key: PublicKey, + }, } #[derive(Debug, Serialize, Deserialize)] @@ -104,6 +107,9 @@ impl Display for NodeCommsRequest { FetchAssetRegistrations { .. } => { write!(f, "FetchAllNonFungibleTokens") }, + FetchAssetMetadata { .. } => { + write!(f, "FetchAssetMetadata") + }, } } } diff --git a/base_layer/core/src/base_node/comms_interface/comms_response.rs b/base_layer/core/src/base_node/comms_interface/comms_response.rs index 81bb9b55f0..bea4ccd0c8 100644 --- a/base_layer/core/src/base_node/comms_interface/comms_response.rs +++ b/base_layer/core/src/base_node/comms_interface/comms_response.rs @@ -55,6 +55,9 @@ pub enum NodeCommsResponse { FetchAssetRegistrationsResponse { outputs: Vec, }, + FetchAssetMetadataResponse { + output: Option, + }, } impl Display for NodeCommsResponse { @@ -84,6 +87,7 @@ impl Display for NodeCommsResponse { MmrNodes(_, _) => write!(f, "MmrNodes"), FetchTokensResponse { .. } => write!(f, "FetchTokensResponse"), FetchAssetRegistrationsResponse { .. } => write!(f, "FetchAssetRegistrationsResponse"), + FetchAssetMetadataResponse { .. } => write!(f, "FetchAssetMetadataResponse"), } } } diff --git a/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs b/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs index 5dc96f8db6..342763adb5 100644 --- a/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs +++ b/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs @@ -19,6 +19,7 @@ // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + use crate::{ base_node::comms_interface::{ error::CommsInterfaceError, @@ -43,6 +44,7 @@ use strum_macros::Display; use tari_common_types::types::{BlockHash, HashOutput, PublicKey}; use tari_comms::peer_manager::NodeId; use tari_crypto::tari_utilities::{hash::Hashable, hex::Hex}; +use tari_utilities::ByteArray; use tokio::sync::Semaphore; const LOG_TARGET: &str = "c::bn::comms_interface::inbound_handler"; @@ -441,6 +443,13 @@ where T: BlockchainBackend + 'static .collect(); Ok(NodeCommsResponse::FetchAssetRegistrationsResponse { outputs }) }, + NodeCommsRequest::FetchAssetMetadata { asset_public_key } => { + let output = self + .blockchain_db + .fetch_utxo_by_unique_id(None, Vec::from(asset_public_key.as_bytes()), None) + .await?; + Ok(NodeCommsResponse::FetchAssetMetadataResponse { output }) + }, } } diff --git a/base_layer/core/src/base_node/comms_interface/local_interface.rs b/base_layer/core/src/base_node/comms_interface/local_interface.rs index b0c885d944..c611207e01 100644 --- a/base_layer/core/src/base_node/comms_interface/local_interface.rs +++ b/base_layer/core/src/base_node/comms_interface/local_interface.rs @@ -303,4 +303,18 @@ impl LocalNodeCommsInterface { _ => Err(CommsInterfaceError::UnexpectedApiResponse), } } + + pub async fn get_asset_metadata( + &mut self, + asset_public_key: PublicKey, + ) -> Result, CommsInterfaceError> { + match self + .request_sender + .call(NodeCommsRequest::FetchAssetMetadata { asset_public_key }) + .await?? + { + NodeCommsResponse::FetchAssetMetadataResponse { output } => Ok(output), + _ => Err(CommsInterfaceError::UnexpectedApiResponse), + } + } } diff --git a/base_layer/wallet/src/types.rs b/base_layer/wallet/src/types.rs index 70346646d0..05490828df 100644 --- a/base_layer/wallet/src/types.rs +++ b/base_layer/wallet/src/types.rs @@ -41,7 +41,6 @@ pub(crate) struct MockPersistentKeyManager { impl MockPersistentKeyManager { pub fn new() -> Self { - // todo!() Self { key_manager: KeyManager::new(), } diff --git a/dan_layer/core/proto/tips/tip001.proto b/dan_layer/core/proto/tips/tip001.proto new file mode 100644 index 0000000000..abde69b28e --- /dev/null +++ b/dan_layer/core/proto/tips/tip001.proto @@ -0,0 +1,12 @@ + +service Tip001 { + rpc ReadMetada(ReadMetadataRequest) returns ReadMetadataResponse; +} + +message ReadMetadataRequest { + +} + +message ReadMetadataResponse { + +} \ No newline at end of file diff --git a/dan_layer/core/proto/tips/tip002.proto b/dan_layer/core/proto/tips/tip002.proto new file mode 100644 index 0000000000..7e7fcaf30d --- /dev/null +++ b/dan_layer/core/proto/tips/tip002.proto @@ -0,0 +1,22 @@ +service Tip002 { + rpc Info(InfoRequest) returns InfoResponse; + rpc BalanceOf(BalanceOfRequest) returns BalanceOfResponse; + } + + message InfoRequest { + + } + + message InfoResponse { + string symbol = 1; + byte decimals = 2; + uint256 total_supply =3; + } + + message BalanceOfRequest { + uint256 owner = 1; + } + + message BalanceOfResponse { + uint256 balance = 1; + } \ No newline at end of file diff --git a/dan_layer/core/src/digital_assets_error.rs b/dan_layer/core/src/digital_assets_error.rs index 816a90d436..079fe2f759 100644 --- a/dan_layer/core/src/digital_assets_error.rs +++ b/dan_layer/core/src/digital_assets_error.rs @@ -38,6 +38,8 @@ pub enum DigitalAssetError { MalformedMetadata(String), #[error("Could not convert between types:{0}")] ConversionError(String), + #[error("Branched to an unexpected logic path, this is most likely due to a bug:{reason}")] + InvalidLogicPath { reason: String }, } impl From for DigitalAssetError { diff --git a/dan_layer/core/src/models/asset_definition.rs b/dan_layer/core/src/models/asset_definition.rs index 4f6a6f3635..6d0b559759 100644 --- a/dan_layer/core/src/models/asset_definition.rs +++ b/dan_layer/core/src/models/asset_definition.rs @@ -21,7 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::types::PublicKey; -use serde::{self, de, Deserialize, Deserializer}; +use serde::{self, de, Deserialize, Deserializer, Serialize}; use std::{fmt, marker::PhantomData}; use tari_crypto::tari_utilities::hex::Hex; @@ -36,7 +36,7 @@ pub struct AssetDefinition { // TODO: Better name? lock time/peg time? (in number of blocks) pub base_layer_confirmation_time: u64, pub checkpoint_unique_id: Vec, - pub templates: Vec, + pub initial_state: InitialState, } impl Default for AssetDefinition { @@ -47,7 +47,7 @@ impl Default for AssetDefinition { public_key: Default::default(), initial_committee: vec![], phase_timeout: 10, - templates: vec![], + initial_state: Default::default(), } } } @@ -73,10 +73,25 @@ impl AssetDefinition { } des.deserialize_str(KeyStringVisitor { marker: PhantomData }) } + + pub fn initial_state(&self) -> &InitialState { + &self.initial_state + } } -#[derive(Deserialize, Clone)] -#[serde(tag = "id", content = "data")] -pub enum TemplateArgs { - Tmp721 { num_tokens: u64 }, +#[derive(Serialize, Deserialize, Default, Clone)] +pub struct InitialState { + pub schemas: Vec, +} + +#[derive(Serialize, Deserialize, Default, Clone)] +pub struct SchemaState { + pub name: String, + pub items: Vec, +} + +#[derive(Serialize, Deserialize, Default, Clone)] +pub struct KeyValue { + pub key: Vec, + pub value: Vec, } diff --git a/dan_layer/core/src/models/hot_stuff_message.rs b/dan_layer/core/src/models/hot_stuff_message.rs index d5757ad918..eab335b624 100644 --- a/dan_layer/core/src/models/hot_stuff_message.rs +++ b/dan_layer/core/src/models/hot_stuff_message.rs @@ -20,7 +20,15 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::models::{HotStuffMessageType, HotStuffTreeNode, Payload, QuorumCertificate, Signature, ViewId}; +use crate::models::{ + HotStuffMessageType, + HotStuffTreeNode, + Payload, + QuorumCertificate, + Signature, + TreeNodeHash, + ViewId, +}; use digest::Digest; use tari_crypto::common::Blake256; @@ -29,8 +37,9 @@ use tari_crypto::common::Blake256; pub struct HotStuffMessage { view_number: ViewId, message_type: HotStuffMessageType, - justify: Option>, + justify: Option, node: Option>, + node_hash: Option, partial_sig: Option, } @@ -38,8 +47,9 @@ impl HotStuffMessage { pub fn new( view_number: ViewId, message_type: HotStuffMessageType, - justify: Option>, + justify: Option, node: Option>, + node_hash: Option, partial_sig: Option, ) -> Self { HotStuffMessage { @@ -47,23 +57,25 @@ impl HotStuffMessage { message_type, justify, node, + node_hash, partial_sig, } } - pub fn new_view(prepare_qc: QuorumCertificate, view_number: ViewId) -> Self { + pub fn new_view(prepare_qc: QuorumCertificate, view_number: ViewId) -> Self { Self { message_type: HotStuffMessageType::NewView, view_number, justify: Some(prepare_qc), node: None, partial_sig: None, + node_hash: None, } } pub fn prepare( proposal: HotStuffTreeNode, - high_qc: Option>, + high_qc: Option, view_number: ViewId, ) -> Self { Self { @@ -72,12 +84,24 @@ impl HotStuffMessage { justify: high_qc, view_number, partial_sig: None, + node_hash: None, + } + } + + pub fn vote_prepare(node_hash: TreeNodeHash, view_number: ViewId) -> Self { + Self { + message_type: HotStuffMessageType::Prepare, + node_hash: Some(node_hash), + view_number, + node: None, + partial_sig: None, + justify: None, } } pub fn pre_commit( node: Option>, - prepare_qc: Option>, + prepare_qc: Option, view_number: ViewId, ) -> Self { Self { @@ -85,13 +109,25 @@ impl HotStuffMessage { node, justify: prepare_qc, view_number, + node_hash: None, + partial_sig: None, + } + } + + pub fn vote_pre_commit(node_hash: TreeNodeHash, view_number: ViewId) -> Self { + Self { + message_type: HotStuffMessageType::PreCommit, + node_hash: Some(node_hash), + view_number, + node: None, partial_sig: None, + justify: None, } } pub fn commit( node: Option>, - pre_commit_qc: Option>, + pre_commit_qc: Option, view_number: ViewId, ) -> Self { Self { @@ -100,12 +136,24 @@ impl HotStuffMessage { justify: pre_commit_qc, view_number, partial_sig: None, + node_hash: None, + } + } + + pub fn vote_commit(node_hash: TreeNodeHash, view_number: ViewId) -> Self { + Self { + message_type: HotStuffMessageType::Commit, + node_hash: Some(node_hash), + view_number, + node: None, + partial_sig: None, + justify: None, } } pub fn decide( node: Option>, - commit_qc: Option>, + commit_qc: Option, view_number: ViewId, ) -> Self { Self { @@ -114,6 +162,7 @@ impl HotStuffMessage { justify: commit_qc, view_number, partial_sig: None, + node_hash: None, } } @@ -123,6 +172,10 @@ impl HotStuffMessage { .chain(self.view_number.as_u64().to_le_bytes()); if let Some(ref node) = self.node { b = b.chain(node.calculate_hash().as_bytes()); + } else { + if let Some(ref node_hash) = self.node_hash { + b = b.chain(node_hash.as_bytes()); + } } b.finalize().to_vec() } @@ -135,11 +188,15 @@ impl HotStuffMessage { self.node.as_ref() } + pub fn node_hash(&self) -> Option<&TreeNodeHash> { + self.node_hash.as_ref() + } + pub fn message_type(&self) -> &HotStuffMessageType { &self.message_type } - pub fn justify(&self) -> Option<&QuorumCertificate> { + pub fn justify(&self) -> Option<&QuorumCertificate> { self.justify.as_ref() } diff --git a/dan_layer/core/src/models/hot_stuff_tree_node.rs b/dan_layer/core/src/models/hot_stuff_tree_node.rs index 115c4fc99a..24180d00a9 100644 --- a/dan_layer/core/src/models/hot_stuff_tree_node.rs +++ b/dan_layer/core/src/models/hot_stuff_tree_node.rs @@ -29,14 +29,16 @@ pub struct HotStuffTreeNode { parent: TreeNodeHash, payload: TPayload, hash: TreeNodeHash, + height: u32, } impl HotStuffTreeNode { - pub fn new(parent: TreeNodeHash, payload: TPayload) -> Self { + pub fn new(parent: TreeNodeHash, payload: TPayload, height: u32) -> Self { let mut s = HotStuffTreeNode { parent, payload, hash: TreeNodeHash(vec![]), + height, }; s.hash = s.calculate_hash(); s @@ -47,19 +49,21 @@ impl HotStuffTreeNode { parent: TreeNodeHash(vec![0u8; 32]), payload, hash: TreeNodeHash(vec![]), + height: 0, }; s.hash = s.calculate_hash(); s } - pub fn from_parent(parent: &HotStuffTreeNode, payload: TPayload) -> HotStuffTreeNode { - Self::new(parent.calculate_hash(), payload) + pub fn from_parent(parent: TreeNodeHash, payload: TPayload, height: u32) -> HotStuffTreeNode { + Self::new(parent, payload, height) } pub fn calculate_hash(&self) -> TreeNodeHash { let result = Blake256::new() .chain(self.parent.0.as_slice()) .chain(self.payload.consensus_hash()) + .chain(self.height.to_le_bytes()) .finalize(); TreeNodeHash(result.to_vec()) } @@ -75,6 +79,10 @@ impl HotStuffTreeNode { pub fn payload(&self) -> &TPayload { &self.payload } + + pub fn height(&self) -> u32 { + self.height + } } impl PartialEq for HotStuffTreeNode { diff --git a/dan_layer/core/src/models/mod.rs b/dan_layer/core/src/models/mod.rs index bead61a970..63187a2467 100644 --- a/dan_layer/core/src/models/mod.rs +++ b/dan_layer/core/src/models/mod.rs @@ -21,8 +21,9 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use std::{convert::TryFrom, fmt::Debug, hash::Hash}; -// mod block; mod asset_definition; +mod base_layer_metadata; +mod base_layer_output; mod committee; pub mod domain_events; mod hot_stuff_message; @@ -30,28 +31,23 @@ mod hot_stuff_tree_node; mod instruction; mod instruction_set; mod quorum_certificate; -// mod replica_info; -mod base_layer_metadata; -mod base_layer_output; mod sidechain_metadata; +mod state_root; mod tari_dan_payload; mod view; mod view_id; - -// pub use block::Block; +pub use asset_definition::AssetDefinition; +pub use base_layer_metadata::BaseLayerMetadata; +pub use base_layer_output::BaseLayerOutput; +use blake2::Digest; pub use committee::Committee; pub use hot_stuff_message::HotStuffMessage; pub use hot_stuff_tree_node::HotStuffTreeNode; pub use instruction::Instruction; pub use instruction_set::InstructionSet; pub use quorum_certificate::QuorumCertificate; -// pub use replica_info::ReplicaInfo; -pub use asset_definition::AssetDefinition; -pub use base_layer_metadata::BaseLayerMetadata; -pub use base_layer_output::BaseLayerOutput; -use blake2::Digest; -use digest::Update; pub use sidechain_metadata::SidechainMetadata; +pub use state_root::StateRoot; use tari_crypto::common::Blake256; pub use tari_dan_payload::{CheckpointData, TariDanPayload}; pub use view::View; @@ -181,10 +177,20 @@ impl ConsensusHash for String { } // TODO: Perhaps should be CoW instead of Clone -pub trait Payload: Debug + Clone + Send + Sync + ConsensusHash {} +pub trait Payload: Debug + Clone + Send + Sync + ConsensusHash { + fn state_root(&self) -> StateRoot; +} -impl Payload for &str {} -impl Payload for String {} +impl Payload for &str { + fn state_root(&self) -> StateRoot { + StateRoot::default() + } +} +impl Payload for String { + fn state_root(&self) -> StateRoot { + StateRoot::default() + } +} pub trait Event: Clone + Send + Sync {} @@ -203,9 +209,17 @@ pub enum ConsensusWorkerState { pub struct Signature {} impl Signature { + pub fn from_bytes(source: &[u8]) -> Self { + Self {} + } + pub fn combine(&self, other: &Signature) -> Signature { other.clone() } + + pub fn to_bytes(&self) -> Vec { + vec![] + } } #[derive(Copy, Clone, Debug)] diff --git a/dan_layer/core/src/models/quorum_certificate.rs b/dan_layer/core/src/models/quorum_certificate.rs index a87d950379..dad2de467b 100644 --- a/dan_layer/core/src/models/quorum_certificate.rs +++ b/dan_layer/core/src/models/quorum_certificate.rs @@ -20,42 +20,42 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::models::{HotStuffMessageType, HotStuffTreeNode, Payload, Signature, ViewId}; +use crate::models::{HotStuffMessageType, HotStuffTreeNode, Payload, Signature, TreeNodeHash, ViewId}; #[derive(Debug, Clone)] -pub struct QuorumCertificate { +pub struct QuorumCertificate { message_type: HotStuffMessageType, - node: HotStuffTreeNode, + node_hash: TreeNodeHash, view_number: ViewId, signature: Option, } -impl QuorumCertificate { +impl QuorumCertificate { pub fn new( message_type: HotStuffMessageType, view_number: ViewId, - node: HotStuffTreeNode, + node_hash: TreeNodeHash, signature: Option, ) -> Self { Self { message_type, - node, + node_hash, view_number, signature, } } - pub fn genesis(payload: TPayload) -> Self { + pub fn genesis(node_hash: TreeNodeHash) -> Self { Self { message_type: HotStuffMessageType::Genesis, - node: HotStuffTreeNode::genesis(payload), + node_hash, view_number: 0.into(), signature: None, } } - pub fn node(&self) -> &HotStuffTreeNode { - &self.node + pub fn node_hash(&self) -> &TreeNodeHash { + &self.node_hash } pub fn view_number(&self) -> ViewId { diff --git a/dan_layer/core/src/models/state_root.rs b/dan_layer/core/src/models/state_root.rs new file mode 100644 index 0000000000..e28b744f94 --- /dev/null +++ b/dan_layer/core/src/models/state_root.rs @@ -0,0 +1,28 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#[derive(Default, PartialEq)] +pub struct StateRoot { + root: Vec, +} + +impl StateRoot {} diff --git a/dan_layer/core/src/models/tari_dan_payload.rs b/dan_layer/core/src/models/tari_dan_payload.rs index 7914b1a8c8..f40c330ab8 100644 --- a/dan_layer/core/src/models/tari_dan_payload.rs +++ b/dan_layer/core/src/models/tari_dan_payload.rs @@ -20,7 +20,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::models::{ConsensusHash, Instruction, InstructionSet, Payload}; +use crate::models::{ConsensusHash, Instruction, InstructionSet, Payload, StateRoot}; use digest::Digest; use std::{ fmt::{Debug, Formatter}, @@ -70,7 +70,11 @@ impl ConsensusHash for TariDanPayload { } } -impl Payload for TariDanPayload {} +impl Payload for TariDanPayload { + fn state_root(&self) -> StateRoot { + StateRoot::default() + } +} #[derive(Debug, Clone)] pub struct CheckpointData { diff --git a/dan_layer/core/src/models/view_id.rs b/dan_layer/core/src/models/view_id.rs index 7ed8297ef7..bc60c079ed 100644 --- a/dan_layer/core/src/models/view_id.rs +++ b/dan_layer/core/src/models/view_id.rs @@ -20,7 +20,11 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::cmp::Ordering; +use std::{ + cmp::Ordering, + fmt::{self, Display}, + ops::Sub, +}; #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct ViewId(pub u64); @@ -50,3 +54,17 @@ impl From for ViewId { Self(v) } } + +impl Display for ViewId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "View({})", self.0) + } +} + +impl Sub for ViewId { + type Output = ViewId; + + fn sub(self, rhs: Self) -> Self::Output { + ViewId(self.0 - rhs.0) + } +} diff --git a/dan_layer/core/src/services/asset_processor.rs b/dan_layer/core/src/services/asset_processor.rs index a0564a17fa..eddce31340 100644 --- a/dan_layer/core/src/services/asset_processor.rs +++ b/dan_layer/core/src/services/asset_processor.rs @@ -34,7 +34,7 @@ use tokio::sync::RwLock; pub trait AssetProcessor { // purposefully made sync, because instructions should be run in order, and complete before the // next one starts. There may be a better way to enforce this though... - fn execute_instruction( + fn execute_instruction( &self, instruction: &Instruction, db: TUnitOfWork, @@ -48,7 +48,7 @@ pub struct ConcreteAssetProcessor { } impl AssetProcessor for ConcreteAssetProcessor { - fn execute_instruction( + fn execute_instruction( &self, instruction: &Instruction, db: TUnitOfWork, @@ -75,7 +75,7 @@ impl ConcreteAssetProcessor { } } - pub fn execute( + pub fn execute( &self, template_id: TemplateId, method: String, diff --git a/dan_layer/core/src/services/infrastructure_services/node_addressable.rs b/dan_layer/core/src/services/infrastructure_services/node_addressable.rs index 476609256c..546167608b 100644 --- a/dan_layer/core/src/services/infrastructure_services/node_addressable.rs +++ b/dan_layer/core/src/services/infrastructure_services/node_addressable.rs @@ -20,10 +20,13 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::{fmt::Debug, hash::Hash}; +use std::{ + fmt::{Debug, Display}, + hash::Hash, +}; use tari_comms::types::CommsPublicKey; -pub trait NodeAddressable: Eq + Hash + Clone + Debug + Send + Sync {} +pub trait NodeAddressable: Eq + Hash + Clone + Debug + Send + Sync + Display {} impl NodeAddressable for String {} diff --git a/dan_layer/core/src/services/payload_processor.rs b/dan_layer/core/src/services/payload_processor.rs index b376ec7b5b..55de06a64e 100644 --- a/dan_layer/core/src/services/payload_processor.rs +++ b/dan_layer/core/src/services/payload_processor.rs @@ -22,9 +22,9 @@ use crate::{ digital_assets_error::DigitalAssetError, - models::{Payload, TariDanPayload}, + models::{Payload, StateRoot, TariDanPayload}, services::{AssetProcessor, MempoolService}, - storage::{ChainDb, ChainDbUnitOfWork, DbFactory, UnitOfWork}, + storage::{ChainDb, ChainDbUnitOfWork, DbFactory, StateDbUnitOfWork, UnitOfWork}, }; use async_trait::async_trait; use std::sync::Arc; @@ -32,11 +32,11 @@ use tokio::sync::RwLock; #[async_trait] pub trait PayloadProcessor { - async fn process_payload( + async fn process_payload( &self, payload: &TPayload, unit_of_work: TUnitOfWork, - ) -> Result<(), DigitalAssetError>; + ) -> Result; } pub struct TariDanPayloadProcessor @@ -63,25 +63,19 @@ impl impl PayloadProcessor for TariDanPayloadProcessor { - async fn process_payload( + async fn process_payload( &self, payload: &TariDanPayload, - unit_of_work: TUnitOfWork, - ) -> Result<(), DigitalAssetError> { - // let mut unit_of_work = db.new_unit_of_work(); + state_tx: TUnitOfWork, + ) -> Result { for instruction in payload.instructions() { dbg!("Executing instruction"); dbg!(&instruction); // TODO: Should we swallow + log the error instead of propagating it? self.asset_processor - .execute_instruction(instruction, unit_of_work.clone())?; + .execute_instruction(instruction, state_tx.clone())?; } - // self.mempool_service.remove_instructions(payload.instructions()).await?; - - // TODO: Remove this....Unit of work should actually be committed only at COMMIT stage - // unit_of_work.commit()?; - - Ok(()) + Ok(state_tx.calculate_root()?) } } diff --git a/dan_layer/core/src/storage/chain_db.rs b/dan_layer/core/src/storage/chain_db.rs new file mode 100644 index 0000000000..08ea76f6bd --- /dev/null +++ b/dan_layer/core/src/storage/chain_db.rs @@ -0,0 +1,277 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::{ + models::{ + HotStuffMessageType, + HotStuffTreeNode, + Instruction, + Payload, + QuorumCertificate, + Signature, + TreeNodeHash, + ViewId, + }, + storage::{BackendAdapter, NewUnitOfWorkTracker, StorageError, UnitOfWork, UnitOfWorkTracker}, +}; +use std::sync::{Arc, RwLock}; + +pub struct ChainDb { + adapter: TBackendAdapter, +} + +impl ChainDb { + pub fn new(adapter: TBackendAdapter) -> ChainDb { + ChainDb { adapter } + } + + pub fn find_highest_prepared_qc(&self) -> Result { + self.adapter + .find_highest_prepared_qc() + .map_err(TBackendAdapter::Error::into) + } + + pub fn get_locked_qc(&self) -> Result { + self.adapter.get_locked_qc().map_err(TBackendAdapter::Error::into) + } +} + +impl ChainDb { + pub fn new_unit_of_work(&self) -> ChainDbUnitOfWork { + ChainDbUnitOfWork { + inner: Arc::new(RwLock::new(ChainDbUnitOfWorkInner::new(self.adapter.clone()))), + } + } +} +impl ChainDb { + pub fn is_empty(&self) -> Result { + self.adapter.is_empty().map_err(TBackendAdapter::Error::into) + } +} + +// Cloneable, Send, Sync wrapper +pub struct ChainDbUnitOfWork { + inner: Arc>>, +} + +impl ChainDbUnitOfWork { + fn find_proposed_node( + &mut self, + node_hash: &TreeNodeHash, + ) -> Result<(TBackendAdapter::Id, UnitOfWorkTracker), StorageError> { + let mut inner = self.inner.write().unwrap(); + for (clean_id, clean_item) in &inner.clean_items { + match clean_item { + UnitOfWorkTracker::Node { hash, .. } => { + if hash == node_hash { + return Ok((*clean_id, clean_item.clone())); + } + }, + _ => (), + }; + } + for (dirty_id, dirty_item) in &inner.dirty_items { + match dirty_item { + UnitOfWorkTracker::Node { hash, .. } => { + if hash == node_hash { + return Ok((*dirty_id, dirty_item.clone())); + } + }, + _ => (), + }; + } + // finally hit the db + let (id, item) = inner + .backend_adapter + .find_node_by_hash(node_hash) + .map_err(TBackendAdapter::Error::into)?; + inner.clean_items.push((id, item.clone())); + Ok((id, item)) + } +} + +pub struct ChainDbUnitOfWorkInner { + backend_adapter: TBackendAdapter, + clean_items: Vec<(TBackendAdapter::Id, UnitOfWorkTracker)>, + dirty_items: Vec<(TBackendAdapter::Id, UnitOfWorkTracker)>, + new_items: Vec, +} + +impl ChainDbUnitOfWorkInner { + pub fn new(backend_adapter: TBackendAdapter) -> Self { + Self { + backend_adapter, + clean_items: vec![], + dirty_items: vec![], + new_items: vec![], + } + } +} + +impl Clone for ChainDbUnitOfWork { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } + } +} + +impl UnitOfWork for ChainDbUnitOfWork { + // pub fn register_clean(&mut self, item: UnitOfWorkTracker) { + // self.clean.push(item); + // } + + fn commit(&mut self) -> Result<(), StorageError> { + let mut inner = self.inner.write().unwrap(); + let tx = inner + .backend_adapter + .create_transaction() + .map_err(TBackendAdapter::Error::into)?; + for item in inner.new_items.iter() { + inner + .backend_adapter + .insert(item, &tx) + .map_err(TBackendAdapter::Error::into)?; + } + + for (id, item) in inner.dirty_items.iter() { + inner + .backend_adapter + .update(id, item, &tx) + .map_err(TBackendAdapter::Error::into)?; + } + + inner + .backend_adapter + .commit(&tx) + .map_err(TBackendAdapter::Error::into)?; + Ok(()) + } + + fn add_node(&mut self, hash: TreeNodeHash, parent: TreeNodeHash, height: u32) -> Result<(), StorageError> { + self.inner + .write() + .unwrap() + .new_items + .push(NewUnitOfWorkTracker::Node { hash, parent, height }); + Ok(()) + } + + fn add_instruction(&mut self, node_hash: TreeNodeHash, instruction: Instruction) -> Result<(), StorageError> { + self.inner + .write() + .unwrap() + .new_items + .push(NewUnitOfWorkTracker::Instruction { node_hash, instruction }); + Ok(()) + } + + fn get_locked_qc(&mut self) -> Result { + let mut inner = self.inner.write().unwrap(); + let id = inner.backend_adapter.locked_qc_id(); + + for (_, clean_item) in &inner.clean_items { + match clean_item { + UnitOfWorkTracker::LockedQc { + message_type, + view_number, + node_hash, + signature, + } => { + return Ok(QuorumCertificate::new( + message_type.clone(), + view_number.clone(), + node_hash.clone(), + signature.clone(), + )); + }, + _ => (), + }; + } + for (_, dirty_item) in &inner.dirty_items { + match dirty_item { + UnitOfWorkTracker::LockedQc { + message_type, + view_number, + node_hash, + signature, + } => { + return Ok(QuorumCertificate::new( + message_type.clone(), + view_number.clone(), + node_hash.clone(), + signature.clone(), + )); + }, + _ => (), + }; + } + // finally hit the db + let qc = inner + .backend_adapter + .get_locked_qc() + .map_err(TBackendAdapter::Error::into)?; + let id = inner.backend_adapter.locked_qc_id(); + inner.clean_items.push((id, UnitOfWorkTracker::LockedQc { + message_type: qc.message_type().clone(), + view_number: qc.view_number().clone(), + node_hash: qc.node_hash().clone(), + signature: qc.signature().cloned(), + })); + Ok(qc) + } + + fn set_locked_qc(&mut self, qc: &QuorumCertificate) -> Result<(), StorageError> { + let mut inner = self.inner.write().unwrap(); + let id = inner.backend_adapter.locked_qc_id(); + inner.dirty_items.push((id, UnitOfWorkTracker::LockedQc { + message_type: qc.message_type(), + view_number: qc.view_number(), + node_hash: qc.node_hash().clone(), + signature: qc.signature().cloned(), + })); + Ok(()) + } + + fn set_prepare_qc(&mut self, qc: &QuorumCertificate) -> Result<(), StorageError> { + let mut inner = self.inner.write().unwrap(); + let id = inner.backend_adapter.prepare_qc_id(); + inner.dirty_items.push((id, UnitOfWorkTracker::PrepareQc { + message_type: qc.message_type(), + view_number: qc.view_number(), + node_hash: qc.node_hash().clone(), + signature: qc.signature().cloned(), + })); + Ok(()) + } + + fn commit_node(&mut self, node_hash: &TreeNodeHash) -> Result<(), StorageError> { + let (id, mut item) = self.find_proposed_node(node_hash)?; + let mut inner = self.inner.write().unwrap(); + match &mut item { + UnitOfWorkTracker::Node { is_committed, .. } => *is_committed = true, + _ => return Err(StorageError::InvalidUnitOfWorkTrackerType), + } + inner.dirty_items.push((id, item)); + Ok(()) + } +} diff --git a/dan_layer/core/src/storage/chain_storage_service.rs b/dan_layer/core/src/storage/chain_storage_service.rs index b4467c945b..f4f7f9bdb2 100644 --- a/dan_layer/core/src/storage/chain_storage_service.rs +++ b/dan_layer/core/src/storage/chain_storage_service.rs @@ -33,11 +33,17 @@ use tokio::sync::RwLock; #[async_trait] pub trait ChainStorageService { async fn get_metadata(&self) -> Result; - async fn save_node( + async fn add_node( &self, node: &HotStuffTreeNode, db: TUnitOfWork, ) -> Result<(), StorageError>; + + async fn set_locked_qc( + &self, + qc: QuorumCertificate, + db: TUnitOfWork, + ) -> Result<(), StorageError>; } // #[derive(Clone)] // pub struct ChainStorageServiceHandle { diff --git a/dan_layer/core/src/storage/error.rs b/dan_layer/core/src/storage/error.rs index e2227ec43c..a235fad0ba 100644 --- a/dan_layer/core/src/storage/error.rs +++ b/dan_layer/core/src/storage/error.rs @@ -26,6 +26,8 @@ use tari_storage::lmdb_store::LMDBError; #[derive(Debug, thiserror::Error)] pub enum StorageError { + #[error("Could not connect to storage:{reason}")] + ConnectionError { reason: String }, #[error("IO Error: {0}")] Io(#[from] io::Error), #[error("LMDB: {0}")] @@ -34,4 +36,12 @@ pub enum StorageError { LMDBError(#[from] LMDBError), #[error("Decode Error: {0}")] DecodeError(#[from] bytecodec::Error), + #[error("Query error:{reason}")] + QueryError { reason: String }, + #[error("Migration error: {reason}")] + MigrationError { reason: String }, + #[error("Invalid unit of work tracker type")] + InvalidUnitOfWorkTrackerType, + #[error("Item does not exist")] + NotFound, } diff --git a/dan_layer/core/src/storage/mod.rs b/dan_layer/core/src/storage/mod.rs index 2845c83efc..95962fe419 100644 --- a/dan_layer/core/src/storage/mod.rs +++ b/dan_layer/core/src/storage/mod.rs @@ -20,16 +20,28 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::{ - models::{ChainHeight, HotStuffTreeNode, Instruction, SidechainMetadata, TreeNodeHash}, - storage::sqlite::SqliteBackendAdapter, +use crate::models::{ + ChainHeight, + HotStuffMessageType, + HotStuffTreeNode, + Instruction, + Payload, + QuorumCertificate, + SidechainMetadata, + Signature, + StateRoot, + TreeNodeHash, + ViewId, }; pub use chain_storage_service::ChainStorageService; pub use error::StorageError; pub use lmdb::{LmdbAssetBackend, LmdbAssetStore}; use std::sync::{Arc, RwLock}; pub use store::{AssetDataStore, AssetStore}; -use tari_common::GlobalConfig; + +mod chain_db; +pub use chain_db::{ChainDb, ChainDbUnitOfWork}; +use std::marker::PhantomData; mod chain_storage_service; mod error; @@ -37,58 +49,42 @@ pub mod lmdb; mod store; // feature sql -pub mod sqlite; pub trait DbFactory { - fn create(&self) -> ChainDb; + fn create(&self) -> Result, StorageError>; + fn create_state_db(&self) -> Result, StorageError>; } +// TODO: I don't really like the matches on this struct, so it would be better to have individual types, e.g. +// NodeDataTracker, QcDataTracker #[derive(Clone)] -pub struct SqliteDbFactory {} - -impl SqliteDbFactory { - pub fn new(config: &GlobalConfig) -> Self { - Self {} - } - - fn create_adapter(&self) -> SqliteBackendAdapter { - SqliteBackendAdapter {} - } -} - -impl DbFactory for SqliteDbFactory { - fn create(&self) -> ChainDb { - ChainDb { - adapter: self.create_adapter(), - } - } -} - -pub struct ChainDb { - adapter: TBackendAdapter, -} - -impl ChainDb { - pub fn new_unit_of_work(&self) -> ChainDbUnitOfWork { - ChainDbUnitOfWork { - inner: Arc::new(RwLock::new(ChainDbUnitOfWorkInner::new(self.adapter.clone()))), - } - } -} -impl ChainDb { - pub fn is_empty(&self) -> bool { - return true; - } -} - pub enum UnitOfWorkTracker { SidechainMetadata, + LockedQc { + message_type: HotStuffMessageType, + view_number: ViewId, + node_hash: TreeNodeHash, + signature: Option, + }, + PrepareQc { + message_type: HotStuffMessageType, + view_number: ViewId, + node_hash: TreeNodeHash, + signature: Option, + }, + Node { + hash: TreeNodeHash, + parent: TreeNodeHash, + height: u32, + is_committed: bool, + }, } pub enum NewUnitOfWorkTracker { Node { hash: TreeNodeHash, parent: TreeNodeHash, + height: u32, }, Instruction { instruction: Instruction, @@ -98,91 +94,77 @@ pub enum NewUnitOfWorkTracker { pub trait BackendAdapter: Send + Sync + Clone { type BackendTransaction; - fn create_transaction(&self) -> Self::BackendTransaction; - fn insert(&self, item: &NewUnitOfWorkTracker, transaction: &Self::BackendTransaction) -> Result<(), StorageError>; - fn commit(&self, transaction: &Self::BackendTransaction) -> Result<(), StorageError>; -} - -pub struct ChainDbUnitOfWorkInner { - backend_adapter: TBackendAdapter, - clean_items: Vec, - dirty_items: Vec, - new_items: Vec, -} - -impl ChainDbUnitOfWorkInner { - pub fn new(backend_adapter: TBackendAdapter) -> Self { - Self { - backend_adapter, - clean_items: vec![], - dirty_items: vec![], - new_items: vec![], - } - } + type Error: Into; + type Id: Copy + Send + Sync; + type Payload: Payload; + + fn is_empty(&self) -> Result; + fn create_transaction(&self) -> Result; + fn insert(&self, item: &NewUnitOfWorkTracker, transaction: &Self::BackendTransaction) -> Result<(), Self::Error>; + fn update( + &self, + id: &Self::Id, + item: &UnitOfWorkTracker, + transaction: &Self::BackendTransaction, + ) -> Result<(), Self::Error>; + fn commit(&self, transaction: &Self::BackendTransaction) -> Result<(), Self::Error>; + fn locked_qc_id(&self) -> Self::Id; + fn prepare_qc_id(&self) -> Self::Id; + fn find_highest_prepared_qc(&self) -> Result; + fn get_locked_qc(&self) -> Result; + fn find_node_by_hash(&self, node_hash: &TreeNodeHash) -> Result<(Self::Id, UnitOfWorkTracker), Self::Error>; } pub trait UnitOfWork: Clone + Send + Sync { fn commit(&mut self) -> Result<(), StorageError>; - fn add_node(&mut self, hash: TreeNodeHash, parent: TreeNodeHash) -> Result<(), StorageError>; + fn add_node(&mut self, hash: TreeNodeHash, parent: TreeNodeHash, height: u32) -> Result<(), StorageError>; fn add_instruction(&mut self, node_hash: TreeNodeHash, instruction: Instruction) -> Result<(), StorageError>; + fn get_locked_qc(&mut self) -> Result; + fn set_locked_qc(&mut self, qc: &QuorumCertificate) -> Result<(), StorageError>; + fn set_prepare_qc(&mut self, qc: &QuorumCertificate) -> Result<(), StorageError>; + fn commit_node(&mut self, node_hash: &TreeNodeHash) -> Result<(), StorageError>; + // fn find_proposed_node(&mut self, node_hash: TreeNodeHash) -> Result<(Self::Id, UnitOfWorkTracker), StorageError>; } -// Cloneable, Send, Sync wrapper -pub struct ChainDbUnitOfWork { - inner: Arc>>, +pub trait StateDbUnitOfWork: Clone + Sized + Send + Sync { + fn set_value(&mut self, schema: String, key: Vec, value: Vec) -> Result<(), StorageError>; + fn commit(&mut self) -> Result; + fn calculate_root(&self) -> Result; } -impl Clone for ChainDbUnitOfWork { - fn clone(&self) -> Self { - Self { - inner: self.inner.clone(), - } - } +#[derive(Clone)] +pub struct StateDbUnitOfWorkImpl { + // hashmap rather? + updates: Vec<(String, Vec, Vec)>, } -impl UnitOfWork for ChainDbUnitOfWork { - // pub fn register_clean(&mut self, item: UnitOfWorkTracker) { - // self.clean.push(item); - // } - - fn commit(&mut self) -> Result<(), StorageError> { - let mut inner = self.inner.write().unwrap(); - let tx = inner.backend_adapter.create_transaction(); - for item in inner.new_items.iter() { - inner.backend_adapter.insert(item, &tx)?; - } - - inner.backend_adapter.commit(&tx)?; +impl StateDbUnitOfWork for StateDbUnitOfWorkImpl { + fn set_value(&mut self, schema: String, key: Vec, value: Vec) -> Result<(), StorageError> { + self.updates.push((schema, key, value)); Ok(()) } - fn add_node(&mut self, hash: TreeNodeHash, parent: TreeNodeHash) -> Result<(), StorageError> { - self.inner - .write() - .unwrap() - .new_items - .push(NewUnitOfWorkTracker::Node { hash, parent }); - Ok(()) + fn commit(&mut self) -> Result { + // todo!("actually commit") + Ok(StateRoot::default()) } - fn add_instruction(&mut self, node_hash: TreeNodeHash, instruction: Instruction) -> Result<(), StorageError> { - self.inner - .write() - .unwrap() - .new_items - .push(NewUnitOfWorkTracker::Instruction { node_hash, instruction }); - Ok(()) + fn calculate_root(&self) -> Result { + Ok(StateRoot::default()) } } -pub struct StateDb { - unit_of_work: Option, +pub struct StateDb { + pd: PhantomData, } -impl StateDb { - pub fn new_unit_of_work(&mut self) -> &mut StateDbUnitOfWork { - self.unit_of_work = Some(StateDbUnitOfWork { child: None }); - self.unit_of_work.as_mut().unwrap() +impl StateDb { + pub fn new() -> Self { + Self { pd: Default::default() } + } + + pub fn new_unit_of_work(&self) -> StateDbUnitOfWorkImpl { + StateDbUnitOfWorkImpl { updates: vec![] } // let mut unit_of_work = self.current_unit_of_work_mut(); // if unit_of_work.is_none() { // self.unit_of_work = Some(StateDbUnitOfWork {}); @@ -190,31 +172,4 @@ impl StateDb { // }; // unit_of_work.as_mut().unwrap() } - - fn current_unit_of_work_mut(&mut self) -> Option<&mut StateDbUnitOfWork> { - unimplemented!() - // let mut result = self.unit_of_work.as_mut(); - // let mut child = result; - // while let Some(c) = child { - // result = child; - // child = c.child.as_mut(); - // } - // - // return result; - } -} - -pub struct StateDbUnitOfWork { - child: Option>>, -} - -impl StateDbUnitOfWork { - pub fn new_unit_of_work(&mut self) -> &mut StateDbUnitOfWork { - // TODO: better implementation - self - } - - pub fn commit(&mut self) -> Result<(), StorageError> { - Ok(()) - } } diff --git a/dan_layer/core/src/workers/consensus_worker.rs b/dan_layer/core/src/workers/consensus_worker.rs index 3ae93f4110..9ce1c176d3 100644 --- a/dan_layer/core/src/workers/consensus_worker.rs +++ b/dan_layer/core/src/workers/consensus_worker.rs @@ -40,7 +40,7 @@ use crate::{ PayloadProvider, SigningService, }, - storage::{BackendAdapter, ChainStorageService, DbFactory}, + storage::{BackendAdapter, ChainStorageService, DbFactory, StateDbUnitOfWork, StateDbUnitOfWorkImpl, UnitOfWork}, workers::{states, states::ConsensusWorkerStateEvent}, }; use log::*; @@ -87,9 +87,7 @@ pub struct ConsensusWorker< timeout: Duration, node_id: TAddr, payload_provider: TPayloadProvider, - prepare_qc: Arc>, events_publisher: TEventsPublisher, - locked_qc: Arc>, signing_service: TSigningService, payload_processor: TPayloadProcessor, asset_definition: AssetDefinition, @@ -97,6 +95,9 @@ pub struct ConsensusWorker< db_factory: TDbFactory, chain_storage_service: TChainStorageService, pd: PhantomData, + pd2: PhantomData, + // TODO: Make generic + state_db_unit_of_work: Option, } impl< @@ -139,10 +140,9 @@ where TSigningService: SigningService, TPayloadProcessor: PayloadProcessor, TCommitteeManager: CommitteeManager, - TBaseNodeClient: BaseNodeClient, // TODO: REmove this Send - TBackendAdapter: BackendAdapter + Send + Sync, + TBackendAdapter: BackendAdapter + Send + Sync, TDbFactory: DbFactory + Clone, TChainStorageService: ChainStorageService, { @@ -162,7 +162,7 @@ where db_factory: TDbFactory, chain_storage_service: TChainStorageService, ) -> Self { - let prepare_qc = Arc::new(QuorumCertificate::genesis(payload_provider.create_genesis_payload())); + // let prepare_qc = Arc::new(QuorumCertificate::genesis(ash())); Self { inbound_connections, @@ -172,8 +172,6 @@ where outbound_service, committee_manager, node_id, - locked_qc: prepare_qc.clone(), - prepare_qc, payload_provider, events_publisher, signing_service, @@ -183,6 +181,8 @@ where db_factory, chain_storage_service, pd: PhantomData, + pd2: PhantomData, + state_db_unit_of_work: None, } } @@ -251,72 +251,101 @@ where .await }, Prepare => { - let mut p = states::Prepare::new(self.node_id.clone(), self.locked_qc.clone(), self.db_factory.clone()); - p.next_event( - &self.get_current_view()?, - self.timeout, - self.committee_manager.current_committee()?, - &mut self.inbound_connections, - &mut self.outbound_service, - &self.payload_provider, - &self.signing_service, - &mut self.payload_processor, - ) - .await + let db = self.db_factory.create()?; + let mut unit_of_work = db.new_unit_of_work(); + let mut state_tx = self.db_factory.create_state_db()?.new_unit_of_work(); + let mut p = states::Prepare::new(self.node_id.clone(), self.db_factory.clone()); + let res = p + .next_event( + &self.get_current_view()?, + self.timeout, + self.committee_manager.current_committee()?, + &mut self.inbound_connections, + &mut self.outbound_service, + &self.payload_provider, + &self.signing_service, + &mut self.payload_processor, + &self.chain_storage_service, + unit_of_work.clone(), + &mut state_tx, + ) + .await?; + // Will only be committed in DECIDE + self.state_db_unit_of_work = Some(state_tx); + unit_of_work.commit()?; + Ok(res) }, PreCommit => { + let db = self.db_factory.create()?; + let mut unit_of_work = db.new_unit_of_work(); let mut state = states::PreCommitState::new( self.node_id.clone(), self.committee_manager.current_committee()?.clone(), ); - let (res, prepare_qc) = state + let res = state .next_event( self.timeout, &self.get_current_view()?, &mut self.inbound_connections, &mut self.outbound_service, &self.signing_service, + unit_of_work.clone(), ) .await?; - if let Some(prepare_qc) = prepare_qc { - self.prepare_qc = Arc::new(prepare_qc); - } + unit_of_work.commit()?; Ok(res) }, Commit => { + let db = self.db_factory.create()?; + let mut unit_of_work = db.new_unit_of_work(); let mut state = states::CommitState::new( self.node_id.clone(), self.committee_manager.current_committee()?.clone(), ); - let (res, locked_qc) = state + let res = state .next_event( self.timeout, &self.get_current_view()?, &mut self.inbound_connections, &mut self.outbound_service, &self.signing_service, + unit_of_work.clone(), ) .await?; - if let Some(locked_qc) = locked_qc { - self.locked_qc = Arc::new(locked_qc); - } + + unit_of_work.commit()?; + Ok(res) }, Decide => { + let db = self.db_factory.create()?; + let mut unit_of_work = db.new_unit_of_work(); let mut state = states::DecideState::new( self.node_id.clone(), self.committee_manager.current_committee()?.clone(), ); - state + let res = state .next_event( self.timeout, &self.get_current_view()?, &mut self.inbound_connections, &mut self.outbound_service, - &self.signing_service, + unit_of_work.clone(), ) - .await + .await?; + unit_of_work.commit()?; + if let Some(mut state_tx) = self.state_db_unit_of_work.take() { + state_tx.commit()?; + } else { + // technically impossible + error!(target: LOG_TARGET, "No state unit of work was present"); + return Err(DigitalAssetError::InvalidLogicPath { + reason: "Tried to commit state after DECIDE, but no state tx was present".to_string(), + }); + } + + Ok(res) }, NextView => { info!( @@ -324,11 +353,12 @@ where "Status: {} in mempool ", self.payload_provider.get_payload_queue().await, ); + self.state_db_unit_of_work = None; let mut state = states::NextViewState::new(); state .next_event( &self.get_current_view()?, - self.prepare_qc.as_ref().clone(), + &self.db_factory, &mut self.outbound_service, self.committee_manager.current_committee()?, self.node_id.clone(), @@ -352,7 +382,7 @@ where use ConsensusWorkerStateEvent::*; let from = self.state; self.state = match (&self.state, event) { - (Starting, Initialized) => Prepare, + (Starting, Initialized) => NextView, (_, NotPartOfCommittee) => Idle, (Idle, TimedOut) => Starting, (_, TimedOut) => NextView, diff --git a/dan_layer/core/src/workers/states/commit_state.rs b/dan_layer/core/src/workers/states/commit_state.rs index 2c8ea178ce..5ef0ef9322 100644 --- a/dan_layer/core/src/workers/states/commit_state.rs +++ b/dan_layer/core/src/workers/states/commit_state.rs @@ -29,6 +29,7 @@ use crate::{ HotStuffTreeNode, Payload, QuorumCertificate, + TreeNodeHash, View, ViewId, }, @@ -36,6 +37,7 @@ use crate::{ infrastructure_services::{InboundConnectionService, NodeAddressable, OutboundService}, SigningService, }, + storage::UnitOfWork, workers::states::ConsensusWorkerStateEvent, }; use std::{collections::HashMap, marker::PhantomData, time::Instant}; @@ -58,8 +60,6 @@ where p_p: PhantomData, p_s: PhantomData, received_new_view_messages: HashMap>, - pre_commit_qc: Option>, - locked_qc: Option>, } impl @@ -80,20 +80,19 @@ where ta: PhantomData, p_p: PhantomData, received_new_view_messages: HashMap::new(), - pre_commit_qc: None, - locked_qc: None, p_s: PhantomData, } } - pub async fn next_event( + pub async fn next_event( &mut self, timeout: Duration, current_view: &View, inbound_services: &mut TInboundConnectionService, outbound_service: &mut TOutboundService, signing_service: &TSigningService, - ) -> Result<(ConsensusWorkerStateEvent, Option>), DigitalAssetError> { + unit_of_work: TUnitOfWork, + ) -> Result { let mut next_event_result = ConsensusWorkerStateEvent::Errored { reason: "loop ended without setting this event".to_string(), }; @@ -102,6 +101,7 @@ where self.received_new_view_messages.clear(); // TODO: rather change the loop below to inside the wait for message let started = Instant::now(); + let mut unit_of_work = unit_of_work; loop { tokio::select! { (from, message) = self.wait_for_message(inbound_services) => { @@ -114,7 +114,7 @@ where } let leader= self.committee.leader_for_view(current_view.view_id).clone(); - if let Some(result) = self.process_replica_message(&message, current_view, &from, &leader, outbound_service, signing_service).await? { + if let Some(result) = self.process_replica_message(&message, current_view, &from, &leader, outbound_service, signing_service, &mut unit_of_work).await? { next_event_result = result; break; } @@ -127,7 +127,7 @@ where } } } - Ok((next_event_result, self.locked_qc.clone())) + Ok(next_event_result) } async fn wait_for_message( @@ -164,19 +164,11 @@ where ); if let Some(qc) = self.create_qc(current_view) { - self.pre_commit_qc = Some(qc.clone()); self.broadcast(outbound, qc, current_view.view_id).await?; - // return Ok(Some(ConsensusWorkerStateEvent::PreCommitted)); return Ok(None); // Replica will move this on } dbg!("committee did not agree on node"); Ok(None) - - // let high_qc = self.find_highest_qc(); - // let proposal = self.create_proposal(high_qc.node(), payload_provider); - // self.broadcast_proposal(outbound, proposal, high_qc, current_view.view_id) - // .await?; - // Ok(Some(ConsensusWorkerStateEvent::Prepared)) } else { println!( "[COMMIT] Consensus has NOT YET been reached with {:?} out of {} votes", @@ -190,7 +182,7 @@ where async fn broadcast( &self, outbound: &mut TOutboundService, - pre_commit_qc: QuorumCertificate, + pre_commit_qc: QuorumCertificate, view_number: ViewId, ) -> Result<(), DigitalAssetError> { let message = HotStuffMessage::commit(None, Some(pre_commit_qc), view_number); @@ -199,14 +191,14 @@ where .await } - fn create_qc(&self, current_view: &View) -> Option> { + fn create_qc(&self, current_view: &View) -> Option { // TODO: This can be done in one loop instead of two - let mut node = None; + let mut node_hash = None; for message in self.received_new_view_messages.values() { - node = match node { - None => message.node().cloned(), + node_hash = match node_hash { + None => message.node_hash().cloned(), Some(n) => { - if let Some(m_node) = message.node() { + if let Some(m_node) = message.node_hash() { if &n != m_node { unimplemented!("Nodes did not match"); } @@ -218,15 +210,15 @@ where }; } - let node = node.unwrap(); - let mut qc = QuorumCertificate::new(HotStuffMessageType::PreCommit, current_view.view_id, node, None); + let node_hash = node_hash.unwrap(); + let mut qc = QuorumCertificate::new(HotStuffMessageType::PreCommit, current_view.view_id, node_hash, None); for message in self.received_new_view_messages.values() { qc.combine_sig(message.partial_sig().unwrap()) } Some(qc) } - async fn process_replica_message( + async fn process_replica_message( &mut self, message: &HotStuffMessage, current_view: &View, @@ -234,6 +226,7 @@ where view_leader: &TAddr, outbound: &mut TOutboundService, signing_service: &TSigningService, + unit_of_work: &mut TUnitOfWork, ) -> Result, DigitalAssetError> { if let Some(justify) = message.justify() { if !justify.matches(HotStuffMessageType::PreCommit, current_view.view_id) { @@ -254,9 +247,9 @@ where return Ok(None); } - self.locked_qc = Some(justify.clone()); + unit_of_work.set_locked_qc(justify); self.send_vote_to_leader( - justify.node(), + justify.node_hash().clone(), outbound, view_leader, current_view.view_id, @@ -272,13 +265,13 @@ where async fn send_vote_to_leader( &self, - node: &HotStuffTreeNode, + node: TreeNodeHash, outbound: &mut TOutboundService, view_leader: &TAddr, view_number: ViewId, signing_service: &TSigningService, ) -> Result<(), DigitalAssetError> { - let mut message = HotStuffMessage::commit(Some(node.clone()), None, view_number); + let mut message = HotStuffMessage::vote_commit(node, view_number); message.add_partial_sig(signing_service.sign(&self.node_id, &message.create_signature_challenge())?); outbound.send(self.node_id.clone(), view_leader.clone(), message).await } diff --git a/dan_layer/core/src/workers/states/decide_state.rs b/dan_layer/core/src/workers/states/decide_state.rs index 0e514169a4..353d198b2e 100644 --- a/dan_layer/core/src/workers/states/decide_state.rs +++ b/dan_layer/core/src/workers/states/decide_state.rs @@ -36,19 +36,19 @@ use crate::{ infrastructure_services::{InboundConnectionService, NodeAddressable, OutboundService}, SigningService, }, + storage::UnitOfWork, workers::states::ConsensusWorkerStateEvent, }; use std::{collections::HashMap, marker::PhantomData, time::Instant}; use tokio::time::{sleep, Duration}; // TODO: This is very similar to pre-commit, and commit state -pub struct DecideState +pub struct DecideState where TInboundConnectionService: InboundConnectionService, TAddr: NodeAddressable, TPayload: Payload, TOutboundService: OutboundService, - TSigningService: SigningService, { node_id: TAddr, committee: Committee, @@ -56,20 +56,16 @@ where phantom_outbound: PhantomData, ta: PhantomData, p_p: PhantomData, - p_s: PhantomData, received_new_view_messages: HashMap>, - commit_qc: Option>, - _locked_qc: Option>, } -impl - DecideState +impl + DecideState where TInboundConnectionService: InboundConnectionService, TOutboundService: OutboundService, TAddr: NodeAddressable, TPayload: Payload, - TSigningService: SigningService, { pub fn new(node_id: TAddr, committee: Committee) -> Self { Self { @@ -80,19 +76,16 @@ where ta: PhantomData, p_p: PhantomData, received_new_view_messages: HashMap::new(), - commit_qc: None, - _locked_qc: None, - p_s: PhantomData, } } - pub async fn next_event( + pub async fn next_event( &mut self, timeout: Duration, current_view: &View, inbound_services: &mut TInboundConnectionService, outbound_service: &mut TOutboundService, - _signing_service: &TSigningService, + unit_of_work: TUnitOfWork, ) -> Result { let mut next_event_result = ConsensusWorkerStateEvent::Errored { reason: "loop ended without setting this event".to_string(), @@ -101,6 +94,7 @@ where self.received_new_view_messages.clear(); let started = Instant::now(); + let mut unit_of_work = unit_of_work; loop { tokio::select! { (from, message) = self.wait_for_message(inbound_services) => { @@ -113,7 +107,7 @@ where } let leader= self.committee.leader_for_view(current_view.view_id).clone(); - if let Some(result) = self.process_replica_message(&message, current_view, &from, &leader).await? { + if let Some(result) = self.process_replica_message(&message, current_view, &from, &leader, &mut unit_of_work).await? { next_event_result = result; break; } @@ -161,7 +155,6 @@ where ); if let Some(qc) = self.create_qc(current_view) { - self.commit_qc = Some(qc.clone()); self.broadcast(outbound, qc, current_view.view_id).await?; return Ok(None); // Replica will move this on } @@ -180,7 +173,7 @@ where async fn broadcast( &self, outbound: &mut TOutboundService, - commit_qc: QuorumCertificate, + commit_qc: QuorumCertificate, view_number: ViewId, ) -> Result<(), DigitalAssetError> { let message = HotStuffMessage::decide(None, Some(commit_qc), view_number); @@ -189,13 +182,13 @@ where .await } - fn create_qc(&self, current_view: &View) -> Option> { - let mut node = None; + fn create_qc(&self, current_view: &View) -> Option { + let mut node_hash = None; for message in self.received_new_view_messages.values() { - node = match node { - None => message.node().cloned(), + node_hash = match node_hash { + None => message.node_hash().cloned(), Some(n) => { - if let Some(m_node) = message.node() { + if let Some(m_node) = message.node_hash() { if &n != m_node { unimplemented!("Nodes did not match"); } @@ -207,20 +200,21 @@ where }; } - let node = node.unwrap(); - let mut qc = QuorumCertificate::new(HotStuffMessageType::Commit, current_view.view_id, node, None); + let node_hash = node_hash.unwrap(); + let mut qc = QuorumCertificate::new(HotStuffMessageType::Commit, current_view.view_id, node_hash, None); for message in self.received_new_view_messages.values() { qc.combine_sig(message.partial_sig().unwrap()) } Some(qc) } - async fn process_replica_message( + async fn process_replica_message( &mut self, message: &HotStuffMessage, current_view: &View, from: &TAddr, view_leader: &TAddr, + unit_of_work: &mut TUnitOfWork, ) -> Result, DigitalAssetError> { if let Some(justify) = message.justify() { if !justify.matches(HotStuffMessageType::Commit, current_view.view_id) { @@ -241,33 +235,11 @@ where return Ok(None); } - // self.locked_qc = Some(justify.clone()); - // self.send_vote_to_leader( - // justify.node(), - // outbound, - // view_leader, - // current_view.view_id, - // &signing_service, - // ) - // .await?; - + unit_of_work.commit_node(justify.node_hash()); Ok(Some(ConsensusWorkerStateEvent::Decided)) } else { dbg!("received non justify message"); Ok(None) } } - - async fn _send_vote_to_leader( - &self, - node: &HotStuffTreeNode, - outbound: &mut TOutboundService, - view_leader: &TAddr, - view_number: ViewId, - signing_service: &TSigningService, - ) -> Result<(), DigitalAssetError> { - let mut message = HotStuffMessage::commit(Some(node.clone()), None, view_number); - message.add_partial_sig(signing_service.sign(&self.node_id, &message.create_signature_challenge())?); - outbound.send(self.node_id.clone(), view_leader.clone(), message).await - } } diff --git a/dan_layer/core/src/workers/states/next_view.rs b/dan_layer/core/src/workers/states/next_view.rs index 9ee5cc17aa..ceeac21d7a 100644 --- a/dan_layer/core/src/workers/states/next_view.rs +++ b/dan_layer/core/src/workers/states/next_view.rs @@ -24,6 +24,7 @@ use crate::{ digital_assets_error::DigitalAssetError, models::{Committee, HotStuffMessage, Payload, QuorumCertificate, View}, services::infrastructure_services::{NodeAddressable, OutboundService}, + storage::{BackendAdapter, DbFactory}, workers::states::ConsensusWorkerStateEvent, }; use log::*; @@ -42,15 +43,19 @@ impl NextViewState { TPayload: Payload, TOutboundService: OutboundService, TAddr: NodeAddressable + Clone + Send, + TBackendAdapter: BackendAdapter, + TDbFactory: DbFactory, >( &mut self, current_view: &View, - prepare_qc: QuorumCertificate, + db_factory: &TDbFactory, broadcast: &mut TOutboundService, committee: &Committee, node_id: TAddr, _shutdown: &ShutdownSignal, ) -> Result { + let db = db_factory.create()?; + let prepare_qc = db.find_highest_prepared_qc()?; let message = HotStuffMessage::new_view(prepare_qc, current_view.view_id); let next_view = current_view.view_id.next(); let leader = committee.leader_for_view(next_view); diff --git a/dan_layer/core/src/workers/states/pre_commit_state.rs b/dan_layer/core/src/workers/states/pre_commit_state.rs index dbbfb4c068..4c9bccbe1d 100644 --- a/dan_layer/core/src/workers/states/pre_commit_state.rs +++ b/dan_layer/core/src/workers/states/pre_commit_state.rs @@ -29,6 +29,7 @@ use crate::{ HotStuffTreeNode, Payload, QuorumCertificate, + TreeNodeHash, View, ViewId, }, @@ -36,6 +37,7 @@ use crate::{ infrastructure_services::{InboundConnectionService, NodeAddressable, OutboundService}, SigningService, }, + storage::UnitOfWork, workers::states::ConsensusWorkerStateEvent, }; use std::{collections::HashMap, marker::PhantomData, time::Instant}; @@ -57,7 +59,6 @@ where p_p: PhantomData, p_s: PhantomData, received_new_view_messages: HashMap>, - prepare_qc: Option>, } impl @@ -78,19 +79,19 @@ where ta: PhantomData, p_p: PhantomData, received_new_view_messages: HashMap::new(), - prepare_qc: None, p_s: PhantomData, } } - pub async fn next_event( + pub async fn next_event( &mut self, timeout: Duration, current_view: &View, inbound_services: &mut TInboundConnectionService, outbound_service: &mut TOutboundService, signing_service: &TSigningService, - ) -> Result<(ConsensusWorkerStateEvent, Option>), DigitalAssetError> { + unit_of_work: TUnitOfWork, + ) -> Result { let mut next_event_result = ConsensusWorkerStateEvent::Errored { reason: "loop ended without setting this event".to_string(), }; @@ -98,6 +99,7 @@ where self.received_new_view_messages.clear(); let started = Instant::now(); + let mut unit_of_work = unit_of_work; loop { tokio::select! { (from, message) = self.wait_for_message(inbound_services) => { @@ -110,7 +112,7 @@ where } let leader= self.committee.leader_for_view(current_view.view_id).clone(); - if let Some(result) = self.process_replica_message(&message, current_view, &from, &leader, outbound_service, signing_service).await? { + if let Some(result) = self.process_replica_message(&message, current_view, &from, &leader, outbound_service, signing_service, &mut unit_of_work).await? { next_event_result = result; break; } @@ -122,7 +124,7 @@ where } } } - Ok((next_event_result, self.prepare_qc.clone())) + Ok(next_event_result) } async fn wait_for_message( @@ -158,7 +160,6 @@ where ); if let Some(qc) = self.create_qc(current_view) { - self.prepare_qc = Some(qc.clone()); self.broadcast(outbound, &self.committee, qc, current_view.view_id) .await?; // return Ok(Some(ConsensusWorkerStateEvent::PreCommitted)); @@ -186,7 +187,7 @@ where &self, outbound: &mut TOutboundService, committee: &Committee, - prepare_qc: QuorumCertificate, + prepare_qc: QuorumCertificate, view_number: ViewId, ) -> Result<(), DigitalAssetError> { let message = HotStuffMessage::pre_commit(None, Some(prepare_qc), view_number); @@ -195,13 +196,13 @@ where .await } - fn create_qc(&self, current_view: &View) -> Option> { + fn create_qc(&self, current_view: &View) -> Option { let mut node = None; for message in self.received_new_view_messages.values() { node = match node { - None => message.node().cloned(), + None => message.node_hash().cloned(), Some(n) => { - if let Some(m_node) = message.node() { + if let Some(m_node) = message.node_hash() { if &n != m_node { unimplemented!("Nodes did not match"); } @@ -214,14 +215,14 @@ where } let node = node.unwrap(); - let mut qc = QuorumCertificate::new(HotStuffMessageType::Prepare, current_view.view_id, node, None); + let mut qc = QuorumCertificate::new(HotStuffMessageType::Prepare, current_view.view_id, node.clone(), None); for message in self.received_new_view_messages.values() { qc.combine_sig(message.partial_sig().unwrap()) } Some(qc) } - async fn process_replica_message( + async fn process_replica_message( &mut self, message: &HotStuffMessage, current_view: &View, @@ -229,6 +230,7 @@ where view_leader: &TAddr, outbound: &mut TOutboundService, signing_service: &TSigningService, + unit_of_work: &mut TUnitOfWork, ) -> Result, DigitalAssetError> { if let Some(justify) = message.justify() { if !justify.matches(HotStuffMessageType::Prepare, current_view.view_id) { @@ -249,9 +251,9 @@ where return Ok(None); } - self.prepare_qc = Some(justify.clone()); + unit_of_work.set_prepare_qc(justify); self.send_vote_to_leader( - justify.node(), + justify.node_hash().clone(), outbound, view_leader, current_view.view_id, @@ -267,13 +269,13 @@ where async fn send_vote_to_leader( &self, - node: &HotStuffTreeNode, + node: TreeNodeHash, outbound: &mut TOutboundService, view_leader: &TAddr, view_number: ViewId, signing_service: &TSigningService, ) -> Result<(), DigitalAssetError> { - let mut message = HotStuffMessage::pre_commit(Some(node.clone()), None, view_number); + let mut message = HotStuffMessage::vote_pre_commit(node.clone(), view_number); message.add_partial_sig(signing_service.sign(&self.node_id, &message.create_signature_challenge())?); outbound.send(self.node_id.clone(), view_leader.clone(), message).await } diff --git a/dan_layer/core/src/workers/states/prepare.rs b/dan_layer/core/src/workers/states/prepare.rs index d7ee3c4154..5985f17a04 100644 --- a/dan_layer/core/src/workers/states/prepare.rs +++ b/dan_layer/core/src/workers/states/prepare.rs @@ -43,8 +43,9 @@ use log::*; use std::{collections::HashMap, marker::PhantomData, sync::Arc, time::Instant}; use crate::{ + models::TreeNodeHash, services::PayloadProcessor, - storage::{BackendAdapter, DbFactory}, + storage::{BackendAdapter, ChainStorageService, DbFactory, StateDbUnitOfWork, StorageError, UnitOfWork}, }; use tokio::time::{sleep, Duration}; @@ -72,7 +73,6 @@ pub struct Prepare< TDbFactory: DbFactory, { node_id: TAddr, - locked_qc: Arc>, // bft_service: Box, // TODO remove this hack phantom: PhantomData, @@ -118,10 +118,9 @@ where TBackendAdapter: BackendAdapter + Send + Sync, TDbFactory: DbFactory + Clone, { - pub fn new(node_id: TAddr, locked_qc: Arc>, db_factory: TDbFactory) -> Self { + pub fn new(node_id: TAddr, db_factory: TDbFactory) -> Self { Self { node_id, - locked_qc, phantom: PhantomData, phantom_payload_provider: PhantomData, phantom_outbound: PhantomData, @@ -134,7 +133,11 @@ where } #[allow(clippy::too_many_arguments)] - pub async fn next_event( + pub async fn next_event< + TChainStorageService: ChainStorageService, + TUnitOfWork: UnitOfWork, + TStateDbUnitOfWork: StateDbUnitOfWork, + >( &mut self, current_view: &View, timeout: Duration, @@ -144,6 +147,9 @@ where payload_provider: &TPayloadProvider, signing_service: &TSigningService, payload_processor: &mut TPayloadProcessor, + chain_storage_service: &TChainStorageService, + chain_tx: TUnitOfWork, + state_tx: &mut TStateDbUnitOfWork, ) -> Result { self.received_new_view_messages.clear(); @@ -153,6 +159,7 @@ where trace!(target: LOG_TARGET, "next_event_result: {:?}", next_event_result); let started = Instant::now(); + let mut chain_tx = chain_tx; loop { tokio::select! { @@ -167,7 +174,7 @@ where } if let Some(result) = self.process_replica_message(&message, current_view, &from, committee.leader_for_view(current_view.view_id), outbound_service, signing_service, - payload_processor).await? { + payload_processor, &mut chain_tx, chain_storage_service, state_tx).await? { next_event_result = result; break; } @@ -202,6 +209,23 @@ where outbound: &mut TOutboundService, ) -> Result, DigitalAssetError> { if message.message_type() != &HotStuffMessageType::NewView { + warn!( + target: LOG_TARGET, + "{} sent wrong message of type {:?}. Expecting NEW_VIEW", + sender, + message.message_type() + ); + return Ok(None); + } + + if message.view_number() != current_view.view_id - 1.into() { + warn!( + target: LOG_TARGET, + "{} sent wrong view number for NEW_VIEW message. Expecting {}, got {}", + sender, + current_view.view_id - 1.into(), + message.view_number() + ); return Ok(None); } @@ -220,10 +244,13 @@ where committee.len() ); let high_qc = self.find_highest_qc(); - let proposal = self.create_proposal(high_qc.node(), payload_provider).await?; + + // TODO: get actual height + let proposal = self + .create_proposal(high_qc.node_hash().clone(), payload_provider, 0) + .await?; self.broadcast_proposal(outbound, committee, proposal, high_qc, current_view.view_id) .await?; - // Ok(Some(ConsensusWorkerStateEvent::Prepared)) Ok(None) // Will move to pre-commit when it receives the message as a replica } else { println!( @@ -235,7 +262,11 @@ where } } - async fn process_replica_message( + async fn process_replica_message< + TUnitOfWork: UnitOfWork, + TChainStorageService: ChainStorageService, + TStateDbUnitOfWork: StateDbUnitOfWork, + >( &self, message: &HotStuffMessage, current_view: &View, @@ -244,6 +275,9 @@ where outbound: &mut TOutboundService, signing_service: &TSigningService, payload_processor: &mut TPayloadProcessor, + chain_tx: &mut TUnitOfWork, + chain_storage_service: &TChainStorageService, + state_tx: &mut TStateDbUnitOfWork, ) -> Result, DigitalAssetError> { if !message.matches(HotStuffMessageType::Prepare, current_view.view_id) { // println!( @@ -263,22 +297,29 @@ where } let node = message.node().unwrap(); if let Some(justify) = message.justify() { - if self.does_extend(node, justify.node()) { - if !self.is_safe_node(node, justify) { + if self.does_extend(node, justify.node_hash()) { + if !self.is_safe_node(node, justify, chain_tx)? { unimplemented!("Node is not safe") } - let db = self.db_factory.create(); - let unit_of_work = db.new_unit_of_work(); - + dbg!(&node); let res = payload_processor - .process_payload(justify.node().payload(), unit_of_work) + .process_payload(node.payload(), state_tx.clone()) .await?; - - // TODO: Check result equals qc result - - self.send_vote_to_leader(node, outbound, view_leader, current_view.view_id, signing_service) + if res == node.payload().state_root() { + chain_storage_service + .add_node::(node, chain_tx.clone()) + .await?; + + self.send_vote_to_leader( + node.hash().clone(), + outbound, + view_leader, + current_view.view_id, + signing_service, + ) .await?; + } Ok(Some(ConsensusWorkerStateEvent::Prepared)) } else { unimplemented!("Did not extend from qc.justify.node") @@ -288,7 +329,7 @@ where } } - fn find_highest_qc(&self) -> QuorumCertificate { + fn find_highest_qc(&self) -> QuorumCertificate { let mut max_qc = None; for message in self.received_new_view_messages.values() { match &max_qc { @@ -308,14 +349,17 @@ where async fn create_proposal( &self, - parent: &HotStuffTreeNode, + parent: TreeNodeHash, payload_provider: &TPayloadProvider, + height: u32, ) -> Result, DigitalAssetError> { + info!(target: LOG_TARGET, "Creating new proposal"); + // TODO: Artificial delay here to set the block time sleep(Duration::from_secs(3)).await; let payload = payload_provider.create_payload().await?; - Ok(HotStuffTreeNode::from_parent(parent, payload)) + Ok(HotStuffTreeNode::from_parent(parent, payload, height)) } async fn broadcast_proposal( @@ -323,7 +367,7 @@ where outbound: &mut TOutboundService, committee: &Committee, proposal: HotStuffTreeNode, - high_qc: QuorumCertificate, + high_qc: QuorumCertificate, view_number: ViewId, ) -> Result<(), DigitalAssetError> { let message = HotStuffMessage::prepare(proposal, Some(high_qc), view_number); @@ -332,27 +376,30 @@ where .await } - fn does_extend(&self, node: &HotStuffTreeNode, from: &HotStuffTreeNode) -> bool { - &from.calculate_hash() == node.parent() + fn does_extend(&self, node: &HotStuffTreeNode, from: &TreeNodeHash) -> bool { + &from == &node.parent() } - fn is_safe_node( + fn is_safe_node( &self, node: &HotStuffTreeNode, - quorum_certificate: &QuorumCertificate, - ) -> bool { - self.does_extend(node, self.locked_qc.node()) || quorum_certificate.view_number() > self.locked_qc.view_number() + quorum_certificate: &QuorumCertificate, + chain_tx: &mut TUnitOfWork, + ) -> Result { + let locked_qc = chain_tx.get_locked_qc()?; + Ok(self.does_extend(node, locked_qc.node_hash()) || quorum_certificate.view_number() > locked_qc.view_number()) } async fn send_vote_to_leader( &self, - node: &HotStuffTreeNode, + node: TreeNodeHash, outbound: &mut TOutboundService, view_leader: &TAddr, view_number: ViewId, signing_service: &TSigningService, ) -> Result<(), DigitalAssetError> { - let mut message = HotStuffMessage::prepare(node.clone(), None, view_number); + // TODO: Only send node hash, not the full node + let mut message = HotStuffMessage::vote_prepare(node, view_number); message.add_partial_sig(signing_service.sign(&self.node_id, &message.create_signature_challenge())?); outbound.send(self.node_id.clone(), view_leader.clone(), message).await } diff --git a/dan_layer/core/src/workers/states/starting.rs b/dan_layer/core/src/workers/states/starting.rs index a7fa4ce0d7..d00cd36080 100644 --- a/dan_layer/core/src/workers/states/starting.rs +++ b/dan_layer/core/src/workers/states/starting.rs @@ -22,7 +22,7 @@ use crate::{ digital_assets_error::DigitalAssetError, - models::{AssetDefinition, Payload, QuorumCertificate, TariDanPayload}, + models::{AssetDefinition, HotStuffTreeNode, Payload, QuorumCertificate, TariDanPayload}, services::{ infrastructure_services::NodeAddressable, BaseNodeClient, @@ -30,7 +30,7 @@ use crate::{ PayloadProcessor, PayloadProvider, }, - storage::{BackendAdapter, ChainStorageService, DbFactory, UnitOfWork}, + storage::{BackendAdapter, ChainStorageService, DbFactory, StateDbUnitOfWork, UnitOfWork}, workers::states::ConsensusWorkerStateEvent, }; use log::*; @@ -98,17 +98,25 @@ where TBaseNodeClient: BaseNodeClient } // read and create the genesis block - let chain_db = db_factory.create(); - if chain_db.is_empty() { + let chain_db = db_factory.create()?; + if chain_db.is_empty()? { let mut tx = chain_db.new_unit_of_work(); - let tx2 = tx.clone(); - // let metadata = chain_db.metadata.read(&mut tx); - let payload = payload_provider.create_genesis_payload(); + let state_db = db_factory.create_state_db()?; + let mut state_tx = state_db.new_unit_of_work(); - payload_processor.process_payload(&payload, tx2).await?; - let genesis_qc = QuorumCertificate::genesis(payload); - chain_storage_service.save_node(genesis_qc.node(), tx.clone()).await?; + let initial_state = asset_definition.initial_state(); + for schema in &initial_state.schemas { + for key_value in &schema.items { + state_tx.set_value(schema.name.clone(), key_value.key.clone(), key_value.value.clone()); + } + } + let node = HotStuffTreeNode::genesis(payload_provider.create_genesis_payload()); + let genesis_qc = QuorumCertificate::genesis(node.hash().clone()); + chain_storage_service.add_node(&node, tx.clone()).await?; + tx.commit_node(node.hash())?; + chain_storage_service.set_locked_qc(genesis_qc, tx.clone()).await?; + state_tx.commit()?; tx.commit()?; } diff --git a/dan_layer/storage_sqlite/Cargo.toml b/dan_layer/storage_sqlite/Cargo.toml new file mode 100644 index 0000000000..175e1fce1e --- /dev/null +++ b/dan_layer/storage_sqlite/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "tari_dan_storage_sqlite" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tari_dan_core = {path="../core"} +tari_common = { path = "../../common"} +diesel = { version = "1.4.8", features = ["sqlite"] } +diesel_migrations = "1.4.0" +dotenv = "0.15.0" +thiserror = "1.0.30" +async-trait = "0.1.50" +tokio = { version="1.10", features = ["macros", "time"]} +tokio-stream = { version = "0.1.7", features = ["sync"] } \ No newline at end of file diff --git a/dan_layer/storage_sqlite/diesel.toml b/dan_layer/storage_sqlite/diesel.toml new file mode 100644 index 0000000000..92267c829f --- /dev/null +++ b/dan_layer/storage_sqlite/diesel.toml @@ -0,0 +1,5 @@ +# For documentation on how to configure this file, +# see diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "src/schema.rs" diff --git a/dan_layer/storage_sqlite/migrations/.gitkeep b/dan_layer/storage_sqlite/migrations/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/dan_layer/validator_node_sqlite/migrations/2021-11-02-185150_create_nodes/down.sql b/dan_layer/storage_sqlite/migrations/2021-11-02-185150_create_nodes/down.sql similarity index 100% rename from dan_layer/validator_node_sqlite/migrations/2021-11-02-185150_create_nodes/down.sql rename to dan_layer/storage_sqlite/migrations/2021-11-02-185150_create_nodes/down.sql diff --git a/dan_layer/storage_sqlite/migrations/2021-11-02-185150_create_nodes/up.sql b/dan_layer/storage_sqlite/migrations/2021-11-02-185150_create_nodes/up.sql new file mode 100644 index 0000000000..61407d524e --- /dev/null +++ b/dan_layer/storage_sqlite/migrations/2021-11-02-185150_create_nodes/up.sql @@ -0,0 +1,43 @@ +-- Your SQL goes here +create table nodes ( + id integer primary key autoincrement not null, + hash blob not null unique, + parent blob not null, + height integer not null, + is_committed boolean not null DEFAULT FALSE +); + +create table instructions ( + id integer primary key autoincrement not null, + hash blob not null, + node_id integer not null , + template_id int not null, + method text not null, + args blob not null, + foreign key (node_id) references nodes(id) +); + + +create table locked_qc ( + id integer primary key not null, -- should always be 1 row + message_type integer not null, + view_number bigint not null, + node_hash blob not null, + signature blob null +); + +create table prepare_qc ( + id integer primary key not null, + message_type integer not null, + view_number bigint not null, + node_hash blob not null, + signature blob null +); + + +create table state_key_values ( + id integer primary key autoincrement not null, + schema_name text not null, + key blob not null, + value blob not null +); \ No newline at end of file diff --git a/dan_layer/storage_sqlite/src/error.rs b/dan_layer/storage_sqlite/src/error.rs new file mode 100644 index 0000000000..574a17bf2d --- /dev/null +++ b/dan_layer/storage_sqlite/src/error.rs @@ -0,0 +1,59 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use diesel; +use tari_dan_core::storage::StorageError; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum SqliteStorageError { + #[error("Could not connect to database: {source}")] + ConnectionError { + #[from] + source: diesel::ConnectionError, + }, + #[error("General diesel error: {source}")] + DieselError { + #[from] + source: diesel::result::Error, + }, + #[error("Could not migrate the database")] + MigrationError { + #[from] + source: diesel_migrations::RunMigrationsError, + }, +} + +impl From for StorageError { + fn from(source: SqliteStorageError) -> Self { + match source { + SqliteStorageError::ConnectionError { .. } => StorageError::ConnectionError { + reason: source.to_string(), + }, + SqliteStorageError::DieselError { .. } => StorageError::QueryError { + reason: source.to_string(), + }, + SqliteStorageError::MigrationError { .. } => StorageError::MigrationError { + reason: source.to_string(), + }, + } + } +} diff --git a/dan_layer/validator_node_sqlite/src/lib.rs b/dan_layer/storage_sqlite/src/lib.rs similarity index 80% rename from dan_layer/validator_node_sqlite/src/lib.rs rename to dan_layer/storage_sqlite/src/lib.rs index dfed793316..8decae6264 100644 --- a/dan_layer/validator_node_sqlite/src/lib.rs +++ b/dan_layer/storage_sqlite/src/lib.rs @@ -22,5 +22,17 @@ #[macro_use] extern crate diesel; +#[macro_use] +extern crate diesel_migrations; +pub mod error; mod schema; +mod sqlite_backend_adapter; +pub use sqlite_backend_adapter::SqliteBackendAdapter; +mod sqlite_transaction; +pub use sqlite_transaction::SqliteTransaction; +mod sqlite_db_factory; +pub use sqlite_db_factory::SqliteDbFactory; +mod models; +mod sqlite_storage_service; +pub use sqlite_storage_service::SqliteStorageService; diff --git a/dan_layer/storage_sqlite/src/models/instruction.rs b/dan_layer/storage_sqlite/src/models/instruction.rs new file mode 100644 index 0000000000..683c4a39d6 --- /dev/null +++ b/dan_layer/storage_sqlite/src/models/instruction.rs @@ -0,0 +1,42 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use crate::schema::*; + +#[derive(Queryable)] +pub struct Instruction { + pub id: i32, + pub hash: Vec, + pub node_hash: Vec, + pub template_id: i32, + pub method: String, + pub args: Vec, +} + +#[derive(Insertable)] +#[table_name = "instructions"] +pub struct NewInstruction { + pub hash: Vec, + pub node_id: i32, + pub template_id: i32, + pub method: String, + pub args: Vec, +} diff --git a/dan_layer/storage_sqlite/src/models/locked_qc.rs b/dan_layer/storage_sqlite/src/models/locked_qc.rs new file mode 100644 index 0000000000..460b9fe323 --- /dev/null +++ b/dan_layer/storage_sqlite/src/models/locked_qc.rs @@ -0,0 +1,30 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#[derive(Queryable)] +pub struct LockedQc { + pub id: i32, + pub message_type: i32, + pub view_number: i64, + pub node_hash: Vec, + pub signature: Option>, +} diff --git a/dan_layer/storage_sqlite/src/models/mod.rs b/dan_layer/storage_sqlite/src/models/mod.rs new file mode 100644 index 0000000000..c29a9c4202 --- /dev/null +++ b/dan_layer/storage_sqlite/src/models/mod.rs @@ -0,0 +1,27 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +pub mod instruction; +pub mod locked_qc; +pub mod node; +pub mod prepare_qc; +pub mod state_key_value; diff --git a/dan_layer/storage_sqlite/src/models/node.rs b/dan_layer/storage_sqlite/src/models/node.rs new file mode 100644 index 0000000000..571d78c072 --- /dev/null +++ b/dan_layer/storage_sqlite/src/models/node.rs @@ -0,0 +1,40 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::schema::*; + +#[derive(Queryable)] +pub struct Node { + pub id: i32, + pub hash: Vec, + pub parent: Vec, + pub height: i32, + pub is_committed: bool, +} + +#[derive(Insertable)] +#[table_name = "nodes"] +pub struct NewNode { + pub hash: Vec, + pub parent: Vec, + pub height: i32, +} diff --git a/dan_layer/storage_sqlite/src/models/prepare_qc.rs b/dan_layer/storage_sqlite/src/models/prepare_qc.rs new file mode 100644 index 0000000000..99a39f4b2b --- /dev/null +++ b/dan_layer/storage_sqlite/src/models/prepare_qc.rs @@ -0,0 +1,30 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#[derive(Queryable)] +pub struct PrepareQc { + pub id: i32, + pub message_type: i32, + pub view_number: i64, + pub node_hash: Vec, + pub signature: Option>, +} diff --git a/dan_layer/storage_sqlite/src/models/state_key_value.rs b/dan_layer/storage_sqlite/src/models/state_key_value.rs new file mode 100644 index 0000000000..74bbfdbabc --- /dev/null +++ b/dan_layer/storage_sqlite/src/models/state_key_value.rs @@ -0,0 +1,39 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::schema::*; + +#[derive(Queryable)] +pub struct StateKeyValue { + pub id: i32, + pub schema_name: String, + pub key: Vec, + pub value: Vec, +} + +#[derive(Insertable)] +#[table_name = "state_key_values"] +pub struct NewStateKeyValue { + pub schema_name: String, + pub key: Vec, + pub value: Vec, +} diff --git a/dan_layer/storage_sqlite/src/schema.rs b/dan_layer/storage_sqlite/src/schema.rs new file mode 100644 index 0000000000..c5246a1b44 --- /dev/null +++ b/dan_layer/storage_sqlite/src/schema.rs @@ -0,0 +1,53 @@ +table! { + instructions (id) { + id -> Integer, + hash -> Binary, + node_id -> Integer, + template_id -> Integer, + method -> Text, + args -> Binary, + } +} + +table! { + locked_qc (id) { + id -> Integer, + message_type -> Integer, + view_number -> BigInt, + node_hash -> Binary, + signature -> Nullable, + } +} + +table! { + nodes (id) { + id -> Integer, + hash -> Binary, + parent -> Binary, + height -> Integer, + is_committed -> Bool, + } +} + +table! { + prepare_qc (id) { + id -> Integer, + message_type -> Integer, + view_number -> BigInt, + node_hash -> Binary, + signature -> Nullable, + } +} + +table! { + state_key_values (id) { + id -> Integer, + schema_name -> Text, + key -> Binary, + value -> Binary, + } +} + +joinable!(instructions -> nodes (node_id)); + +allow_tables_to_appear_in_same_query!(instructions, locked_qc, nodes, prepare_qc, state_key_values,); diff --git a/dan_layer/storage_sqlite/src/sqlite_backend_adapter.rs b/dan_layer/storage_sqlite/src/sqlite_backend_adapter.rs new file mode 100644 index 0000000000..ef82e57638 --- /dev/null +++ b/dan_layer/storage_sqlite/src/sqlite_backend_adapter.rs @@ -0,0 +1,252 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::{ + error::SqliteStorageError, + models::{ + locked_qc::LockedQc, + node::{NewNode, Node}, + prepare_qc::PrepareQc, + }, + schema::{locked_qc::dsl, *}, + SqliteTransaction, +}; +use diesel::{prelude::*, Connection, SqliteConnection}; +use diesel_migrations::embed_migrations; +use std::convert::TryFrom; +use tari_dan_core::{ + models::{HotStuffMessageType, Payload, QuorumCertificate, Signature, TariDanPayload, TreeNodeHash, ViewId}, + storage::{BackendAdapter, NewUnitOfWorkTracker, StorageError, UnitOfWorkTracker}, +}; + +#[derive(Clone)] +pub struct SqliteBackendAdapter { + database_url: String, +} + +impl SqliteBackendAdapter { + pub fn new(database_url: String) -> SqliteBackendAdapter { + Self { database_url } + } +} + +impl BackendAdapter for SqliteBackendAdapter { + type BackendTransaction = SqliteTransaction; + type Error = SqliteStorageError; + type Id = i32; + type Payload = TariDanPayload; + + fn is_empty(&self) -> Result { + let connection = SqliteConnection::establish(self.database_url.as_str())?; + let n: Option = nodes::table.first(&connection).optional()?; + Ok(n.is_none()) + } + + fn create_transaction(&self) -> Result { + let connection = SqliteConnection::establish(self.database_url.as_str())?; + connection.execute("PRAGMA foreign_keys = ON;"); + connection.execute("BEGIN EXCLUSIVE TRANSACTION;"); + + Ok(SqliteTransaction::new(connection)) + } + + fn insert(&self, item: &NewUnitOfWorkTracker, transaction: &Self::BackendTransaction) -> Result<(), Self::Error> { + match item { + NewUnitOfWorkTracker::Node { hash, parent, height } => { + let new_node = NewNode { + hash: Vec::from(hash.as_bytes()), + parent: Vec::from(parent.as_bytes()), + height: *height as i32, + }; + diesel::insert_into(nodes::table) + .values(&new_node) + .execute(transaction.connection())?; + }, + NewUnitOfWorkTracker::Instruction { .. } => { + todo!() + }, + } + Ok(()) + } + + fn update( + &self, + id: &Self::Id, + item: &UnitOfWorkTracker, + transaction: &Self::BackendTransaction, + ) -> Result<(), Self::Error> { + match item { + UnitOfWorkTracker::SidechainMetadata => { + todo!() + }, + UnitOfWorkTracker::LockedQc { + message_type, + view_number, + node_hash, + signature, + } => { + use crate::schema::locked_qc::dsl; + let message_type = message_type.as_u8() as i32; + let existing: Result = dsl::locked_qc.find(id).first(transaction.connection()); + match existing { + Ok(x) => { + diesel::update(dsl::locked_qc.find(id)) + .set(( + dsl::message_type.eq(message_type), + dsl::view_number.eq(view_number.0 as i64), + dsl::node_hash.eq(node_hash.as_bytes()), + dsl::signature.eq(signature.as_ref().map(|s| s.to_bytes())), + )) + .execute(transaction.connection())?; + }, + Err(_) => { + diesel::insert_into(locked_qc::table) + .values(( + dsl::id.eq(id), + dsl::message_type.eq(message_type), + dsl::view_number.eq(view_number.0 as i64), + dsl::node_hash.eq(node_hash.as_bytes()), + dsl::signature.eq(signature.as_ref().map(|s| s.to_bytes())), + )) + .execute(transaction.connection())?; + }, + } + }, + UnitOfWorkTracker::PrepareQc { + message_type, + view_number, + node_hash, + signature, + } => { + use crate::schema::prepare_qc::dsl; + let message_type = message_type.as_u8() as i32; + let existing: Result = dsl::prepare_qc.find(id).first(transaction.connection()); + match existing { + Ok(x) => { + diesel::update(dsl::prepare_qc.find(id)) + .set(( + dsl::message_type.eq(message_type), + dsl::view_number.eq(view_number.0 as i64), + dsl::node_hash.eq(node_hash.as_bytes()), + dsl::signature.eq(signature.as_ref().map(|s| s.to_bytes())), + )) + .execute(transaction.connection())?; + }, + Err(_) => { + diesel::insert_into(prepare_qc::table) + .values(( + dsl::id.eq(id), + dsl::message_type.eq(message_type), + dsl::view_number.eq(view_number.0 as i64), + dsl::node_hash.eq(node_hash.as_bytes()), + dsl::signature.eq(signature.as_ref().map(|s| s.to_bytes())), + )) + .execute(transaction.connection())?; + }, + } + }, + UnitOfWorkTracker::Node { + hash, + parent, + height, + is_committed, + } => { + use crate::schema::nodes::dsl; + diesel::update(dsl::nodes.find(id)) + .set(( + dsl::hash.eq(&hash.0), + dsl::parent.eq(&parent.0), + dsl::height.eq(*height as i32), + dsl::is_committed.eq(is_committed), + )) + .execute(transaction.connection())?; + }, + } + Ok(()) + } + + fn commit(&self, transaction: &Self::BackendTransaction) -> Result<(), Self::Error> { + transaction.connection().execute("COMMIT TRANSACTION;")?; + Ok(()) + } + + fn locked_qc_id(&self) -> Self::Id { + 1 + } + + fn prepare_qc_id(&self) -> Self::Id { + 1 + } + + fn find_highest_prepared_qc(&self) -> Result { + use crate::schema::*; + let connection = SqliteConnection::establish(self.database_url.as_str())?; + // TODO: this should be a single row + let result: Option = prepare_qc::table + .order_by(prepare_qc::view_number.desc()) + .first(&connection) + .optional()?; + let qc = match result { + Some(r) => r, + None => { + let l: LockedQc = dsl::locked_qc.find(self.locked_qc_id()).first(&connection)?; + PrepareQc { + id: 1, + message_type: l.message_type, + view_number: l.view_number, + node_hash: l.node_hash.clone(), + signature: l.signature.clone(), + } + }, + }; + + Ok(QuorumCertificate::new( + HotStuffMessageType::try_from(qc.message_type as u8).unwrap(), + ViewId::from(qc.view_number as u64), + TreeNodeHash(qc.node_hash.clone()), + qc.signature.map(|s| Signature::from_bytes(s.as_slice())), + )) + } + + fn get_locked_qc(&self) -> Result { + let connection = SqliteConnection::establish(self.database_url.as_str())?; + let qc: LockedQc = dsl::locked_qc.find(self.locked_qc_id()).first(&connection)?; + Ok(QuorumCertificate::new( + HotStuffMessageType::try_from(qc.message_type as u8).unwrap(), + ViewId::from(qc.view_number as u64), + TreeNodeHash(qc.node_hash.clone()), + qc.signature.map(|s| Signature::from_bytes(s.as_slice())), + )) + } + + fn find_node_by_hash(&self, node_hash: &TreeNodeHash) -> Result<(Self::Id, UnitOfWorkTracker), Self::Error> { + use crate::schema::nodes::dsl; + let connection = SqliteConnection::establish(self.database_url.as_str())?; + let node: Node = dsl::nodes.filter(nodes::hash.eq(&node_hash.0)).first(&connection)?; + Ok((node.id, UnitOfWorkTracker::Node { + hash: TreeNodeHash(node.hash), + parent: TreeNodeHash(node.parent), + height: node.height as u32, + is_committed: node.is_committed, + })) + } +} diff --git a/dan_layer/storage_sqlite/src/sqlite_db_factory.rs b/dan_layer/storage_sqlite/src/sqlite_db_factory.rs new file mode 100644 index 0000000000..c72d44e785 --- /dev/null +++ b/dan_layer/storage_sqlite/src/sqlite_db_factory.rs @@ -0,0 +1,64 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::{error::SqliteStorageError, SqliteBackendAdapter}; +use diesel::{prelude::*, Connection, SqliteConnection}; +use diesel_migrations::embed_migrations; +use tari_common::GlobalConfig; +use tari_dan_core::storage::{ChainDb, DbFactory, StateDb, StateDbUnitOfWorkImpl, StorageError}; + +#[derive(Clone)] +pub struct SqliteDbFactory { + database_url: String, +} + +impl SqliteDbFactory { + pub fn new(config: &GlobalConfig) -> Self { + let database_url = config + .data_dir + .join("dan_storage.sqlite") + .into_os_string() + .into_string() + .unwrap(); + + Self { database_url } + } + + fn create_adapter(&self) -> SqliteBackendAdapter { + SqliteBackendAdapter::new(self.database_url.clone()) + } +} + +impl DbFactory for SqliteDbFactory { + fn create(&self) -> Result, StorageError> { + let connection = SqliteConnection::establish(self.database_url.as_str()).map_err(SqliteStorageError::from)?; + connection.execute("PRAGMA foreign_keys = ON;"); + // Create the db + embed_migrations!("./migrations"); + embedded_migrations::run(&connection).map_err(SqliteStorageError::from)?; + Ok(ChainDb::new(self.create_adapter())) + } + + fn create_state_db(&self) -> Result, StorageError> { + Ok(StateDb::new()) + } +} diff --git a/dan_layer/core/src/storage/sqlite/mod.rs b/dan_layer/storage_sqlite/src/sqlite_storage_service.rs similarity index 74% rename from dan_layer/core/src/storage/sqlite/mod.rs rename to dan_layer/storage_sqlite/src/sqlite_storage_service.rs index 068646b5f1..678effef50 100644 --- a/dan_layer/core/src/storage/sqlite/mod.rs +++ b/dan_layer/storage_sqlite/src/sqlite_storage_service.rs @@ -20,23 +20,25 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::{ - models::{HotStuffTreeNode, QuorumCertificate, SidechainMetadata, TariDanPayload}, - storage::{BackendAdapter, ChainDbUnitOfWork, ChainStorageService, NewUnitOfWorkTracker, StorageError, UnitOfWork}, -}; use async_trait::async_trait; use std::sync::Arc; +use tari_dan_core::{ + models::{HotStuffTreeNode, QuorumCertificate, SidechainMetadata, TariDanPayload}, + storage::{ChainStorageService, StorageError, UnitOfWork}, +}; use tokio::sync::RwLock; pub struct SqliteStorageService {} +// TODO: this has no references to Sqlite, so may be worth moving to dan_layer.core + #[async_trait] impl ChainStorageService for SqliteStorageService { async fn get_metadata(&self) -> Result { todo!() } - async fn save_node( + async fn add_node( &self, node: &HotStuffTreeNode, db: TUnitOfWork, @@ -45,28 +47,17 @@ impl ChainStorageService for SqliteStorageService { for instruction in node.payload().instructions() { db.add_instruction(node.hash().clone(), instruction.clone())?; } - db.add_node(node.hash().clone(), node.parent().clone())?; + db.add_node(node.hash().clone(), node.parent().clone(), node.height())?; Ok(()) } -} - -#[derive(Clone)] -pub struct SqliteBackendAdapter {} - -pub struct SqliteTransaction {} - -impl BackendAdapter for SqliteBackendAdapter { - type BackendTransaction = SqliteTransaction; - - fn create_transaction(&self) -> Self::BackendTransaction { - SqliteTransaction {} - } - fn insert(&self, item: &NewUnitOfWorkTracker, transaction: &Self::BackendTransaction) -> Result<(), StorageError> { - todo!() - } - - fn commit(&self, transaction: &Self::BackendTransaction) -> Result<(), StorageError> { - todo!() + async fn set_locked_qc( + &self, + qc: QuorumCertificate, + db: TUnitOfWork, + ) -> Result<(), StorageError> { + let mut db = db; + db.set_locked_qc(&qc)?; + Ok(()) } } diff --git a/dan_layer/storage_sqlite/src/sqlite_transaction.rs b/dan_layer/storage_sqlite/src/sqlite_transaction.rs new file mode 100644 index 0000000000..a29f0a1c4a --- /dev/null +++ b/dan_layer/storage_sqlite/src/sqlite_transaction.rs @@ -0,0 +1,37 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use diesel::SqliteConnection; + +pub struct SqliteTransaction { + connection: SqliteConnection, +} + +impl SqliteTransaction { + pub fn new(connection: SqliteConnection) -> Self { + Self { connection } + } + + pub fn connection(&self) -> &SqliteConnection { + &self.connection + } +} diff --git a/dan_layer/validator_node_sqlite/Cargo.toml b/dan_layer/validator_node_sqlite/Cargo.toml deleted file mode 100644 index 17b27cb3b5..0000000000 --- a/dan_layer/validator_node_sqlite/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "validator_node_sqlite" -version = "0.1.0" -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -diesel = { version = "1.4.8", features = ["sqlite"] } -dotenv = "0.15.0" \ No newline at end of file diff --git a/dan_layer/validator_node_sqlite/migrations/2021-11-02-185150_create_nodes/up.sql b/dan_layer/validator_node_sqlite/migrations/2021-11-02-185150_create_nodes/up.sql deleted file mode 100644 index 87d2ce970a..0000000000 --- a/dan_layer/validator_node_sqlite/migrations/2021-11-02-185150_create_nodes/up.sql +++ /dev/null @@ -1,15 +0,0 @@ --- Your SQL goes here -create table nodes ( - hash blob not null primary key, - parent blob not null -); - -create table instructions ( - id integer primary key autoincrement not null, - hash blob not null, - node_hash blob not null, - asset_id blob not null, - template_id int not null, - method text not null, - args blob not null -); \ No newline at end of file diff --git a/dan_layer/validator_node_sqlite/src/schema.rs b/dan_layer/validator_node_sqlite/src/schema.rs deleted file mode 100644 index 7a413a2344..0000000000 --- a/dan_layer/validator_node_sqlite/src/schema.rs +++ /dev/null @@ -1,20 +0,0 @@ -table! { - instructions (id) { - id -> Integer, - hash -> Binary, - node_hash -> Binary, - asset_id -> Binary, - template_id -> Integer, - method -> Text, - args -> Binary, - } -} - -table! { - nodes (hash) { - hash -> Binary, - parent -> Binary, - } -} - -allow_tables_to_appear_in_same_query!(instructions, nodes,);